[운영체제] CPU 가상화 | Logical Control Flow에서 Context Switch까지

 

지난 시간에는 디스크에 잠자던 프로그램이 어떻게 '프로세스(Process)'가 되는지 알아보았다. OS는 각 프로세스에게 마치 혼자 CPU와 메모리를 다 쓰는 듯한 환상, 즉 가상화(Virtualization)를 제공한다고 했다. 오늘은 그중에서도 CPU 가상화의 핵심 원리인 Limited Direct Execution에 대해 깊이 파고들어 보려 한다.


Logical Control Flow: 나만의 CPU를 가진다는 착각

현대의 컴퓨터는 수많은 프로세스를 동시에 실행한다. 웹 브라우저, 음악 플레이어, 메신저 등 수많은 프로그램이 동시에 돌아가는 것처럼 '보인다'. 각 프로세스는 자신만의 논리적 제어 흐름(Logical Control Flow)을 가지고 순차적으로 명령어를 실행해 나간다. 이는 마치 각 프로세스가 자신만의 전용 CPU를 가지고 있는 것처럼 행동한다는 의미다.

Logical Control Flow

하지만 물리적인 CPU는 한두 개에 불과하다. 어떻게 이런 착각이 가능할까? 그 비밀은 바로 OS가 CPU 시간을 매우 잘게 쪼개 여러 프로세스에게 나누어 주는 시분할(Time Sharing) 기법에 있다.

Concurrent Processes with Time Sharing

이 시분할 기법을 구현하기 위해 OS는 두 가지 중요한 요소를 분리하여 접근한다.

  • Mechanism (메커니즘): "어떻게" 할 것인가? 즉, 실제로 한 프로세스에서 다른 프로세스로 CPU의 제어권을 넘기는 저수준(low-level)의 동작 방식이다.

  • Policy (정책): "무엇을" 할 것인가? 즉, 수많은 '준비(Ready)' 상태의 프로세스 중 다음에 어떤 프로세스를 실행할지 결정하는 고수준(high-level)의 알고리즘이다. (예: 우선순위가 높은 프로세스 먼저 실행)

이처럼 메커니즘과 정책을 분리하는 것은 시스템 설계의 매우 중요한 원칙이다. 정책은 상황에 따라 얼마든지 바뀔 수 있지만(예: 노트북의 고성능 모드 vs 절전 모드), 그 기반이 되는 메커니즘은 안정적으로 유지되어야 하기 때문이다.



Direct Execution: 빠르지만 통제 불가능한 방법

그렇다면 CPU 가상화를 구현하는 가장 효율적인 메커니즘은 무엇일까? 가장 먼저 떠오르는 단순한 방법은 직접 실행(Direct Execution)이다. 말 그대로, 프로세스의 코드를 그냥 CPU 위에서 직접 실행시키는 것이다.

Direct Execution

[Direct Execution 과정]

  1. OS가 프로세스 리스트에 항목을 만들고 메모리를 할당한다.

  2. 프로그램을 메모리에 로드한다.

  3. 스택을 설정하고 main() 함수로 점프한다.

  4. 프로세스가 CPU에서 직접 실행된다.

  5. 프로세스가 끝나면 OS에게 제어권을 돌려준다.

 

이 방식은 OS의 개입을 최소화하므로 속도 면에서는 가장 이상적이다. 하지만 여기에는 두 가지 치명적인 문제가 존재한다.

  1. 제한된 연산 수행 불가: 만약 프로세스가 디스크에 파일을 쓰는 등 특별한 권한이 필요한 작업을 하려면 어떻게 해야 할까? 직접 실행 환경에서는 OS의 보호를 받을 방법이 없다.

  2. CPU 제어권 회수 불가: 만약 프로세스가 무한 루프에 빠지거나, 악의적으로 CPU를 독점하고 OS에게 제어권을 돌려주지 않으면 어떻게 될까? OS는 그저 속수무책으로 기다릴 수밖에 없다.

이런 문제들 때문에, 순수한 직접 실행 방식은 OS가 시스템을 통제할 수 없게 만들어 사실상 '운영체제'가 아닌 '라이브러리'로 전락하게 만든다.



Limited Direct Execution: 통제권을 더한 실행

이 문제를 해결하기 위해 제한된 직접 실행(Limited Direct Execution) 이라는 개념이 등장했다. 기본적으로는 프로세스를 CPU에서 직접 실행시켜 성능을 보장하되, OS가 시스템에 대한 통제권을 잃지 않도록 최소한의 안전장치를 두는 방식이다.


안전장치 1: 시스템 콜 (System Call)과 두 개의 모드

'제한된 연산' 문제를 해결하기 위해 현대 CPU는 사용자 모드(User Mode)커널 모드(Kernel Mode)라는 두 가지 실행 모드를 제공한다.

  • 사용자 모드: 일반적인 프로세스가 실행되는 모드. 하드웨어 자원에 직접 접근할 수 없다.

  • 커널 모드: OS 커널이 실행되는 모드. 시스템의 모든 자원에 접근할 수 있다.

프로세스는 평소에 사용자 모드에서 실행되다가, 파일 I/O나 메모리 추가 할당처럼 특별한 권한이 필요한 작업을 해야 할 때 시스템 콜(System Call)을 호출한다. 

System Call

이 시스템 콜은 CPU에게 트랩(Trap) 이라는 특별한 명령을 내리는, 일종의 의도된 예외(Synchronous Exception)이다. 트랩이 발생하면 다음과 같은 일이 벌어진다.

Synchronous Exception: Caused by events that occur as a result of executing an instruction

  1. CPU는 현재 실행하던 사용자 프로세스의 레지스터 상태(PC, 스택 포인터 등)를 해당 프로세스에 할당된 커널 스택(Kernel Stack)에 저장한다.

  2. 실행 모드를 커널 모드로 전환한다.

  3. OS는 미리 정의된 트랩 테이블(Trap Table)을 참조하여, 해당 시스템 콜 번호에 맞는 핸들러(Handler) 코드를 실행한다.

  4. OS는 요청받은 작업을 안전하게 처리해준 뒤, 리턴-프롬-트랩(Return-from-trap) 명령을 실행한다.

  5. 커널 스택에 저장해두었던 사용자 프로세스의 레지스터 상태를 복원하고, 다시 사용자 모드로 돌아가 중단되었던 지점부터 실행을 재개한다.

이 과정을 통해 OS는 시스템의 안정성을 해치지 않는 선에서만 프로세스의 요청을 허락할 수 있다.



안전장치 2: 타이머 인터럽트 (Timer Interrupt)

'CPU 제어권' 문제를 해결하기 위해 OS는 타이머 인터럽트라는 하드웨어의 도움을 받는다. 이는 외부 장치에 의해 발생하는 비동기적 예외(Asynchronous Exception)이다. OS는 부팅 과정에서 타이머 하드웨어를 설정하여, 일정한 시간(예: 수 밀리초)마다 CPU에게 인터럽트 신호를 보내도록 한다.

Asynchronous Exeption Examples: Caused by events external to the processor

프로세스가 CPU를 점유하고 실행되던 중 이 타이머 인터럽트가 발생하면, CPU는 현재 하던 일을 즉시 멈추고 시스템 콜과 마찬가지로 커널 모드로 전환하여 OS의 인터럽트 핸들러를 실행시킨다. 이 순간, CPU의 제어권은 강제로 OS에게 넘어오게 된다. OS는 이 타이밍을 이용해 현재 프로세스를 계속 실행할지, 아니면 다른 프로세스로 전환할지 결정할 수 있는 기회를 얻는다. 악의적인 프로그램이 무한 루프에 빠지더라도, 타이머 인터럽트 덕분에 OS는 어김없이 제어권을 되찾아와 시스템을 정상 상태로 유지할 수 있다.



Context Switch: 프로세스 전환의 핵심 메커니즘

이제 OS는 시스템 콜과 타이머 인터럽트라는 두 가지 강력한 무기를 통해 CPU 제어권을 확실히 쥘 수 있게 되었다. 타이머 인터럽트가 발생하여 OS가 제어권을 얻었을 때, "이제 프로세스 A를 멈추고 프로세스 B를 실행해야겠다"라는 정책적 결정을 내렸다고 가정해보자.

이때 바로 CPU 가상화의 핵심 메커니즘인 문맥 교환(Context Switch)이 일어난다.

문맥 교환이란, 현재 실행 중인 프로세스(A)의 상태(문맥)를 어딘가에 저장하고, 새로 실행할 프로세스(B)의 저장된 상태를 복원하는 일련의 과정을 의미한다. 여기서 '문맥(Context)'이란 해당 프로세스를 다시 실행시키기 위해 필요한 모든 정보, 즉 프로그램 카운터(PC), 각종 레지스터 값, 스택 포인터 등을 포함한다.

Context Switch

[Context Switch 상세 과정]

  1. 인터럽트 발생: 프로세스 A 실행 중 타이머 인터럽트가 발생한다. 하드웨어는 즉시 A의 사용자 레지스터(PC, 스택 포인터 등)를 A의 커널 스택에 저장하고, 커널 모드로 전환하여 OS의 인터럽트 핸들러로 점프한다.

  2. 문맥 저장 (Save): OS는 스케줄링 정책에 따라 프로세스 B로 전환하기로 결정한다. OS는 프로세스 A의 커널 레지스터(베이스 포인터 등)를 A의 프로세스 구조체(PCB)에 마저 저장한다. 이로써 프로세스 A의 모든 문맥이 저장된다.

  3. 문맥 복원 (Restore): OS는 프로세스 B의 프로세스 구조체에서 B의 커널 레지스터들을 복원한다.

  4. 전환 (Switch): OS는 return-from-trap 명령을 실행한다. 이 명령은 B의 커널 스택에 저장되어 있던 B의 사용자 레지스터들을 복원하고, 실행 모드를 다시 사용자 모드로 전환한 뒤, B가 멈췄던 바로 그 지점으로 PC를 점프시킨다. 이제 CPU는 프로세스 B의 코드를 실행하기 시작한다.

이 과정은 매우 낮은 수준의 어셈블리 코드로 구현되며, OS 내부에서 빈번하게 발생한다. 비록 약간의 오버헤드가 발생하지만, 이 문맥 교환 덕분에 우리는 수많은 프로세스가 동시에 실행되는 듯한 CPU 가상화의 편리함을 누릴 수 있는 것이다.



결론

결국 CPU 가상화는 '제한된 직접 실행'이라는 큰 틀 안에서 시스템 콜과 타이머 인터럽트라는 안전장치를 통해 OS가 제어권을 확보하고, 이를 바탕으로 '문맥 교환'이라는 메커니즘을 수행하여 여러 프로세스를 번갈아 실행시키는 정교한 과정의 산물이다. 오늘은 그 메커니즘에 집중했지만, 다음 시간에는 "어떤 프로세스를 다음에 실행할 것인가?"라는 '스케줄링 정책'에 대해 더 깊이 알아볼 예정이다.

추천글:

[시스템프로그래밍] Process & Thread

[운영체제] 운영체제란? | Virtualization, Concurrency, Persistence 핵심 개념 이해

[운영체제] 프로세스 | 프로그램이 실행되기까지의 여정


hyeon_B

안녕하세요! AI 기술을 이용해 더 나은 세상을 만들어 나가고 싶은 과기원생 Hyeon이라고 합니다. 저는 앞으로 인공지능 시대에는 지식을 '활용'하는 능력이 중요해질 것이라고 생각합니다. 대부분의 일들은 인공지능이 뛰어난 모습을 보이지만, 인공지능은 데이터로 부터 연관관계를 학습하기 때문에 지식들을 새로 통합해서 활용하는 능력이 부족합니다. 인공지능이 뉴턴 전에 만들어졌다면 사과가 떨어지는 이유에 대답하지 못했을 것이고, 아인슈타인 전에 만들어졌다면 중력이 어떻게 생기는지 설명하지 못했을 것입니다. 따라서 앞으로 우리는 '본질'을 탐구하고 그 본질로부터 다른 곳에 적용하며 인공지능을 현명하게 활용해야 할 것입니다. 함께 인공지능 시대를 준비합시다!

댓글 쓰기

다음 이전

POST ADS1

POST ADS 2