문맥 교환과 PCB: CPU가 프로세스 상태를 전환하는 방식
문맥 교환의 처리 순서와 PCB 구성, 스케줄링·타임 퀀텀·선점의 관계, 오버헤드 원인과 운영체제 최적화 방식을 정리한다.
2026-08-14 · 최초 발행 2025-12-28
CPU 실행을 넘겨주기 전에 남겨야 하는 상태
멀티태스킹 환경에서 CPU는 한 시점에 하나의 프로세스를 실행한다. 여러 작업이 동시에 진행되는 것처럼 보이는 이유는 운영체제가 실행 대상을 빠르게 바꾸기 때문이다. 이때 현재 프로세스가 멈춘 지점과 실행 상태를 보관하고, 다음 프로세스의 상태를 CPU에 되돌리는 작업이 문맥 교환(Context Switching)이다.
문맥은 프로세스의 실행 상태를 나타내는 모든 정보다. 교환은 한 프로세스의 문맥을 저장한 뒤 다른 프로세스의 문맥을 복원하는 절차를 뜻한다. 제한된 시간인 타임 퀀텀 동안 프로세스가 CPU를 사용하도록 하는 시분할 시스템에서, 문맥 교환은 CPU 공유를 가능하게 하는 기반이다.
전환은 현재 프로세스의 상태를 PCB에 저장하는 데서 시작한다. 스케줄러가 다음 실행 대상을 고르면, 해당 프로세스 PCB에 있던 상태를 CPU로 로드해 실행을 이어 간다.
PCB는 프로세스 상태를 보관하는 커널 자료구조다
PCB(Process Control Block)는 프로세스와 관련된 중요 정보를 한데 보관하는 자료구조다. 운영체제에 따라 Task Control Block(TCB)이라고 부르기도 하며, Linux에서는 task_struct로 구현된다. 일반적으로 커널 메모리 영역에 저장된다.
PCB에는 프로세스가 생성 중인 New, CPU 할당을 기다리는 Ready, 실행 중인 Running, I/O 완료 등의 이벤트를 기다리는 Waiting, 실행이 끝난 Terminated 상태가 기록된다. 시스템 내 고유 식별자인 PID와 부모-자식 관계를 나타내는 PPID도 자원 추적과 프로세스 계층 관리에 쓰인다.
프로그램 카운터는 다음에 실행할 명령어의 메모리 주소를 나타낸다. 이 값이 보존되어야 문맥 교환 뒤에도 정확한 위치에서 실행을 재개할 수 있으며, 크기와 형식은 CPU 아키텍처에 따라 달라진다.
레지스터 정보에는 데이터 연산에 쓰이는 범용 레지스터, 현재 스택 최상위 위치를 가리키는 스택 포인터, 주소 계산의 기준이 되는 베이스 레지스터, CPU 플래그와 조건 코드를 담는 상태 레지스터, 배열 접근 등에 쓰이는 인덱스 레지스터가 포함된다.
메모리 관리 정보로는 프로세스 메모리 시작 주소를 나타내는 베이스 레지스터, 사용 가능한 메모리 범위를 나타내는 한계 레지스터, 가상 메모리와 물리 메모리의 매핑을 위한 페이지 테이블, 세그먼테이션 환경의 세그먼트 테이블이 있다.
스케줄링 정보는 우선순위, 소속 스케줄링 큐 포인터, 누적 CPU 사용 시간, 프로세스에 할당된 타임 퀀텀을 포함한다.
I/O 상태 정보에는 열린 파일 목록, 할당된 I/O 장치, 완료를 기다리는 I/O 요청, 네트워크 소켓 연결 정보가 들어간다.
CPU를 다른 작업에 넘기는 시점
타임 퀀텀이 끝나면 타이머 인터럽트가 발생하고, 운영체제는 CPU 시간을 다음 프로세스에 배분할 수 있다. 이 방식은 각 프로세스가 CPU를 사용할 기회를 보장하는 시분할 시스템의 동작과 연결된다.
문맥 교환은 하드웨어 인터럽트에도 이어질 수 있다. I/O 완료, 타이머, 전원 이상이 여기에 속하며, 시스템 콜과 예외 처리 같은 소프트웨어 인터럽트도 전환 계기가 된다. 더 높은 우선순위 작업이 실행을 요청하는 경우도 마찬가지다.
프로세스가 스스로 sleep() 시스템 콜을 호출하거나 세마포어·뮤텍스 같은 동기화 객체를 기다리고, 특정 조건이 충족될 때까지 대기하는 경우에도 실행 대상을 바꿀 수 있다. 디스크 읽기·쓰기나 네트워크 송수신처럼 블로킹 I/O를 요청한 프로세스는 완료를 기다리는 동안 CPU를 다른 프로세스에 넘긴다.
정상 종료는 exit() 시스템 콜로 일어나며, 예외나 시그널에 따른 비정상 종료도 있다. 종료된 프로세스가 사용하던 자원은 회수된다.
디스패처, 타임 퀀텀, 선점의 역할
디스패칭은 스케줄러가 선택한 프로세스에 실제로 CPU를 할당하는 과정이다. 이를 수행하는 디스패처는 문맥을 교환하고 사용자 모드로 전환한 뒤, 적절한 위치로 점프해 프로그램 실행을 다시 시작한다.
타임 퀀텀은 프로세스에 배정되는 CPU 시간의 단위이며, 전형적 크기는 10~100 밀리초다. 너무 작으면 빠른 응답 시간을 얻는 대신 문맥 교환 오버헤드가 커지고, 너무 크면 오버헤드는 낮아지지만 응답 시간이 길어져 대화식 성능이 저하된다.
선점형 스케줄링은 실행 중인 프로세스를 강제로 멈추고 다른 프로세스를 실행한다. 비선점형 스케줄링은 프로세스가 자발적으로 CPU를 양보할 때까지 기다린다. 선점은 CPU 시간을 공정하게 나누고 응답 시간을 개선할 수 있지만, 문맥 교환 오버헤드를 높일 수 있다.
상태 보존과 캐시 손실이 만드는 비용
문맥 교환의 직접 비용에는 상태 저장·복원 시간, 스케줄러 실행 시간, 메모리 접근 비용이 포함된다. 간접 비용으로는 캐시 무효화, TLB 플러시, CPU 파이프라인 플러시가 있다.
레지스터 저장과 복원에는 수십~수백 개 레지스터 값을 메모리에 읽고 쓰는 시간이 든다. 메모리 관리 구조를 전환하면서 페이지 테이블을 바꾸고 TLB를 플러시할 수 있다. 새 프로세스의 데이터가 캐시를 채우면 기존 데이터는 무효화되고, CPU 파이프라인에 있던 명령어도 버려질 수 있다.
전형적인 문맥 교환 시간은 110 마이크로초다. CPU 개수, 메모리 속도, 캐시 크기 같은 시스템 조건에 따라 달라지며, 문맥 교환 빈도가 높을 때 전체 CPU 시간의 15%를 소비할 수 있다.
전환 빈도와 보존 범위를 조절하는 방법
동시 실행 프로세스가 많아질수록 문맥 교환이 잦아지고 오버헤드가 커질 수 있다. 프로세스 수를 적절히 제한하고 CPU 코어 수에 비례해 조절하면, 과도한 페이징으로 인한 스래싱도 피할 수 있다.
같은 프로세스 안의 스레드 사이에서는 메모리 컨텍스트가 유지된다. 코드·데이터·파일을 공유하고 메모리 관리 구조를 바꾸지 않은 채 레지스터를 교체할 수 있어, 프로세스 전환보다 비용을 낮출 수 있다. 사용자 레벨 스레드는 커널 스레드보다 더 빠른 전환에 활용할 수 있다.
협력적 멀티태스킹의 코루틴이나 Fiber/Green Thread 구현은 스택 포인터 변경으로 전환 범위를 줄일 수 있다.
PCB 크기를 줄이고 필수 정보만 저장하면 메모리 접근 시간을 낮출 수 있다. 일부 상태 정보는 실제로 필요해질 때까지 저장을 미루고, 자주 쓰는 레지스터를 우선 저장·복원하는 방식도 가능하다. Copy-on-Write는 메모리 페이지를 즉시 복사하지 않고 필요할 때 복사한다.
하드웨어 차원의 지원도 전환 비용에 영향을 준다. 일부 CPU는 여러 레지스터 세트를 지원하며, 태그된 TLB는 각 항목에 프로세스 ID를 태그해 플러시를 줄인다. 일부 아키텍처는 하드웨어 수준의 빠른 문맥 교환을 지원한다.
운영체제가 줄이는 전환 경로
Linux의 문맥 교환 경로는 메모리 컨텍스트를 바꾸고 CPU 컨텍스트를 전환하는 형태로 볼 수 있다.
// Linux 문맥 교환 핵심 코드 예시 (단순화)
struct task_struct *context_switch(struct task_struct *prev,
struct task_struct *next)
{
// 메모리 컨텍스트 전환
switch_mm(prev->mm, next->mm, next);
// CPU 컨텍스트 전환 (레지스터 등)
switch_to(prev, next);
return prev;
}
Linux는 부동소수점 레지스터를 실제 사용 시에만 저장하는 Lazy FPU 저장을 사용하고, PCID(Process-Context Identifier)를 활용해 TLB를 최적화한다. CFS(Completely Fair Scheduler)는 효율적인 스케줄링을 담당한다.
Windows는 필요한 최소 정보만 담는 Context 구조체를 사용한다. SYSENTER/SYSEXIT 명령어를 이용한 Fast System Call로 모드 전환을 빠르게 하고, Thread Pool로 스레드 생성과 삭제의 오버헤드를 줄인다.
전환 횟수를 관찰하고 튜닝에 반영하기
Linux에서는 vmstat, pidstat, /proc/stat, 프로세스별 status를 통해 문맥 교환을 확인할 수 있다.
# Linux에서 문맥 교환 횟수 확인
vmstat 1
# 프로세스별 문맥 교환 확인
pidstat -w 1
# 시스템 전체 통계
cat /proc/stat | grep ctxt
# 특정 프로세스의 문맥 교환
cat /proc/[PID]/status | grep voluntary_ctxt_switches
Voluntary Switches는 프로세스가 자발적으로 CPU를 양보한 횟수이고, Involuntary Switches는 선점당한 횟수다. 비자발적 전환이 높다면 CPU 경쟁이 심하다는 뜻이다.
튜닝에서는 CPU 코어당 1~2개의 실행 가능 프로세스를 유지하고, I/O 바운드 작업과 CPU 바운드 작업의 특성에 맞춰 타임 퀀텀을 설정한다. 캐시 지역성을 높이기 위해 프로세스를 특정 CPU에 바인딩하는 친화성 설정과, 중요한 작업에 높은 우선순위를 부여하는 방식도 함께 고려할 수 있다.