-
[OS] Direct Execution과 Limited Direct Execution: CPU 가상화의 핵심 원리CS/OS 2025. 9. 15. 00:59
CPU 가상화는 여러 작업이 마치 각자 전용 CPU를 가지고 있는 것처럼 보이게 하는 기술입니다. 실제로는 단 하나의 물리적 CPU만 존재하지만, 운영체제(OS)가 이 CPU를 아주 짧은 시간 단위로 쪼개어 각 작업에 할당함으로써 이러한 착시 효과를 만들어냅니다.
이러한 것의 기본 원리에는 time sharing이 존재합니다.
Time-sharing 원리
타임 쉐어링은 CPU를 여러 작업이 번갈아 가며 사용하도록 하는 방식입니다.
- 시간 할당: 운영체제는 각 작업에 아주 짧은 시간을 할당합니다.
- Context Switch: 한 작업의 할당된 시간이 끝나면, 운영체제는 현재 작업의 상태(레지스터 값, 프로그램 카운터 등)를 저장하고, 다음에 실행할 다른 작업의 상태를 불러옵니다.
- 반복: 이 과정이 매우 빠르게 반복됩니다. 한 작업이 실행되다가 다음 작업으로 넘어가고, 그 다음 작업으로 넘어가는 식으로 CPU를 끊임없이 공유합니다.
이렇게 빠르게 전환되기 때문에, 사용자 입장에서는 여러 프로그램이 동시에 실행되는 것처럼 느끼게 됩니다.
CPU 가상화의 두 가지 과제: Performance & Control
CPU를 가상화하기 위해 OS는 time-sharing을 사용해 물리적 CPU를 여러 프로세스가 공유하도록 합니다. 하지만 이 과정에서 두 가지 중요한 과제에 직면합니다.
- Performance: 가상화 과정이 시스템에 과도한 부하(overhead)를 주지 않으면서 효율적으로 구현되어야 합니다.
- Control: OS가 CPU에 대한 통제권을 유지하면서 프로세스를 효율적으로 실행해야 합니다.
이 두 가지 과제를 해결하기 위한 첫 번째 방법이 바로 Direct Execution입니다.
Direct Execution
Direct Execution은 말 그대로 사용자의 프로그램을 CPU 위에서 직접 실행하는 방식입니다. OS는 프로세스를 위한 메모리를 할당하고, 프로그램을 메모리에 올린 후, 단순히 main() 함수를 호출하여 프로그램이 스스로 실행되도록 합니다.
하지만 이 방식에는 두 가지 심각한 문제가 발생합니다.
- Direct Execution의 한계: 프로그램이 OS의 통제 밖에서 마음대로 행동할 수 있게 됩니다. 예를 들어, 민감한 시스템 자원(예: 디스크 I/O)에 직접 접근하여 중요한 데이터를 파괴할 수 있습니다.
- 프로세스 전환 문제: OS가 한 프로세스의 실행을 멈추고 다른 프로세스로 전환할 수 있는 방법이 없습니다.
문제 해결: Limited Direct Execution 프로토콜
문제 1: Restricted Operations
직접 실행 방식에서 프로세스가 디스크 I/O 요청이나 시스템 자원에 대한 접근과 같은 제한된 작업을 수행하려 할 때 문제가 발생합니다. 모든 것을 허용하면 프로세스가 시스템 전체를 망가뜨릴 위험이 있습니다.
이 문제에 대한 해결책은 protected control transfer입니다. 이는 CPU 모드를 도입하여 해결합니다.
- User Mode: 일반적인 사용자 프로그램이 실행되는 모드입니다. 이 모드에서는 I/O 요청과 같은 privileged 작업이 제한됩니다. 만약 사용자 모드에서 privileged 작업을 시도하면 CPU는 exception를 발생시키고, OS는 해당 프로세스를 종료시킵니다.
- Kernel Mode: OS가 실행되는 모드입니다. 이 모드에서는 I/O와 같은 모든 privileged 작업이 허용됩니다.
즉, 대부분의 일반적인 연산(산술, 반복 등)은 사용자 모드에서 직접 실행되지만, 제한된 작업은 OS에게 제어권을 넘겨 커널 모드에서만 실행되도록 함으로써, OS는 시스템에 대한 통제권을 유지하게 됩니다.
System Call & Trap
사용자 프로그램이 파일에 접근하거나 메모리를 할당하는 등 privileged 작업을 수행하려면, 하드웨어가 제공하는 system call 기능을 이용해야 합니다. system call은 커널(kernel)이 사용자 프로그램에게 특정 기능을 안전하게 제공하도록 허용하는 메커니즘입니다.
Trap의 동작 과정
system call은 trap이라는 특별한 명령어 실행을 통해 이루어집니다.
- 트랩 실행: 사용자 프로그램이 시스템 호출을 위해 트랩 명령어(e.g., x86의 INT)를 실행합니다.
- 권한 상승: 트랩 명령어는 자동으로 CPU의 privilege level을 커널 모드로 상승시키고, 제어권을 OS 커널로 넘깁니다.
- 특권 작업 수행: 이제 커널 모드에서 OS는 사용자 프로그램이 요청한 privileged 작업을 안전하게 수행합니다.
- 복귀: 작업이 완료되면, OS는 return-from-trap 명령어(e.g., x86의 IRET)를 호출하여 다시 사용자 프로그램으로 돌아가고, CPU의 privilege level을 user mode로 낮춥니다.

이 과정에서 하드웨어는 현재 실행 중이던 사용자 프로그램의 상태(PC, 플래그, 레지스터 등)를 프로세스별 커널 스택에 저장합니다. 이는 return-from-trap 명령어가 호출될 때 원래 상태로 정확하게 복귀하기 위함입니다.
Trap Handling
OS는 부팅 시점에 trap table을 설정하여, 트랩 발생 시 어떤 코드를 실행할지 하드웨어에 알려줍니다.
- 트랩 테이블: 특정 시스템 호출 번호에 해당하는 trap handler의 주소를 저장하는 테이블입니다.
- system call number: 각 system call에는 고유한 번호가 할당되어 있습니다. 트랩 핸들러는 이 번호를 확인하여 요청된 작업을 실행하고, 유효성을 검사하여 보안을 강화합니다.
- privilege 작업: 트랩 테이블 위치를 CPU에 알리는 작업(IDTR 레지스터 설정 등)은 반드시 커널 모드에서만 가능한 privilege 작업입니다.
트랩 핸들링은 사용자 프로그램이 system call을 통해 커널 기능을 안전하게 요청할 때 사용되는 핵심 메커니즘입니다.
1. 상태 저장 및 복원
트랩이 발생하면 하드웨어는 현재 실행 중인 프로그램의 상태를 프로세스별 커널 스택에 저장합니다. 이 상태에는 PC(프로그램 카운터), 플래그(flags), 레지스터(registers) 값이 포함됩니다. OS가 작업을 마친 후 return-from-trap 명령어를 실행하면, 하드웨어는 저장된 값들을 스택에서 다시 불러와 사용자 프로그램의 실행을 중단된 지점부터 재개합니다.
2. 트랩 테이블을 이용한 제어권 전달
트랩 발생 시 하드웨어가 실행해야 할 OS 코드를 찾기 위해 트랩 테이블을 사용합니다.
- OS의 역할: OS는 부팅 시점에 커널 모드에서 메모리에 트랩 테이블을 설정하고, IDTR이라는 특수 레지스터에 그 주소를 기록하여 CPU에게 위치를 알려줍니다. 이 과정은 privileged operation에 속합니다.
- 하드웨어의 역할: trap이 발생하면, 하드웨어는 IDTR이 가리키는 트랩 테이블에서 해당 트랩 유형에 맞는 trap handler의 주소를 찾아 그곳으로 제어권을 넘깁니다.
3. System-call Number
각 시스템 호출에는 고유한 system-call number가 할당됩니다.
- 역할: 트랩 핸들러에 진입한 OS는 이 번호를 확인하여 요청된 작업이 무엇인지 파악합니다.
- 보안: OS는 이 번호의 유효성을 검사하여 사용자 프로그램이 허용되지 않은 작업을 시도하는 것을 막습니다. 이는 시스템을 보호하는 중요한 수단입니다.
System call interface
시스템 호출 인터페이스는 system call number에 따라 인덱싱된 테이블을 관리합니다. 사용자 프로그램이 시스템 호출을 요청하면, 이 인터페이스가 OS 커널 내의 해당 함수를 호출하고, 작업의 성공 여부(상태)와 결과값을 사용자 프로그램으로 반환하는 역할을 합니다.

표준 라이브러리
표준 라이브러리(Standard Library)는 시스템 호출 인터페이스의 일부를 사용자에게 편리하게 제공합니다. 예를 들어, C 언어의 printf() 함수는 직접 시스템 호출을 하지 않습니다. 대신, 다음과 같은 과정을 거칩니다.
- 사용자 프로그램이 printf() 함수를 호출합니다.
- 표준 라이브러리는 이 호출을 가로채서(intercepting), 실제 파일 쓰기 작업을 수행하는 write() 시스템 호출을 대신 호출합니다.
- write() 시스템 호출이 실행된 후, 그 결과(성공 여부, 쓰여진 바이트 수)를 반환합니다.
- 표준 라이브러리는 이 결과값을 다시 사용자 프로그램에 전달하여, 프로그래머가 복잡한 시스템 호출의 세부사항을 몰라도 printf()를 쉽게 사용할 수 있도록 돕습니다.

요약하자면, 시스템 호출 인터페이스는 OS와의 직접적인 통로 역할을 하고, 표준 라이브러리는 사용자에게 이 통로를 더 쉽게 사용할 수 있는 추상화된 함수(API)를 제공하는 것입니다.
Limited Direct Execution 프로토콜
Limited Direct Execution은 Direct Execution의 성능상의 이점을 유지하면서 OS의 제어권을 확보하는 핵심 프로토콜입니다. 이 프로토콜은 시스템 부팅 시점부터 프로세스 종료 시점까지의 OS와 하드웨어, 프로그램 간의 상호작용을 단계별로 보여줍니다.
1. 부팅 시 OS의 역할 (OS @ boot)
- 커널 모드 진입: 시스템이 부팅되면 OS는 가장 높은 권한을 가진 커널 모드에서 시작합니다.
- 트랩 테이블 초기화: OS는 trap table을 메모리에 설정합니다. 이 테이블에는 system call을 처리하는 system call 핸들러를 포함하여 다양한 트랩 핸들러의 주소가 담겨 있습니다.
- 트랩 테이블 위치 알림: OS는 하드웨어의 특수 레지스터(x86의 경우 IDTR)에 트랩 테이블의 주소를 기록하여, 하드웨어에 "트랩이 발생하면 여기를 참조하라"고 알려줍니다.
2. 프로그램 실행 과정 (OS @ run)
프로그램 시작
- OS의 준비: OS는 새 프로세스를 위해 프로세스 목록에 엔트리를 생성하고, 프로그램 코드를 위한 메모리를 할당한 후, argv 등으로 사용자 스택을 설정합니다.
- kernel 스택 준비: OS는 복귀를 위해 필요한 레지스터와 프로그램 카운터(PC) 값을 커널 스택에 채워 넣습니다.
- 제어권 이전: OS는 return-from-trap 명령어를 실행하여 사용자 모드로 전환하고, main() 함수가 있는 위치로 점프합니다.
프로그램 실행 및 System call
- Direct Execution: 프로그램은 사용자 모드에서 CPU를 직접 사용하며 main() 함수를 실행합니다.
- System call: 프로그램이 I/O와 같은 privileged operation 필요해지면, call system call 명령어를 통해 trap을 발생시킵니다.
- 트랩 발생: 하드웨어는 트랩을 감지하고, 커널 스택에 사용자 프로그램의 상태를 저장한 뒤 커널 모드로 전환합니다.
- 트랩 핸들러로 점프: 하드웨어는 미리 설정된 트랩 테이블을 참조하여 시스템 호출 핸들러로 제어권을 넘깁니다.
- OS의 작업: OS는 시스템 호출 핸들러 내에서 요청된 작업을 수행합니다.
- 복귀: 작업이 끝나면 OS는 return-from-trap 명령어를 실행하여 커널 스택의 레지스터 값을 복원하고, 사용자 모드로 전환하여 트랩이 발생했던 명령어의 다음 위치로 돌아갑니다.
프로그램 종료 과정
- 종료 요청: main() 함수가 종료되거나 exit() 시스템 호출이 발생하면 다시 트랩이 발생합니다.
- OS의 마무리: OS는 해당 프로세스에 할당된 메모리를 회수하고, 프로세스 목록에서 제거하는 등 종료 절차를 수행합니다.
Interrupt Descriptor Table
1. 인터럽트 디스크립터 테이블(IDT)의 역할
IDT는 인터럽트 핸들러의 주소록입니다. CPU는 예외(exception)나 시스템 호출과 같은 특정 이벤트가 발생했을 때, 해당 이벤트 번호를 이용해 이 테이블에서 어떤 코드를 실행해야 할지 찾습니다.
- 인터럽트 번호: 다양한 시스템 이벤트에 할당된 고유한 번호입니다.
- 핸들러: 특정 인터럽트 번호에 대응하여 OS가 실행하는 함수(코드)입니다.
2. 시스템 호출 핸들러 등록
운영체제는 시스템 호출을 처리하기 위해 IDT의 특정 번호를 사용합니다.
- system call number: 일반적으로 0x30과 같은 특정 번호가 시스템 호출 전용으로 지정됩니다.
- 핸들러 등록: src/userprog/syscall.c 파일에서 시스템 호출 핸들러를 0x30에 등록한다는 것은, "0x30 인터럽트가 발생하면 syscall_handler라는 함수를 실행하라"는 규칙을 OS가 설정하는 것을 의미합니다.
결론적으로, 사용자 프로그램이 int $0x30과 같은 명령어를 실행하면 CPU는 IDT를 통해 0x30에 등록된 시스템 호출 핸들러를 즉시 찾아 실행함으로써 안전하게 OS의 기능을 사용할 수 있습니다.
문제 2: Switching between Processes
Direct Execution 방식에서 프로그램이 CPU를 사용하고 있을 때는 OS가 실행되지 않습니다. 이로 인해 OS는 프로세스를 전환할 수 있는 제어권을 상실하게 됩니다. 이 문제를 해결하기 위한 2가지 접근법이 있습니다. (OS가 제어권을 확보하는 2가지 방법)
1. A Cooperative Approach
이 방법은 프로세스가 자발적으로 제어권을 OS에 넘겨주는 방식입니다.
- 원리: 프로그램이 exit()나 yield()와 같은 system call을 통해 스스로 CPU 사용을 멈추고 OS에게 제어권을 넘깁니다.
- 장점: 구현이 간단합니다.
- 문제점: 만약 프로세스가 무한 루프에 빠지거나 시스템 호출을 전혀 사용하지 않으면, OS는 제어권을 되찾을 방법이 없습니다. 이 경우 시스템을 재부팅해야 합니다. 이 방식은 신뢰할 수 없는 프로세스 환경에서는 적합하지 않습니다.
2. A Non-Cooperative Approach
이 방법은 OS가 강제로 제어권을 가져오는 방식입니다.
- 원리: OS는 timer interrupt라는 하드웨어 장치를 사용합니다.
- OS가 프로그램을 실행시키기 전에 타이머를 설정합니다.
- 타이머가 지정된 시간이 지나면 인터럽트를 발생시킵니다.
- 이 인터럽트는 현재 실행 중인 프로그램의 실행을 강제로 중단시키고, OS에게 제어권을 넘겨줍니다.
- 이제 OS는 다음 프로세스로 전환할 수 있습니다.
- OS와 하드웨어의 협력: OS는 부팅 시 타이머 인터럽트 핸들러를 등록하고, 타이머를 시작시키는 privileged operation을 수행합니다. 하드웨어는 인터럽트 발생 시, 실행 중이던 프로세스의 상태를 충분히 저장해야 OS가 나중에 그 프로세스를 재개할 수 있습니다. 이는 시스템 호출 시의 트랩 처리와 유사합니다.
Context Switch
OS가 제어권을 되찾은 후, 스케줄러라는 OS의 한 부분이 현재 프로세스를 계속 실행할지, 아니면 다른 프로세스로 전환할지 결정합니다. 다른 프로세스로 전환하기로 결정하면 context switch이 일어납니다.
- 개념: context switch은 현재 프로세스의 상태를 저장하고, 다음 실행할 프로세스의 상태를 불러오는 과정입니다. 이 과정은 다음과 같이 진행됩니다.
- OS는 현재 실행 중인 프로세스의 일반 레지스터, PC(프로그램 카운터), 커널 스택 포인터 등의 값을 메모리에 저장합니다.
- 이어서, 다음 실행할 프로세스의 저장된 레지스터 값과 PC를 레지스터에 복원하고, 커널 스택 포인터를 전환합니다.
- 결과: 이 과정을 통해 OS는 return-from-trap 명령어가 실행될 때 원래의 프로세스가 아닌 다른 프로세스의 실행을 재개하도록 만듭니다.
Timer Interrupt를 이용한 Limited Direct Execution
이 프로토콜은 OS가 timer interrupt를 사용하여 제어권을 확보하고 프로세스를 전환하는 과정을 설명합니다. 이는 앞서 설명한 비협력적 접근의 구체적인 구현 방식입니다.
부팅 시 OS의 역할 (OS @ boot)
- OS는 커널 모드에서 시작하며, 트랩 테이블을 초기화합니다.
- 이 트랩 테이블에는 system call 핸들러뿐만 아니라 timer 핸들러의 주소도 포함됩니다.
- OS는 특권 작업을 통해 하드웨어 타이머를 설정하고 시작시킵니다. 이 타이머는 특정 시간마다 CPU에 인터럽트를 발생시키도록 프로그래밍됩니다.
실행 중 OS의 역할 (OS @ run)
- OS는 프로세스 A를 사용자 모드에서 직접 실행시킵니다.
- timer interrupt 발생: 설정된 시간이 지나면 하드웨어 타이머가 인터럽트를 발생시키고, 프로세스 A의 실행이 강제로 중단됩니다.
- 트랩 처리: 하드웨어는 프로세스 A의 상태(레지스터)를 커널 스택에 저장하고, 커널 모드로 전환한 뒤, 미리 등록된 타이머 핸들러로 제어권을 넘깁니다.
- Context Switch: OS는 타이머 핸들러 내에서 switch() 루틴을 호출하여 context switch을 수행합니다.
- 프로세스 A의 상태를 A의 프로세스 구조체(process struct)에 저장합니다.
- 다음 실행할 프로세스 B의 상태를 프로세스 구조체에서 불러와 레지스터에 복원합니다.
- 커널 스택 포인터를 B의 커널 스택으로 전환합니다.
- 복귀: context switch가 완료되면 OS는 return-from-trap 명령어를 실행하여 프로세스 B의 실행을 재개합니다. 이 명령어는 B의 상태(PC, 레지스터)를 복원하고 사용자 모드로 전환하여 B가 실행되도록 합니다.
동시성 문제 (Concurrency)
- 문제점: interrupt나 trap 처리 중에 또 다른 interrupt가 발생하면 시스템 상태가 불안정해질 수 있습니다.
- 해결책:
- 인터럽트 비활성화: OS는 인터럽트 처리 중에는 잠시 모든 인터럽트를 비활성화하여 다른 인터럽트가 발생하지 않도록 합니다. 하지만 너무 오래 비활성화하면 중요한 인터럽트 신호를 놓칠 수 있으므로 주의해야 합니다.
- Locking: OS의 내부 자료 구조를 보호하기 위해 정교한 잠금 기법을 사용합니다. 이는 여러 프로세스가 동시에 같은 자료 구조에 접근하는 것을 막아 데이터의 일관성을 유지합니다.
정리
- CPU 가상화의 핵심: time sharing은 CPU를 가상화하는 핵심 기술입니다.
- 두 가지 이슈:
- Performance: 가상화로 인한 오버헤드를 최소화해야 합니다.
- Control: OS가 CPU에 대한 통제권을 유지하면서 프로세스를 실행해야 합니다.
- 해결책:
- 성능: Direct Execution을 사용하여 대부분의 코드를 오버헤드 없이 실행합니다.
- 제어: Limited Direct Execution 방식을 사용합니다.
- mode switching: 트랩을 이용해 privileged 작업 시에만 사용자 모드와 커널 모드를 전환하여 OS가 시스템 자원을 보호합니다. 프로그램이 자발적으로 OS에 제어권을 넘깁니다. (예: 시스템 콜)
- timer interrupt: 타이머 인터럽트를 이용해 OS가 강제로 제어권을 되찾아 프로세스를 전환할 수 있습니다. OS가 비자발적으로 개입해 제어권을 강제로 가져옵니다.
참고
- Privileged operation = OS만 수행 가능한 작업
- Trap = 사용자 프로그램이 OS에게 안전하게 요청하는 메커니즘
- Return-from-trap = 사용자 모드로 복귀
- Timer Interrupt = OS가 강제로 CPU 제어권을 가져오는 방법
출처: 경북대학교 한명균 교수님, “운영체제” 강의 자료
'CS > OS' 카테고리의 다른 글
[OS] 주소 변환(Address Translation)과 Base-and-Bounds (1) 2025.10.04 [OS] 가상 메모리(Virtual Memory)의 원리와 작동 방식 (0) 2025.09.24 [OS] Priority Inversion부터 MLFQ까지: 스케줄링 문제와 해결책 (0) 2025.09.17 [OS] CPU 스케줄링의 원리와 알고리즘: FIFO, SJF, STCF, RR 비교와 I/O 통합 (0) 2025.09.15 [OS] 운영체제의 추상화: 프로세스와 CPU 가상화 (1) 2025.09.09