7 분 소요

이번 포스트는 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

  • 유저 프로그램이 커널에게 부탁하는 순간
  • 역할
    1. Trap 명령이 실행되면 CPU는 커널 코드쪽으로 점프한다.
    2. 하드웨어의 Mode bit (권한 플래그)가 바뀐다. (CPU가 자동으로 수행)
    3. 현재 Program Counter (PC), 레지스터, 상태 비트 등을 커널 스택에 저장한다. (나중에 다시 돌아올 수 있도록 백업)

유저가 직접 커널 모드로 진입하는 것은 불가능

Trap은 보호된 문으로 진입하여 OS에게 제어권을 지키는 명령어

Return-from-trap Instruction

  • 커널이 일을 다 마치고 다시 유저에게 돌려주는 순간
  • 역할
    1. 커널이 처리를 마친 뒤에 이전에 저장된 PC / 레지스터 상태를 복원
    2. CPU가 유저 코드의 다음 명령으로 복귀
    3. 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로 점프
  • 인터럽트가 발생하면 생기는 일
    1. 현재 실행 중인 프로세스 일시 중단 (suspended)
    2. 프로세스의 상태 (state)를 저장
    3. 미리 설정된 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를 누가 사용할지 결정하는 운영체제의 핵심 컴포넌트
    • 두 가지 중 하나를 결정
      1. 현재 프로세스를 계속 실행시킬지
      2. 다른 프로세스로 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를 통해 프로세스 간 실행을 전환하는 메커니즘