[Daily morning study] 컨텍스트 스위칭(Context Switching)의 동작 원리
#daily morning study
컨텍스트 스위칭(Context Switching)의 동작 원리
컨텍스트 스위칭이란
CPU는 한 번에 하나의 프로세스(또는 스레드)만 실행할 수 있다. 그런데 우리는 웹 브라우저를 켜면서 음악을 듣고, 동시에 파일을 다운로드한다. 이게 가능한 이유는 OS가 아주 빠르게 여러 작업을 번갈아 실행하기 때문이다. 이 전환 과정을 컨텍스트 스위칭(Context Switching)이라 한다.
컨텍스트(context)란 CPU가 어떤 프로세스를 실행하는 데 필요한 모든 정보다. 현재 실행 중인 프로세스의 상태를 저장해두고, 다른 프로세스의 상태를 불러와 CPU에 넘기는 것이 컨텍스트 스위칭이다.
언제 컨텍스트 스위칭이 발생하는가
컨텍스트 스위칭은 주로 세 가지 상황에서 일어난다.
1. 타이머 인터럽트 (Timer Interrupt)
OS는 타임 퀀텀(time quantum) 또는 타임 슬라이스(time slice)라는 일정 시간 단위를 설정한다. 이 시간이 지나면 하드웨어 타이머가 인터럽트를 발생시키고, OS 스케줄러가 다음 프로세스로 교체한다.
프로세스 A 실행 → (타이머 인터럽트) → 컨텍스트 스위칭 → 프로세스 B 실행
2. 시스템 콜 및 I/O 대기
프로세스가 파일 읽기, 네트워크 요청 같은 I/O 작업을 요청하면 CPU를 낭비 없이 다른 프로세스에 넘긴다. I/O는 CPU보다 수십~수백 배 느리기 때문에 기다리는 동안 다른 작업을 처리하는 것이 효율적이다.
3. 우선순위 선점 (Preemption)
높은 우선순위의 프로세스가 준비 상태가 되면 현재 실행 중인 낮은 우선순위 프로세스를 밀어내고 CPU를 차지한다.
컨텍스트의 구성 — PCB (Process Control Block)
컨텍스트를 저장하는 자료구조가 PCB다. PCB에는 다음 정보가 담긴다.
| 항목 | 설명 |
|---|---|
| 프로세스 ID (PID) | 프로세스 식별자 |
| 프로세스 상태 | Running / Ready / Waiting / Terminated |
| 프로그램 카운터 (PC) | 다음에 실행할 명령어의 주소 |
| CPU 레지스터 | 범용 레지스터, 스택 포인터, 플래그 레지스터 등 |
| 메모리 관리 정보 | 페이지 테이블 포인터, 세그먼트 테이블 등 |
| I/O 상태 정보 | 열려 있는 파일 목록, 소켓 등 |
| 스케줄링 정보 | 우선순위, CPU 사용 시간 등 |
스레드도 비슷한 구조인 TCB(Thread Control Block)를 갖는다. 다만 스레드는 같은 프로세스 내에서 메모리(코드, 데이터, 힙)를 공유하므로 저장해야 할 컨텍스트 크기가 훨씬 작다.
컨텍스트 스위칭 과정
1. 인터럽트 또는 시스템 콜 발생
2. 현재 프로세스(A)의 컨텍스트를 PCB_A에 저장
- 프로그램 카운터, 레지스터 값 등
3. 스케줄러가 다음 실행할 프로세스(B) 선택
4. PCB_B에서 프로세스 B의 컨텍스트를 CPU에 복원
5. 프로세스 B 실행 재개
이 과정 동안 CPU는 유용한 작업을 아무것도 하지 못한다. 이 시간을 컨텍스트 스위칭 오버헤드라 한다.
프로세스 vs 스레드 컨텍스트 스위칭
| 항목 | 프로세스 전환 | 스레드 전환 |
|---|---|---|
| 저장 대상 | PCB (레지스터 + 메모리 맵 전체) | TCB (레지스터 + 스택) |
| 메모리 공유 | X (독립적인 주소 공간) | O (힙, 코드, 데이터 공유) |
| 비용 | 높음 (TLB 플러시 포함) | 낮음 |
| 안전성 | 격리되어 안전 | 공유 자원 동기화 필요 |
스레드 전환이 더 가볍다. 같은 프로세스 내의 스레드는 주소 공간을 공유하기 때문에 TLB(Translation Lookaside Buffer) 플러시 같은 비싼 작업이 필요 없다.
TLB 플러시란
TLB는 가상 주소 → 물리 주소 변환을 캐싱하는 하드웨어다. 프로세스가 전환되면 이전 프로세스의 주소 공간이 무효해지므로 TLB를 비워야 한다. 이 비용이 꽤 크다.
프로세스 전환 비용 ≈ 레지스터 저장/복원 + TLB 플러시 + 캐시 미스 증가
스레드 전환 비용 ≈ 레지스터 저장/복원 (TLB 플러시 없음)
컨텍스트 스위칭 오버헤드와 최적화
컨텍스트 스위칭 자체는 순수한 오버헤드다. 빈도가 높을수록 실제 작업에 쓰이는 CPU 시간이 줄어든다.
오버헤드를 줄이는 방법
1. 타임 퀀텀 조정
타임 퀀텀이 너무 짧으면 전환이 자주 일어나 오버헤드가 커진다. 너무 길면 응답성이 떨어진다. Linux는 기본 약 4~15ms 사이로 동적 조정한다.
2. 스레드 대신 코루틴(Coroutine) / 그린 스레드(Green Thread)
OS 레벨이 아닌 언어 런타임 레벨에서 협력적으로 전환한다. 전환 시 OS 개입이 없으므로 오버헤드가 매우 작다.
# Python async/await — 코루틴으로 I/O 대기 중 다른 작업 수행
import asyncio
async def fetch(name, delay):
print(f"{name} 시작")
await asyncio.sleep(delay) # OS 스케줄러가 아닌 이벤트 루프가 전환
print(f"{name} 완료")
async def main():
await asyncio.gather(fetch("A", 2), fetch("B", 1))
asyncio.run(main())
# 출력:
# A 시작
# B 시작
# B 완료
# A 완료
3. CPU 어피니티(CPU Affinity)
특정 프로세스나 스레드를 특정 CPU 코어에 고정하면 캐시 재사용률이 높아져 컨텍스트 스위칭 후 캐시 미스가 줄어든다.
# 프로세스를 CPU 0, 1번에 고정
taskset -c 0,1 ./my_program
컨텍스트 스위칭 횟수 확인 (Linux)
# vmstat으로 초당 컨텍스트 스위칭 횟수 확인
vmstat 1
# cs 컬럼이 초당 컨텍스트 스위칭 횟수
# procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
# r b swpd free buff cache si so bi bo in cs us sy id wa st
# 2 0 0 1024MB ... 0 0 0 0 500 1200 5 2 93 0 0
# 특정 프로세스의 컨텍스트 스위칭 통계
cat /proc/<PID>/status | grep ctxt
# voluntary_ctxt_switches: 1234 (자발적: 스스로 CPU 양보)
# nonvoluntary_ctxt_switches: 56 (비자발적: 타이머 인터럽트 등으로 강제 전환)
자발적 전환(voluntary)은 I/O 대기나 sleep처럼 프로세스가 스스로 CPU를 양보하는 경우다. 비자발적 전환(nonvoluntary)은 타임 퀀텀 만료처럼 강제로 빼앗기는 경우다. 비자발적 전환이 많다면 CPU 경쟁이 심하다는 신호다.
정리
| 핵심 개념 | 요약 |
|---|---|
| 컨텍스트 스위칭 | CPU가 다른 프로세스/스레드로 전환할 때 상태를 저장하고 불러오는 과정 |
| PCB / TCB | 프로세스/스레드의 컨텍스트를 저장하는 자료구조 |
| 발생 원인 | 타이머 인터럽트, I/O 대기, 우선순위 선점 |
| 비용 | 프로세스 전환 > 스레드 전환 (TLB 플러시 차이) |
| 최적화 방법 | 타임 퀀텀 조정, 코루틴/그린 스레드, CPU 어피니티 |
컨텍스트 스위칭은 멀티태스킹의 핵심 메커니즘이지만 그 자체가 순수 오버헤드다. 고성능 시스템을 설계할 때는 불필요한 컨텍스트 스위칭을 줄이는 것이 중요한 최적화 포인트가 된다.