PCB와 컨텍스트 스위칭
PCB에 보존되는 실행 상태를 살펴보고 프로세스 전환이 레지스터·페이지 테이블·캐시에 만드는 비용을 분석합니다.
OS는 수십, 수백 개의 프로세스를 동시에 관리합니다.
각 프로세스의 상태, CPU 레지스터 값, 메모리 정보, 열린 파일 목록을 어딘가에 기록해야 합니다.
프로세스 A에서 B로 전환한 후, 다시 A로 돌아왔을 때 A가 이전에 실행하던 그 지점에서 정확히 이어서 실행되어야 하니까요.
이 정보를 담는 자료구조가 PCB(Process Control Block)이고, 프로세스를 전환하는 과정이 컨텍스트 스위칭(Context Switching)입니다.
PCB의 구조
PCB는 OS가 프로세스를 관리하기 위해 유지하는 커널 수준의 데이터 구조입니다.
생성 시 관리 자료구조를 준비하고 종료 시 자원을 정리합니다. 종료 상태를 수거하기 전의 좀비에는 일부 기록이 남으므로, 종료와 모든 관리 정보의 해제는 같은 순간이 아닙니다.
PCB는 프로세스의 신분증이자 상태 기록부입니다.
Linux에서는 OS 스레드마다 task_struct가 있고, 연결된 메모리·파일 정보와 커널 스택·아키텍처별 문맥 구조가 함께 이 역할을 합니다. 모든 레지스터가 task_struct 안에 직접 들어가는 것은 아닙니다.
Linux 커널 소스의 include/linux/sched.h에 정의되어 있으며, 수백 개의 필드를 가지고 있습니다.
크기가 수 KB에 달하는 복잡한 구조체입니다.
PCB에 저장되는 주요 정보를 범주별로 정리하면 다음과 같습니다.
식별 정보
- PID(Process ID): 프로세스를 고유하게 식별하는 양의 정수. Linux에서 PID 할당 범위의 설정은 환경에 따라 다르며,
/proc/sys/kernel/pid_max로 확인하고 변경할 수 있습니다. - PPID(Parent PID): 이 프로세스를 생성한 부모 프로세스의 PID.
- UID/GID: 프로세스를 실행한 사용자(User)와 그룹(Group)의 ID. 파일 접근 권한, 시그널 전송 권한 등을 결정합니다.
CPU 상태 정보
- 프로그램 카운터(PC/IP): 이 프로세스가 다음에 실행할 명령어의 메모리 주소.
- CPU 레지스터 값: 범용 레지스터(RAX, RBX, RCX, ...), 스택 포인터(RSP), 베이스 포인터(RBP), 플래그 레지스터(RFLAGS) 등의 현재 값. 컨텍스트 스위칭 시 이 값들이 저장되고 복원됩니다.
- FPU/SIMD 상태: 부동소수점 레지스터, SSE/AVX 레지스터의 값. 멀티미디어나 과학 계산 프로그램에서 중요합니다.
스케줄링 정보
- 프로세스 상태: Ready, Running, Waiting, Stopped, Zombie 등.
- 우선순위: 스케줄러가 CPU 할당 순서를 결정하는 데 사용하는 값. Linux에서는
nice값(-2019)과 SCHED_FIFO·SCHED_RR의 실시간 우선순위(199); 일반 정책의 sched_priority는 0가 있습니다. - CPU 사용 시간: 사용자 모드와 커널 모드에서 소비한 시간을 각각 기록합니다.
메모리 관리 정보
- 페이지 테이블 포인터: 가상 주소를 물리 주소로 변환하는 페이지 테이블의 위치. x86에서는 CR3 레지스터에 저장됩니다.
- 메모리 영역 정보: 텍스트, 데이터, 힙, 스택 각 영역의 시작/끝 주소와 접근 권한.
I/O 및 파일 정보
- 파일 디스크립터 테이블: 프로세스가 열어 놓은 모든 파일에 대한 참조. 0(stdin), 1(stdout), 2(stderr)은 표준 입출력의 관례이며, 실제로는 닫히거나 다른 대상으로 연결될 수 있습니다.
- 현재 작업 디렉토리:
pwd명령어로 보는 경로. - 시그널 관련 정보: 등록된 시그널 핸들러, 대기 중인 시그널, 차단된 시그널 마스크.
Linux에서 프로세스의 PCB 정보를 확인하는 가장 쉬운 방법은 /proc/<PID>/ 디렉토리입니다.
이 가상 파일 시스템은 커널이 실시간으로 생성하는 정보를 파일 형태로 제공합니다.
ls /proc/1/
# cmdline environ fd maps status stat ...
cat /proc/1/status
# Name: systemd
# State: S (sleeping)
# Pid: 1
# PPid: 0
# Uid: 0 0 0 0
# VmSize: 173048 kB
# VmRSS: 12940 kB
# Threads: 1
ls -l /proc/1/fd/
# 0 -> /dev/null (stdin)
# 1 -> /dev/null (stdout)
# 2 -> /dev/null (stderr)
# 3 -> socket:[12345]
# 4 -> /var/log/journal/...위 주석은 필드 형식을 설명하는 예시입니다. PID 1의 프로그램·열린 파일과 표시 권한은 시스템·컨테이너에 따라 달라집니다.
VmSize는 프로세스의 가상 메모리 총 크기, VmRSS는 실제 물리 메모리에 상주하는 크기(Resident Set Size)입니다.
top이나 ps에서 보는 메모리 사용량이 바로 이 값입니다.
컨텍스트 스위칭
컨텍스트 스위칭(Context Switching)은 CPU에서 실행 중인 프로세스를 다른 프로세스로 교체하는 과정입니다.
"컨텍스트"란 프로세스가 실행을 재개하는 데 필요한 모든 CPU 상태(레지스터 값, PC, 스택 포인터 등)를 의미합니다.
프로세스 A가 실행 중인데, 타이머 인터럽트가 발생하여 프로세스 B로 전환해야 하는 상황을 단계별로 따라가 보겠습니다.
-
인터럽트 발생: 타이머 인터럽트가 CPU에 전달됩니다. 사용자 모드에서 실행 중이었다면 커널의 인터럽트 처리 경로로 진입합니다.
-
현재 컨텍스트 저장: 재개에 필요한 레지스터·스택 문맥을 A의 커널 스택과 아키텍처별 저장 영역 등에 보존합니다. 여기서 PCB는 이러한 관리 정보 전체를 가리키는 개념입니다.
-
스케줄링 결정: 커널의 스케줄러가 Ready Queue를 확인하여 다음에 실행할 프로세스(B)를 결정합니다. 5장에서 이 결정 알고리즘을 다룹니다.
-
메모리 공간 전환: 주소 공간이 다른 B로 넘어갈 때 페이지 테이블의 기준을 바꿉니다. x86의 CR3와 PCID 등 구체적인 처리는 CPU·커널 설정에 따릅니다. 같은 주소 공간을 공유하는 스레드 사이에서는 이 변경을 생략할 수 있습니다.
-
새 컨텍스트 복원: B의 PCB에서 저장된 레지스터 값들을 CPU에 복원합니다. PC가 B가 마지막으로 실행하던 명령어 주소로 설정됩니다.
-
실행 재개: B가 중단되었던 커널 실행 지점에서 이어 가고, 사용자 실행으로 돌아갈 차례라면 사용자 모드로 복귀합니다.
A의 입장에서는 마치 잠깐 멈추었다가 이어서 실행되는 것처럼 보입니다.
컨텍스트 스위칭이 발생했다는 사실 자체를 알지 못합니다.
이것이 가능한 이유는 PCB에 모든 상태가 완벽하게 저장되고 복원되기 때문입니다.
컨텍스트 스위칭의 오버헤드
컨텍스트 스위칭은 순수한 오버헤드입니다.
전환하는 동안 CPU는 사용자의 어떤 작업도 수행하지 않습니다.
오버헤드는 크게 두 가지입니다.
직접적 오버헤드
레지스터를 저장하고 복원하는 시간, 스케줄링 결정에 걸리는 시간입니다.
소요 시간은 CPU·커널·보안 완화 설정·실행 문맥에 따라 달라집니다. 이 절에서는 특정 하드웨어의 시간을 측정하지 않았습니다.
간접적 오버헤드 (캐시 오염)
이것이 더 큰 문제입니다.
A와 B의 작업 집합이 캐시 용량을 두고 경쟁하면, 다시 실행할 때 필요한 데이터가 밀려나 캐시 미스가 늘 수 있습니다. 모든 캐시 내용이 전환마다 사라지는 것은 아닙니다.
페이지 테이블이 달라질 때는 주소 변환 캐시인 TLB도 고려해야 합니다. ASID·x86 PCID 같은 주소 공간 태그와 무효화 정책에 따라 기존 항목을 재사용할 수 있습니다. 필요한 변환이 TLB에 없을 때 페이지 테이블 탐색 비용이 생기며, 전환 후 모든 접근이 반드시 미스가 되는 것은 아닙니다.
이 오버헤드들이 누적되면 체감 성능에 영향을 줍니다.
산술 예로, 논리 CPU 하나에서 초당 1000번 전환하고 직접 비용을 매번 10μs로 가정하면 10ms, 즉 1초의 1%입니다. 이는 측정 결과가 아닙니다.
간접 비용은 실제 작업 집합으로 측정해야 하며 고정 비율로 더할 수 없습니다.
같은 프로세스의 스레드는 주소 공간을 공유하여 전환 비용 일부를 줄일 수 있습니다. 다만 캐시 경합과 스케줄러 비용은 여전히 있으며 항상 일정 배수로 빠르지는 않습니다.
4장에서 자세히 다룹니다.
# 시스템 전체 컨텍스트 스위칭 횟수
vmstat 1
# procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
# r b swpd free buff cache si so bi bo in cs us sy id wa st
# 1 0 0 3241284 145832 1284776 0 0 0 0 234 512 2 1 97 0 0
# ^^^ ^^^
# 인터럽트 컨텍스트스위칭
# 특정 프로세스의 컨텍스트 스위칭 확인
pid=1234 # 관찰할 실제 PID로 바꾸기
cat "/proc/$pid/status" | grep ctxt
# voluntary_ctxt_switches: 1523 (자발적: I/O 대기 등)
# nonvoluntary_ctxt_switches: 847 (비자발적: 선점 등)vmstat 1의 첫 보고는 부팅 이후의 평균이고 이후 보고가 1초 구간입니다. 주석의 수치는 설명용입니다.
voluntary_ctxt_switches는 해당 태스크가 대기 등으로 CPU를 양보한 횟수이고, nonvoluntary_ctxt_switches는 선점 등에 의한 전환 횟수입니다. 타이머만이 선점 원인은 아닙니다. 스레드별 값은 /proc/<PID>/task/<TID>/status에서 읽습니다.
비자발적 전환이 매우 많다면 CPU 경쟁이 심하다는 의미입니다.
컨텍스트 스위칭을 볼 때는 레지스터 저장/복원만 생각하면 비용을 과소평가하기 쉽습니다.
실제 성능에는 캐시와 TLB가 다시 따뜻해지는 간접 비용까지 함께 반영됩니다.
프로세스 큐
OS는 프로세스들을 여러 큐(Queue)로 조직합니다.
큐는 프로세스의 현재 상태에 따라 분류됩니다.
Ready Queue(준비 큐): CPU를 사용할 준비가 된 프로세스들을 담은 큐입니다.
스케줄러가 이 큐에서 다음에 실행할 프로세스를 선택합니다.
실제로는 단순한 FIFO 큐가 아니라, 우선순위 큐, 멀티레벨 큐 등 다양한 구조가 사용됩니다.
기존 CFS는 가상 실행 시간으로 레드-블랙 트리를 관리했습니다. 트리 갱신 비용과 가장 왼쪽 후보를 고르는 비용을 구분해야 합니다. Linux 6.6부터는 공정 스케줄링이 EEVDF로 전환되기 시작했습니다. 여기서 Ready Queue는 특정 자료구조 이름보다 실행 가능 후보들의 집합을 뜻합니다.
Wait Queue(대기 큐): 특정 이벤트(디스크 I/O 완료, 네트워크 패킷 도착, 시그널 수신 등)를 기다리는 프로세스들의 목록입니다.
이벤트의 종류별로 별도의 큐가 존재합니다.
디스크 I/O를 기다리는 프로세스들의 큐, 네트워크 I/O를 기다리는 큐가 각각 있습니다.
이벤트가 발생하면 해당 큐의 프로세스(들)를 Ready Queue로 옮깁니다.
큐의 구현과 스케줄러의 알고리즘에 따라, 어떤 프로세스가 먼저 CPU를 받는지, 얼마나 오래 실행하는지, 얼마나 빨리 응답하는지가 결정됩니다.
이것이 5장에서 다룰 CPU 스케줄링의 핵심입니다.
PCB의 각 필드가 컨텍스트 스위칭의 어느 단계에서 쓰이는지 연결하면, 저장과 복원이 단순 복사가 아니라 실행 재개의 조건임을 이해하기 쉽습니다.
다음 절에서는 프로세스가 어떻게 생성되고 종료되는지, fork()와 exec()의 실제 동작을 살펴보겠습니다.