Limited Direct Execution
이번 포스트는 Limited Direct Execution으로 프로세스 실행의 기초와 제약에 대해 다룬다.
Limited Direct Execution
Direct Exectuion (D.E.)
CPU virtualization
- 목표는 하나의 물리 CPU를 여러 프로세스가 동시에 쓰는 것처럼 보이게 만드는 것이다. sharing (시분할)
- 여기서 OS가 반드시 해결해야하는 과제가 두 개 존재한다.
- Performance (성능) :부가비용 (문맥 전환, 커널 진입, 타이머 인터럽트)을 최소화해서 최대한 프로그램이 직접 CPU에 달리는 느낌을 주는 것
- Control (제어) : CPU를 OS가 항상 쥐고 있어야 한다. 한 프로세스가 영원히 점유하거나, 권한 밖의 I/O 메모리 접근을 못하게 해야 한다.
Direct Exectuion (D.E.)
- 가장 빠른 방법으로 그냥 프로그램을 CPU에서 바로 돌리는 것이다.
- 과정
1. 프로세스 엔트리 (PCB) 만들기
- PID, 레지스터 스냅샷,상태(READY / RUNNING..),스케줄링 정보 등
2. 메모리 할당
3. 프로그램 로드
4. 스택 초기화
- argc/argv(그리고 env) 준비
5. 레지스터 초기화
6. 진입
- 실제로는 _start-> C 런타임 초기화 -> main() 호출
(프로그램 쪽 흐름 : 유저모드로 넘어옴)
7. main() 실행
8. main return or exit()으로 종료
(다시 OS쪽으로 넘어옴)
9. 자원 해제
- Free : 메모리, 파일 디스크립터 정리
10. 프로세스 리스트에서 제거
Without limits on running programs, the OS wouldn’t be in control of anything and thus would be “just a library”
위 과정을 아무 제약 없이 두면, OS는 그저 실행을 도와주는 라이브러리일 뿐이다.
- 프로세스가 CPU를 독점하거나, I/O 같은 특권 작업을 멋대로 수행할 수도 있다.
Problem 1 : Restricted Operation
Issue 1 : 안전성 (Safety)
-
유저 프로그램을 그대로 CPU에 실행시키면, 임의 주소 쓰기/읽기, 디스크 I/O, 인터럽트 설정 같은 특권 연산까지 해버릴 수 있음.
-
예시
int *i; i = 0; *i = 1;- 주소 0에 쓰기를 시도하는 사고 사례.
- 커널과 다른 프로세스 메모리를 건드릴 수 있어서 위험.
Solution : Protected Control Transfer
“유저 → 커널”로 보호된 문을 통해서만 들어가게 하는 절차
User mode vs. Kernel mode
- User mode : 대부분의 위험한 명령과 레지스터 접근 금지. 장치, 메모리, 스케줄러 등 커널만 다룰 수 있는 자원은 직접 못 만진다.
- Kernel mode : OS 코드만 들어온다. 장치 I/O, 페이지 테이블, 인터럽트, 타이머 등 모든 특권 기능이 가능하다.
System Call
- 커널이 사용자 프로그램에게 제한된 형태로 기능을 제공하는 인터페이스
- 유저 프로그램은 직접 하드웨어에 접근 X
- 대신 OS (커널)가 그런 일을 대신하면서, 유저는 시스템 콜을 통해 간접적으로 요청
Trap Instruction
- 유저 프로그램이 커널에게 부탁하는 순간
- 역할
- Trap 명령이 실행되면 CPU는 커널 코드쪽으로 점프한다.
- 하드웨어의 Mode bit (권한 플래그)가 바뀐다. (CPU가 자동으로 수행)
- 현재 Program Counter (PC), 레지스터, 상태 비트 등을 커널 스택에 저장한다. (나중에 다시 돌아올 수 있도록 백업)
유저가 직접 커널 모드로 진입하는 것은 불가능
Trap은 보호된 문으로 진입하여 OS에게 제어권을 지키는 명령어
Return-from-trap Instruction
- 커널이 일을 다 마치고 다시 유저에게 돌려주는 순간
- 역할
- 커널이 처리를 마친 뒤에 이전에 저장된 PC / 레지스터 상태를 복원
- CPU가 유저 코드의 다음 명령으로 복귀
- CPU 모드 비트를 다시 User Mode로 되돌린다.
제어권과 권한을 되돌려주는 명령어
커널이 직접 user 모드로 내려오지 않고, 반드시 하드웨어 return 명령을 통해 복귀하게 함.
Trap
Trap이 발생할 때 커널은 어디서부터 코드를 실행할까?
- Trap 이 일어나면 CPU는 그냥 “커널로 간다” 가 아니다.
- 어떤 trap인지 구분해서 해당하는 코드를 실행해야 함.
- 이걸 담당하는 것 : Trap handler, Trap table
Source of Trap : Trap은 두 가지의 주요 원인으로 발생한다.
- Interrupt : 외부 하드웨어 이벤트에 의해 발생
- System Call : 소프트웨어가 의도적으로 trap 명령을 실행
Trap Handler
- Trap 이 발생했을 때 실행되는 커널 코드 (루틴)
- 각 trap(혹은 Interrupt)마다 자기 전용의 처리 코드가 있다.
- 예시
- Trap #0x00 -> Reset Handler
- Trap #0x04 -> Undefined Instruction Handler
- Trap #0x08 -> Software Interrupt (SWI) Handler
- CPU는 trap이 발생하면 해당 trap 번호를 이용해 handler 주소를 찾아 점프 한다.
Trap Table
- Trap 번호를 통해 Handler 주소를 저장하는 매핑 테이블
- 일종의 주소 목록으로 각 trap number에 대응하는 핸들러의 시작 주소를 저장
- Trap Table 초기화와 보호
- OS 부팅 시 : 커널이 trap table을 초기화
- 하드웨어가 OS에게 trap table의 위치를 알려줌
- 설정 명령 자체도 특권 명령 (privileged Instruction)
요약
Trap 발생 이후 Trap 번호 확인, Trap Table에서 해당 핸들러 주소로 점프하여 커널 핸들러 코드를 실행
Limited Direct Execution Protocol
Limited Direct Execution 이란?
- 사용자 프로그램을 CPU에서 직접 실행 (direct Execution) 시키되, OS가 완전히 제어권을 잃지 않도록 제한하는 실행 방식
- 빠르게 실행해야 하기 때문에 CPU에서 직접 돌림
- 하지만 보안과 제어를 위해 모드 전환/트랩/커널 스택 구조로 보호
1. 부팅 시 (OS boot - Kernel Mode)
- Trap Table 초기화
- CPU가 “어디로 점프해야 하는지”를 알게 함.
- Trap 발생 시 CPU가 점프할 주소를 미리 등록하는 과정
2. 프로그램 실행 시작 (OS run - Kernel Mode to User Mode)
- 커널이 새 프로세스를 실행시키는 단계
- 새 프로세스 (PCB) 생성, 프로세스의 상태 정보, 메모리 위치, 스택 포인터 저장
- 실행할 프로그램용 메모리 공간 확보
- 디스크에서 프로그램을 메모리로 로드
- 사용자 프로그램의 스택 초기화 (매개변수, 환경변수 등을 설정)
- 커널 스택에 초기 레지스터 값과 PC 저장
- 나중에 트랩 발생 시 돌아올 수 있도록 백업
- 하드웨어가 커널에서 유저모드로 전환하여
main()으로 점프
3. 실행 중 (Program - User Mode to Kernel Mode to User Mode)
- 커널과 사용자 프로그램이 트랩을 통해 안전하게 제어권을 주고받는 구조
4. 프로그램 종료
return from main -> trap (via exit())- OS가 프로세스 메모리 해제, 프로세스 리스트에서 제거
- 완전히 종료되고 커널로 제어권 복귀
Problem 2 : Swithcing Between Processes
Issue 2 : 제어 (Control)
- OS가 CPU를 time sharing하기 위해 주기적으로 통제권을 되찾아야 하는데, 유저 코드가 while(1) { } 처럼 영원히 안 돌아오면 OS가 스케줄링을 못 한다.
CPU는 한 번에 하나의 프로세스만 실행이 가능하다. 그런데 여러 프로세스가 동시에 돌아가는 것처럼 보이기 위해 OS가 주기적으로 CPU를 다른 프로세스로 넘겼다가 다시 가져와야 한다.
OS가 어떻게 CPU 제어권을 얻어올 수 있을까?
문제 상황
- 사용자 프로그램은 User Mode에서 CPU를 직접 사용
- 프로그램이 만약 계속 무한루프를 돌거나, system call을 하지 않는다면?
- 다른 프로세스는 실행될 수 없고, 스케줄링도 불가능하다.
CPU 제어권을 되찾는 두 가지 접근 방식
1. Cooperative Approach
- OS가 프로세스가 자발적으로 CPU를 양보할 때까지 기다린다.
- 프로세스가 System Call을 호출해야 OS가 제어권을 얻는다.
2. Non-Cooperative Approach
- OS가 강제로 제어권을 가져온다.
- Timer Interrupt를 이용해서 주기적으로 CPU를 빼앗는다.
Cooperative Approach : Wait for System Calls
프로세스가 System Call을 통해 주기적으로 CPU를 양보
-
사용자 프로그램이 일정 주기마다 system call 등을 호출하면서 스스로 CPU를 OS에 반환
-
예시
while (true) { do_some_work(); yield(); // 다른 프로세스가 실행할 수 있게 CPU 넘김 }yield()를 통해 OS가 제어권을 얻고 다른 프로세스를 실행이 가능
예외 상황이 발생 시 OS가 제어권을 휙득
- 프로그램이 잘못된 연산을 하면 (예 : divide by zero, 잘못된 메모리 접근 등) 하드웨어가 Trap을 발생시키고 OS로 제어권이 넘어간다.
- 이 경우 OS가 예외 처리를 수행하고 다시 CPU를 관리할 수 있다.
Cooperative Approach 의 문제점
- 만약 프로세스가 협조하지 않는 상황
- 프로그램이
while (1) ;과 같은 무한 루프에 빠지면 더 이상 system call을 호출하지 않는다. - trap이 발생하지 않으면 OS가 개입할 방법이 없다.
- 결과적으로 CPU 제어권을 영영 돌려받지 못함.
해결책은 단 하나
Reboot the Machine (컴퓨터 재부팅)
Non-Cooperative Approach : OS Takes Control
Timer Interrupt
- 타이머 설정 (During the boot sequence)
- 부팅할 때 OS는 하드웨어 타이머를 설정한다.
- Timer가 주기적으로 인터럽트를 발생
- 지금 실행 중인 프로세스를 잠시 멈추고 미리 등록된 Interrupt Handler로 점프
- 인터럽트가 발생하면 생기는 일
- 현재 실행 중인 프로세스 일시 중단 (suspended)
- 프로세스의 상태 (state)를 저장
- 미리 설정된 interrupt handler를 실행
- 결과 : OS가 CPU를 다시 제어한다.
A timer interrupt gives OS the ability to run again on a CPU
- 타이머 인터럽트는 OS가 주기적으로 CPU 제어권을 확보할 수 있는 통로
- OS는 강제로 문맥 전환 (context switch)을 실행할 수 있음.
- 현대 운영체제의 선점형 멀티태스킹의 핵심 메커니즘
Saving & Restoring Context
CPU 제어권을 되찾은 OS가 다음으로 무엇을 하는가?
-
현재 프로세스의 상태를 저장하고, 다음 프로세스의 상태를 복원하는 과정
-
Scheduler 의 역할
- 지금 이 CPU를 누가 사용할지 결정하는 운영체제의 핵심 컴포넌트
- 두 가지 중 하나를 결정
- 현재 프로세스를 계속 실행시킬지
- 다른 프로세스로 CPU를 넘길지
- 스케줄러가 Switch를 결정하면 Context Switch가 발생
Context : 프로세스의 실행 상태
Context Switch
동작 단계
1. Save 현재 프로세스 (A)의 상태 (Context Save)
-
현재 CPU에서 실행 중인 프로세스 A의 실행 정보를 저장해야 한다.
-
CPU 내부에는 다음과 같은 정보들이 있음.
- Registers : 연산 중인 값들
- Program Counter : 지금 실행 중인 명령어 주소
- Stack Pointer : 현재 스택의 top 위치
이 값들을 모두 A의 커널 스택에 저장 한다.
Kernel Stack
- 각 프로세스가 시스템 콜이나 인터럽트로 커널 모드에 들어올 때 자신만의 커널 공간에 존재하는 스택 영역
2. Scheduler가 새로운 프로세스(B) 선택
(타이머 인터럽트나 스케줄러 결정에 따라)
- OS의 스케줄러가 실행 준비 상태 (Ready Queue)에 있는 다음 프로세스 B를 선택한다.
3. Restore 다음 프로세스(B)의 상태 (Context Restore)
- 이제 B의 PCB 와 커널 스택에 저장되어 있던 값을 CPU로 다시 복원한다.
4. Switch to the kernel stack for B
- CPU는 이제 A의 커널 스택이 아닌 B의 커널 스택으로 전환해야 한다.
- 다음 번 system call이나 interrupt가 발생할 때 B의 정보를 올바르게 저장이 가능하다.
5. 실행 재개 (Return to User Mode)
- 모든 레지스터 복원과 스택 교체가 끝나면, CPU는 이제 B의 Program Counter 값으로 점프한다.
Limited Direct Execution
| 단계 | 주체 | 설명 |
|---|---|---|
| ① 부팅 시 | OS & HW | Trap Table 초기화 + Timer 설정 |
| ② 프로세스 실행 중 | HW | Timer Interrupt 발생 → CPU 제어권 OS로 |
| ③ 커널 진입 | OS | A의 상태 저장 + Scheduler 호출 |
| ④ 스케줄러 결정 | OS | 다음 실행할 프로세스(B) 선택 |
| ⑤ Context Switch | OS | A의 레지스터/스택 저장 → B의 레지스터/스택 복원 |
| ⑥ 실행 재개 | HW & OS | 커널 모드 → 유저 모드 전환 → B의 PC로 점프 |
타이머 인터럽트를 이용한 Limited Direct Execution은 OS가 주기적으로 CPU 제어권을 강제로 되찾아, Context Swtich를 통해 프로세스 간 실행을 전환하는 메커니즘