2025-10-06 21:51

CPU의 마술 지휘봉, 문맥 교환 완벽 핸드북

  • 문맥 교환은 단일 CPU가 수많은 작업을 동시에 처리하는 것처럼 보이게 하는 운영체제의 핵심 기술입니다.

  • 운영체제는 프로세스의 모든 상태 정보(문맥)를 PCB라는 데이터 구조에 저장했다가 복원하는 방식으로 작업을 전환합니다.

  • 문맥 교환은 멀티태스킹을 가능하게 하지만, 그 자체로 비용(오버헤드)이 발생하므로 시스템 성능에 지대한 영향을 미칩니다.

컴퓨터 전원을 켜고 웹 브라우저, 음악 플레이어, 워드 프로세서를 동시에 실행하는 순간을 상상해 보자. 우리는 마치 여러 개의 뇌가 각 프로그램을 전담하여 처리하는 것처럼 느끼지만, 대부분의 컴퓨터에는 작업을 처리하는 핵심 두뇌, 즉 CPU 코어가 소수만 존재한다. 어떻게 단 몇 개의 CPU 코어가 수십 개의 작업을 동시에 매끄럽게 처리할 수 있을까? 그 비밀의 중심에는 운영체제의 정교한 마술, ‘문맥 교환(Context Switching)‘이 있다.

문맥 교환은 한 편의 연극에서 무대 감독이 여러 배우를 차례로 무대에 올리는 것과 같다. 각 배우는 자신만의 대사, 위치, 감정선(문맥)을 가지고 있다. 배우 A가 연기를 잠시 멈추면, 무대 감독은 배우 A의 모든 상태를 기록해 둔다. 그리고 배우 B에게 필요한 조명과 소품을 준비하고 무대에 올린다. 잠시 후 배우 B가 퇴장하면, 기록해 둔 배우 A의 상태를 그대로 복원하여 연기를 재개한다. 관객 입장에서는 두 배우의 이야기가 끊김 없이 자연스럽게 흘러가는 것처럼 보인다.

이 핸드북에서는 컴퓨터의 무대 감독, 문맥 교환의 모든 것을 탐험한다. 이것이 왜 탄생했는지, 어떤 구조로 작동하는지, 그리고 어떻게 우리의 컴퓨팅 경험을 가능하게 하는지에 대한 깊이 있는 이해를 제공할 것이다.


1. 문맥 교환의 탄생 배경: 하나의 CPU로 세상을 움직이는 법

초창기 컴퓨터는 한 번에 하나의 작업만 처리할 수 있었다. 하나의 프로그램이 실행을 시작하면, 그 작업이 완전히 끝날 때까지 CPU는 오롯이 그 작업에만 매달려야 했다. 이는 CPU 자원의 심각한 낭비를 초래했다. 예를 들어, 프로그램이 사용자 입력을 기다리거나 디스크에서 데이터를 읽어오는 동안 CPU는 아무 일도 하지 않고 빈둥거려야만 했다. CPU의 연산 속도는 매우 빠른데, 느린 입출력(I/O) 장치를 기다리며 시간을 허비하는 것은 엄청난 비효율이었다.

이 문제를 해결하기 위해 시분할 시스템(Time-sharing System)멀티태스킹(Multitasking) 이라는 개념이 등장했다. 여러 개의 프로그램을 메모리에 올려두고, CPU의 시간을 아주 짧은 단위(Time Slice 또는 Quantum)로 쪼개어 여러 프로그램에 번갈아 할당하자는 아이디어였다. A 프로그램에 0.01초, B 프로그램에 0.01초, C 프로그램에 0.01초를 할당하고 이를 매우 빠르게 반복하면, 사용자 입장에서는 모든 프로그램이 동시에 실행되는 것처럼 느끼게 된다.

이러한 멀티태스킹을 현실로 만들기 위한 핵심 기술이 바로 문맥 교환이다. A 프로그램에서 B 프로그램으로 CPU의 사용 권한을 넘길 때, 그냥 넘겨주기만 해서는 안 된다. A 프로그램이 어디까지 실행했는지, 레지스터에는 어떤 값들이 들어 있었는지 등 모든 상태를 어딘가에 안전하게 보관해야만 나중에 다시 A의 차례가 왔을 때 중단된 지점부터 정확히 실행을 재개할 수 있다. 이 ‘어디까지 실행했는지에 대한 총체적인 상태 정보’를 문맥(Context) 이라 부르며, 이 문맥을 저장하고 새로운 문맥을 불러오는 과정을 문맥 교환이라고 한다.

결론적으로 문맥 교환은 CPU 활용률을 극대화하고 멀티태스킹 환경을 구현하기 위해 탄생했다. CPU가 놀지 않게 계속해서 일감을 던져주고, 여러 작업이 동시에 처리되는 듯한 사용자 경험을 제공하는 현대 운영체제의 근간이 되는 기술인 것이다.


2. 문맥의 구조: 무엇을 저장하고 무엇을 불러오는가?

문맥 교환은 ‘프로세스의 상태를 저장하고 복원하는 것’이라고 했다. 그렇다면 이 ‘상태’, 즉 문맥은 구체적으로 어떤 정보들을 포함할까? 운영체제는 프로세스와 관련된 모든 핵심 정보를 프로세스 제어 블록(Process Control Block, PCB) 이라는 특별한 데이터 구조에 저장한다. PCB는 말 그대로 프로세스의 신분증이자 모든 상태를 기록한 종합 기록부와 같다.

문맥 교환이 일어날 때 PCB에 저장되는 주요 정보는 다음과 같다.

정보 분류세부 항목설명
프로세스 식별 정보프로세스 ID (PID), 부모 프로세스 ID (PPID)시스템 내에서 각 프로세스를 고유하게 식별하기 위한 번호.
프로세서 상태 정보프로그램 카운터 (Program Counter, PC)다음에 실행될 명령어의 메모리 주소를 가리킴. 문맥 교환 후 정확히 중단된 지점부터 재시작하기 위한 핵심 정보.
CPU 레지스터 (CPU Registers)현재 연산에 사용되던 데이터들이 저장된 CPU 내의 작은 고속 메모리. (e.g., 누산기, 범용 레지스터, 스택 포인터)
프로세스 상태 (Process State)프로세스가 현재 어떤 상태인지 나타냄 (e.g., 실행(Running), 준비(Ready), 대기(Waiting)).
메모리 관리 정보페이지 테이블 (Page Table) 포인터각 프로세스에 할당된 가상 메모리와 실제 물리 메모리 간의 매핑 정보를 담고 있는 테이블의 위치.
세그먼트 테이블 (Segment Table) 포인터세그먼테이션 메모리 관리 기법을 사용할 경우의 메모리 매핑 정보.
파일 및 입출력 정보열린 파일 목록 (List of Open Files)프로세스가 현재 사용 중인 파일들의 목록.
입출력 장치 정보프로세스에 할당된 키보드, 디스크 등의 장치 정보.
계정 및 스케줄링 정보CPU 사용 시간, 우선순위 (Priority)스케줄링 알고리즘이 다음 실행할 프로세스를 결정하는 데 사용하는 정보.

이 중에서도 가장 핵심적인 정보는 프로그램 카운터CPU 레지스터다. 프로그램 카운터는 “어디까지 읽었는지”를 표시하는 책갈피와 같고, CPU 레지스터는 “무슨 생각을 하고 있었는지”를 담아두는 단기 기억과 같다. 문맥 교환은 이 책갈피와 단기 기억을 PCB에 완벽하게 백업해두는 과정이라 할 수 있다.


3. 문맥 교환의 작동 원리: 정교한 하드웨어와 소프트웨어의 협업

문맥 교환은 운영체제 커널이라는 소프트웨어에 의해 주도되지만, 하드웨어의 긴밀한 도움이 없이는 불가능하다. 그 과정은 다음과 같은 단계로 이루어진다.

문맥 교환이 발생하는 경우 (Triggers):

문맥 교환은 아무 때나 일어나지 않는다. 특정 ‘사건’이 발생했을 때 시작된다.

  1. 시간 만료 (Time Slice Expired): 멀티태스킹 시스템에서 한 프로세스에 할당된 CPU 사용 시간(Time Slice)을 모두 소진하면, 타이머 인터럽트(Timer Interrupt) 가 발생한다. 인터럽트는 CPU에게 하던 일을 멈추고 운영체제 커널을 실행하라고 알리는 하드웨어 신호다.

  2. 입출력 요청 (I/O Request): 프로세스가 디스크 읽기나 네트워크 통신과 같은 시간이 오래 걸리는 I/O 작업을 시스템 콜(System Call)을 통해 요청하면, 해당 작업이 완료될 때까지 기다려야 한다. 이때 CPU를 낭비하지 않고 다른 프로세스에게 사용권을 넘기기 위해 문맥 교환이 일어난다.

  3. 인터럽트 발생 (Interrupt Handling): 키보드 입력이나 마우스 클릭 같은 예기치 않은 하드웨어 신호(인터럽트)가 발생하면, CPU는 현재 작업을 중단하고 인터럽트 서비스 루틴(ISR)이라는 커널 코드를 실행해야 한다. 이 과정에서 필요에 따라 문맥 교환이 발생할 수 있다.

문맥 교환의 단계별 과정:

프로세스 P1이 실행되다가 타이머 인터럽트가 발생하여 프로세스 P2로 전환되는 상황을 가정해 보자.

  1. 트랩(Trap) 및 커널 모드 전환: 타이머 인터럽트가 발생하면 CPU는 현재 실행 중인 P1의 작업을 중단한다. 하드웨어는 즉시 현재의 프로그램 카운터(PC)와 일부 핵심 레지스터를 스택에 잠시 저장하고, 운영체제 커널 코드가 있는 특정 주소로 점프한다. 이와 동시에 CPU의 실행 모드가 사용자 모드(User Mode) 에서 커널 모드(Kernel Mode) 로 전환된다. 커널 모드는 시스템의 모든 자원에 접근할 수 있는 특권 모드다.

  2. 현재 문맥 저장 (Save Context): 커널은 P1의 나머지 문맥(범용 레지스터, 메모리 정보 등)을 P1의 PCB에 차곡차곡 저장한다. 하드웨어가 저장한 정보와 소프트웨어(커널)가 저장한 정보가 합쳐져 P1의 완전한 상태가 PCB에 백업된다.

  3. 스케줄러 호출 및 다음 프로세스 선택: 문맥 저장이 완료되면, 커널의 스케줄러(Scheduler) 가 호출된다. 스케줄러는 준비 큐(Ready Queue)에 있는 여러 프로세스 중, 우선순위나 스케줄링 알고리즘(예: Round Robin, Priority-based)에 따라 다음에 실행할 프로세스 P2를 선택한다.

  4. 새로운 문맥 복원 (Restore Context): 스케줄러가 P2를 선택하면, 디스패처(Dispatcher) 라는 커널 코드가 P2의 PCB에 저장되어 있던 문맥을 CPU 레지스터와 프로그램 카운터 등으로 불러온다. P2의 메모리 매핑 정보(페이지 테이블)도 활성화된다.

  5. 커널 모드 해제 및 실행 재개: P2의 문맥 복원이 완료되면, CPU는 다시 사용자 모드로 전환되고, 복원된 프로그램 카운터가 가리키는 지점부터 P2의 실행을 시작한다. 이로써 CPU의 제어권이 P1에서 P2로 완벽하게 이전된다.

이 모든 과정은 수 마이크로초(μs)라는 극히 짧은 시간 안에 일어나기 때문에 사용자는 인지하지 못한다.


4. 문맥 교환의 비용: 공짜 점심은 없다

문맥 교환은 멀티태스킹을 가능하게 하는 필수적인 기능이지만, 결코 공짜가 아니다. 문맥을 저장하고 복원하는 과정 자체도 CPU 시간을 소모하는 하나의 작업이기 때문이다. 이 비용을 문맥 교환 오버헤드(Context Switching Overhead) 라고 부른다.

오버헤드는 두 가지 종류로 나뉜다.

  1. 직접 비용 (Direct Costs):

    • 커널이 PCB에 레지스터 상태를 저장하고 새로운 상태를 불러오는 데 걸리는 순수한 시간.

    • 스케줄러와 디스패처 코드를 실행하는 데 필요한 CPU 시간.

  2. 간접 비용 (Indirect Costs):

    • 캐시 미스 (Cache Miss): 프로세스 P1이 실행될 때, CPU 캐시에는 P1의 코드와 데이터가 채워져 있다. 문맥 교환으로 P2가 실행되면 이 캐시 데이터는 대부분 쓸모없어진다. P2는 필요한 데이터를 다시 메모리에서 캐시로 가져와야 하는데, 이 과정에서 성능 저하가 발생한다. 이를 ‘캐시가 오염되었다(Cache Pollution)‘고 표현하기도 한다.

    • TLB 플러시 (TLB Flush): TLB(Translation Lookaside Buffer)는 가상 주소를 물리 주소로 변환하는 정보를 담고 있는 고속 캐시다. 프로세스가 바뀌면 이전 프로세스의 주소 변환 정보는 무효화되므로 TLB를 비워야 한다(Flush). 새로운 프로세스는 TLB 미스를 겪으며 주소 변환을 다시 수행해야 하므로 속도가 느려진다.

문맥 교환이 너무 빈번하게 일어나면, CPU는 실제 사용자 작업을 처리하는 시간보다 문맥 교환 오버헤드에 더 많은 시간을 쓰게 되어 시스템 전체 성능이 급격히 저하될 수 있다.


5. 심화 내용: 스레드, 스래싱, 그리고 최적화

프로세스 문맥 교환 vs. 스레드 문맥 교환

현대 운영체제에서는 프로세스보다 더 작은 실행 단위인 스레드(Thread) 를 사용한다. 하나의 프로세스는 여러 개의 스레드를 가질 수 있다. 중요한 점은, 같은 프로세스에 속한 스레드들은 메모리 공간(코드, 데이터, 힙 영역)과 파일 같은 자원을 공유한다는 것이다.

이 때문에 스레드 간의 문맥 교환프로세스 간의 문맥 교환보다 훨씬 가볍고 빠르다. 스레드 문맥 교환 시에는 공유하는 메모리 정보(페이지 테이블 등)를 바꿀 필요가 없고, 스레드 고유의 정보(프로그램 카운터, 레지스터, 스택 포인터)만 저장하고 복원하면 되기 때문이다. 이는 TLB 플러시 같은 큰 비용을 유발하지 않는다. 이러한 이유로 현대 애플리케이션들은 무거운 프로세스를 여러 개 만드는 대신, 가벼운 스레드를 여러 개 만들어 병렬성을 높이는 방식을 선호한다.

최악의 시나리오: 스래싱 (Thrashing)

문맥 교환 오버헤드가 극단적으로 치달아 시스템이 거의 마비되는 현상을 스래싱(Thrashing) 이라고 한다. 이는 주로 물리적 메모리(RAM)가 부족할 때 발생한다.

  1. 시스템에 실행 중인 프로세스들이 요구하는 메모리 총량이 실제 물리 메모리보다 훨씬 많아진다.

  2. 운영체제는 메모리 공간을 확보하기 위해 당장 쓰이지 않는 메모리 페이지를 디스크(스왑 공간)로 내보낸다 (Page-out).

  3. 어떤 프로세스가 실행되려는데 필요한 데이터가 디스크에 내려가 있는 경우(페이지 폴트, Page Fault), 운영체제는 디스크에서 해당 페이지를 다시 메모리로 가져와야 한다 (Page-in).

  4. 이 I/O 작업 동안 CPU는 다른 프로세스를 실행하기 위해 문맥 교환을 한다.

  5. 하지만 새로 실행된 프로세스 역시 필요한 데이터가 디스크에 있을 확률이 높다. 또다시 페이지 폴트가 발생한다.

  6. 이 과정이 반복되면, 시스템은 실제 연산은 거의 하지 못하고 디스크와 메모리 간에 페이지를 교체하는 작업과 이로 인한 문맥 교환에 모든 시간을 소모하게 된다. CPU 이용률은 바닥을 치는데 시스템은 극심한 버벅임을 보이는, 재앙적인 상태에 빠진다.

문맥 교환 최소화 기법

운영체제와 개발자는 문맥 교환 비용을 줄이기 위해 다양한 노력을 기울인다.

  • 적절한 시간 할당량(Time Slice) 설정: 시간 할당량이 너무 짧으면 문맥 교환이 빈번해져 오버헤드가 커지고, 너무 길면 반응성이 떨어져 사용자가 시스템이 멈춘 것처럼 느낄 수 있다. 시스템의 특성에 맞는 최적의 값을 찾는 것이 중요하다.

  • 사용자 수준 스레드 (User-level Threads): 커널의 개입 없이 사용자 라이브러리 수준에서 스레드를 관리한다. 문맥 교환이 커널 모드 전환 없이 사용자 공간에서 일어나므로 매우 빠르다. 다만, 하나의 스레드가 블록킹되면 프로세스 전체가 멈추고, 멀티코어를 활용할 수 없는 단점이 있다.

  • 커널 수준 스레드 (Kernel-level Threads): 커널이 직접 스레드를 관리한다. 문맥 교환 비용은 더 비싸지만, 하나의 스레드가 블록킹되어도 다른 스레드는 계속 실행될 수 있고, 여러 코어에 스레드를 분배하여 진정한 병렬 처리가 가능하다. (대부분의 현대 OS가 이 방식을 사용)

  • 게으른 FPU 상태 저장 (Lazy FPU State Saving): 부동소수점 연산(FPU)이나 멀티미디어(SIMD) 레지스터는 크기가 매우 크다. 문맥 교환 시 일단 이 레지스터들은 저장하지 않고, 새로 실행되는 프로세스가 실제로 FPU/SIMD 명령을 사용하려는 순간에 이전 프로세스의 FPU 상태를 저장하는 기법이다. 불필요한 저장/복원 비용을 절약할 수 있다.


결론: 보이지 않는 질서의 핵심

문맥 교환은 겉으로 드러나지 않지만, 우리가 당연하게 여기는 현대 컴퓨팅의 멀티태스킹 환경을 지탱하는 보이지 않는 기둥이다. 단일 CPU가 마치 수십 개의 손을 가진 것처럼 여러 작업을 능수능란하게 처리할 수 있는 것은, 운영체제가 지휘하는 이 정교하고 빠른 문맥 교환 덕분이다.

물론 이 과정에는 명백한 비용이 따르며, 때로는 시스템 성능의 발목을 잡는 주범이 되기도 한다. 하지만 이러한 오버헤드를 감수하고서라도 문맥 교환이 주는 ‘자원의 효율적 활용’과 ‘사용자 경험의 향상’이라는 이점은 비교할 수 없이 크다. 개발자로서, 혹은 깊이 있는 사용자로서 문맥 교환의 원리를 이해하는 것은 시스템의 성능을 이해하고 최적화하는 데 있어 강력한 통찰력을 제공할 것이다. 그것은 단순한 기술적 지식을 넘어, 우리와 컴퓨터 사이의 매끄러운 상호작용을 가능하게 하는 근본적인 질서에 대한 이해이기 때문이다.