PCB와 컨텍스트 스위칭 (Context Switching)
PCB(Process Control Block)는 운영체제가 프로세스 하나하나의 정보를 적어 두는 카드이고, 컨텍스트 스위칭(Context Switching)은 CPU에서 실행 중인 프로세스를 다른 프로세스로 바꿀 때, 지금 상태를 PCB에 저장하고 다음 프로세스의 상태를 PCB에서 꺼내 오는 과정입니다.
한눈에 보기
- CPU는 한 번에 하나의 프로세스만 실행할 수 있습니다(코어 하나 기준). 그런데도 음악을 들으면서 문서를 쓸 수 있는 이유는, 운영체제가 아주 짧은 시간마다 프로세스를 번갈아 바꿔 끼우기 때문입니다.
- 비유: 혼자서 여러 손님의 상담을 번갈아 하는 상담사를 떠올려 보세요.
- 손님마다 상담 카드(PCB) 가 있습니다. 이름(PID), 지금 상태, 어디까지 이야기했는지(다음에 실행할 위치)가 적혀 있습니다.
- 한 손님과의 시간이 끝나면, 지금까지 나눈 이야기를 카드에 적어 두고(저장), 다음 손님의 카드를 꺼내 읽고 이어서 상담합니다(복원). 이 바꿔 앉는 과정이 컨텍스트 스위칭입니다.
- 카드를 적고 읽는 시간에는 상담을 못 합니다. 이것이 컨텍스트 스위칭의 오버헤드(비용) 입니다.
프로세스 관리와 프로세스 메타데이터
여러 프로세스가 있을 때 운영체제가 CPU 스케줄링으로 누가 언제 CPU를 쓸지 정하고 관리하는 것을 프로세스 관리라고 합니다.
관리하려면 먼저 각 프로세스가 누구인지, 어떤 상태인지 알아야 합니다. 이렇게 프로세스의 특징을 담은 정보를 프로세스 메타데이터라고 합니다.
| 메타데이터 | 뜻 |
|---|---|
| Process ID (PID) | 프로세스를 구분하는 고유 번호 |
| Process State | 지금 상태 (준비, 실행, 대기 등. 아래에서 설명) |
| Process Priority | 우선순위. 스케줄링할 때 누구를 먼저 실행할지 정하는 데 씀 |
| CPU Registers | 멈출 당시 CPU 레지스터 값. 다음에 실행할 명령어 주소(PC) 포함 |
| Owner | 이 프로세스를 실행한 사용자 (권한 확인에 사용) |
| CPU Usage | 지금까지 CPU를 얼마나 썼는지 |
| Memory Usage | 메모리를 얼마나, 어디를 쓰고 있는지 |
이 메타데이터는 프로세스가 생성될 때 PCB에 저장됩니다.
PCB (Process Control Block)
PCB는 프로세스 메타데이터를 저장해 두는 곳입니다. PCB 하나에는 프로세스 하나의 정보가 담깁니다. 운영체제(커널)가 자기 메모리 영역에 만들어 관리하기 때문에, 일반 프로그램은 PCB를 직접 고칠 수 없습니다.
아래 그림처럼 저장장치에 있던 프로그램이 실행되어 프로세스가 되면, 메모리에 code, data, stack 영역이 만들어지고 그와 함께 그 프로세스를 설명하는 PCB도 하나 만들어집니다.

그림 출처: gyoogle 기술 면접 정리, Process Management & PCB
PCB 하나에 들어 있는 내용은 다음과 같습니다.
┌──────────────────────────────┐
│ PCB (프로세스 1개당 1개) │
├──────────────────────────────┤
│ 포인터 (다음 PCB 주소) │ ← PCB끼리 연결 리스트로 이어짐
│ 프로세스 상태 (Ready ...) │
│ 프로세스 ID (PID) │
│ 프로그램 카운터 (PC) │ ← 다음에 실행할 명령어 위치
│ CPU 레지스터 값 │
│ 스케줄링 정보 (우선순위 등) │
│ 메모리 관리 정보 │
│ 입출력 상태 (열어 둔 파일 등) │
│ 계정 정보 (소유자, CPU 사용량) │
└──────────────────────────────┘다시 정리해 보면
프로그램 실행
→ 프로세스 생성
→ 프로세스 주소 공간에 (코드, 데이터, 힙, 스택) 영역 생성
→ 이 프로세스의 메타데이터가 PCB에 저장주소 공간의 각 영역은 메모리 구조 글에서 자세히 다룹니다.
PCB가 왜 필요한가요?
CPU에서는 프로세스의 상태에 따라 실행 중인 프로세스를 교체합니다. 예를 들어 실행 중이던 프로세스가 입출력을 기다리느라 대기(Waiting) 상태가 되거나, 타이머 인터럽트로 정해진 시간이 끝나면 다른 프로세스를 실행(Running) 상태로 올립니다.
이때 나중에 다시 실행할 프로세스가 어디까지 했는지를 PCB에 저장해 두는 것입니다. 그래야 다음 차례에 멈췄던 바로 그 자리부터 이어서 실행할 수 있습니다.
PCB는 어떻게 관리되나요?
- 연결 리스트(Linked List) 방식으로 관리합니다.
- PCB 리스트의 시작점(Head)에 PCB가 생성될 때마다 이어 붙습니다. 주소값으로 연결된 연결 리스트라서 중간에 넣고 빼기가 쉽습니다. (이중 연결 리스트 참고)
- 즉, 프로세스가 생성되면 PCB가 생성되고, 프로세스가 끝나면 PCB도 제거됩니다. (정확히는 부모가
wait로 종료 상태를 받아 간 뒤에 제거됩니다. 시스템 콜의 좀비 프로세스 참고) - 실제 운영체제는 상태별로 줄을 따로 세워 둡니다. 실행을 기다리는 PCB들의 줄을 준비 큐(Ready Queue), 입출력을 기다리는 PCB들의 줄을 대기 큐(Wait Queue) 라고 합니다.
프로세스의 상태 (5가지)
프로세스는 태어나서 끝날 때까지 아래 다섯 가지 상태를 오갑니다.
┌──────────────── ② 디스패치 ───────────────┐
│ ▼
┌──────┐ ① 승인 ┌───────┐ ┌─────────┐ ⑥ 종료 ┌────────────┐
│ New │ ───────▶ │ Ready │ ◀──── ③ 시간 끝(인터럽트) ─── │ Running │ ───────▶ │ Terminated │
└──────┘ └───────┘ └─────────┘ └────────────┘
▲ │
│ │ ④ 입출력 요청 / 이벤트 대기
│ ┌─────────┐ │
└── ⑤ ──── │ Waiting │ ◀────────────────┘
입출력 완료 └─────────┘| 상태 | 뜻 | 상담사 비유 |
|---|---|---|
| New (생성) | 프로세스가 막 만들어지는 중 | 접수처에서 상담 카드를 만드는 중 |
| Ready (준비) | 실행할 준비는 끝났고 CPU 차례만 기다림 | 대기석에 앉아 내 차례를 기다림 |
| Running (실행) | CPU를 받아 실제로 명령어를 실행 중 | 지금 상담사와 이야기하는 중 |
| Waiting (대기) | 입출력이나 이벤트가 끝나기를 기다림. CPU를 줘도 할 일이 없음 | 서류를 떼러 다른 창구에 다녀오는 중 |
| Terminated (종료) | 실행이 끝남 | 상담 끝, 카드 정리 |
| 번호 | 상태 변화 | 언제 일어나나 |
|---|---|---|
| ① | New → Ready | 운영체제가 프로세스를 받아들여 준비 큐에 넣음 |
| ② | Ready → Running | 스케줄러가 이 프로세스를 골라 CPU를 줌 (디스패치) |
| ③ | Running → Ready | 정해진 CPU 사용 시간이 끝났거나, 더 급한 프로세스가 와서 CPU를 빼앗김 |
| ④ | Running → Waiting | 파일 읽기, 키보드 입력처럼 입출력을 요청하고 기다림 |
| ⑤ | Waiting → Ready | 기다리던 입출력이 끝남. 바로 Running이 아니라 다시 줄을 섬 |
| ⑥ | Running → Terminated | 실행을 마치거나 강제 종료됨 |
Waiting에서 Running으로 바로 가지 않는다는 점이 자주 나오는 질문입니다. 입출력이 끝나도 CPU는 이미 다른 프로세스가 쓰고 있으므로, 다시 준비 큐에 줄을 서야 합니다.
컨텍스트 스위칭 (Context Switching)
수행 중인 프로세스를 바꿀 때 CPU의 레지스터 정보가 바뀌는 것을 컨텍스트 스위칭이라고 합니다. 여기서 컨텍스트(문맥)는 "프로세스가 지금 어디까지, 어떤 상태로 실행되고 있었는지"를 뜻합니다.
정의: 운영체제가 실행 중이던 프로세스의 상태(레지스터 값 등)를 그 프로세스의 PCB에 저장하고, 다음에 실행할 프로세스의 PCB에서 상태를 읽어 CPU 레지스터에 불러오는 과정
프로세스 A 운영체제 프로세스 B
(Running) (Ready)
│
│── 인터럽트 / 시스템 콜 ──▶ A의 상태를 PCB_A에 저장
│ PCB_B에서 B의 상태 불러오기
(Ready) │
└──────────▶ (Running)
│
B의 상태를 PCB_B에 저장 ◀────────│ 인터럽트 / 시스템 콜
PCB_A에서 A의 상태 불러오기
◀───────────────────────────┘
(Running) (Ready)
상태를 저장하고 불러오는 동안 (가운데 운영체제 부분)에는
A도 B도 실행되지 않는다 → 이 시간이 오버헤드언제 일어나나요?
보통 아래와 같은 경우에 일어납니다.
- 인터럽트가 발생했을 때
- 실행 중인 프로세스가 CPU 사용 허가 시간(타임 슬라이스)을 모두 쓴 경우 (타이머 인터럽트)
- 입출력을 위해 기다려야 하는 경우
즉, 위 상태 그림에서 Running 상태인 프로세스가 CPU를 내려놓고(③ Running → Ready, ④ Running → Waiting, ⑥ 종료), 준비 큐의 다른 프로세스가 CPU를 받을 때(② Ready → Running) 일어납니다.
코드로 감 잡기: 아주 작은 스케줄러 흉내 내기
실제 컨텍스트 스위칭은 커널 안에서 일어나 직접 볼 수 없으니, PCB를 딕셔너리로 흉내 낸 간단한 시뮬레이션으로 흐름을 살펴보겠습니다. 프로세스마다 2칸씩 번갈아 실행합니다.
from collections import deque
# 프로세스마다 PCB(정보 카드)를 하나씩 만든다
def make_pcb(pid, name, total_steps):
return {"pid": pid, "name": name, "state": "Ready",
"pc": 0, # 다음에 실행할 줄 번호 (프로그램 카운터)
"total": total_steps}
ready_queue = deque([make_pcb(1, "음악", 3), make_pcb(2, "문서", 5)])
TIME_SLICE = 2 # 한 번에 CPU를 쓸 수 있는 시간 (2칸)
cpu_pc = None # CPU 레지스터 (지금 실행 중인 위치)
while ready_queue:
pcb = ready_queue.popleft()
cpu_pc = pcb["pc"] # PCB → 레지스터로 불러오기 (복원)
pcb["state"] = "Running"
for _ in range(TIME_SLICE):
if cpu_pc == pcb["total"]:
break
cpu_pc += 1
print(f" {pcb['name']} {cpu_pc}번째 줄 실행")
pcb["pc"] = cpu_pc # 레지스터 → PCB로 저장 (보관)
if pcb["pc"] == pcb["total"]:
pcb["state"] = "Terminated"
print(f"[끝] {pcb['name']} 종료, PCB 삭제")
else:
pcb["state"] = "Ready"
ready_queue.append(pcb)
print(f"[교체] {pcb['name']}의 위치 {pcb['pc']}를 PCB에 저장하고 다음 프로세스로")실행 결과:
음악 1번째 줄 실행
음악 2번째 줄 실행
[교체] 음악의 위치 2를 PCB에 저장하고 다음 프로세스로
문서 1번째 줄 실행
문서 2번째 줄 실행
[교체] 문서의 위치 2를 PCB에 저장하고 다음 프로세스로
음악 3번째 줄 실행
[끝] 음악 종료, PCB 삭제
문서 3번째 줄 실행
문서 4번째 줄 실행
[교체] 문서의 위치 4를 PCB에 저장하고 다음 프로세스로
문서 5번째 줄 실행
[끝] 문서 종료, PCB 삭제"음악"이 두 번째 차례에 1번이 아니라 3번째 줄부터 이어서 실행된 것은, 첫 차례가 끝날 때 위치 2를 PCB에 저장해 두었기 때문입니다.
컨텍스트 스위칭의 오버헤드
오버헤드(Overhead)는 어떤 일을 하기 위해 추가로 드는 시간과 자원을 말합니다. 보통 안 좋은 뜻으로 많이 쓰입니다. 컨텍스트 스위칭에도 오버헤드가 있습니다.
- PCB에 상태를 저장하고 불러오는 동안 CPU는 어떤 프로세스의 일도 하지 못합니다.
- 새 프로세스로 바뀌면 CPU 캐시 메모리(자주 쓰는 데이터를 CPU 가까이에 둔 작은 저장소)에 있던 이전 프로세스의 데이터가 쓸모없어져서, 한동안 메모리에서 다시 읽어 오느라 느려집니다.
- 그래서 너무 자주 바꾸면 정작 일하는 시간보다 바꾸는 시간이 더 길어질 수 있습니다.
그런데도 이 오버헤드를 감수할 가치가 있습니다.
프로세스를 수행하다가 입출력 이벤트가 발생해서 대기 상태로 전환됨
이때 CPU를 그냥 놀게 두는 것보다, 다른 프로세스를 실행시키는 것이 훨씬 효율적즉, 입출력을 기다리는 동안 CPU가 놀지 않도록 다른 프로세스를 실행시키고, 여러 프로그램이 동시에 반응하는 것처럼 사용자에게 빠르게 일을 처리해 주기 위한 것입니다. 바꾸는 비용보다 CPU가 노는 시간을 줄여서 얻는 이득이 훨씬 큽니다.
같은 프로세스 안의 스레드끼리 바꿀 때는 메모리 영역을 공유하므로, 프로세스끼리 바꿀 때보다 저장하고 바꿀 정보가 적어서 더 가볍습니다. (프로세스와 스레드 참고)
정리
| 개념 | 비전공자 버전 요약 |
|---|---|
| 프로세스 관리 | 운영체제가 여러 프로세스에게 CPU를 번갈아 나눠 주는 일 |
| 프로세스 메타데이터 | PID, 상태, 우선순위, 레지스터 값 같은 프로세스의 신상 정보 |
| PCB | 프로세스마다 하나씩 있는 상담 카드. 어디까지 했는지가 적혀 있음 |
| PCB 관리 | 연결 리스트로 이어 두고, 프로세스가 생기면 추가, 끝나면 삭제 |
| 5가지 상태 | 생성, 준비, 실행, 대기, 종료. 대기가 끝나면 다시 준비로 줄을 섬 |
| 컨텍스트 스위칭 | 지금 손님 이야기를 카드에 적고, 다음 손님 카드를 꺼내 이어서 상담 |
| 발생 시점 | 인터럽트, 사용 시간 끝, 입출력 대기처럼 실행 중인 프로세스가 CPU를 내려놓을 때 |
| 오버헤드 | 카드 적고 읽는 동안은 아무 일도 못 하는 시간 |
| 감수하는 이유 | CPU가 입출력을 기다리며 노는 것보다 다른 일을 시키는 게 훨씬 이득 |
