운영체제 아주 쉬운 세가지 이야기- 2주차

2025. 9. 9. 17:21개념 공부

<!doctype html>

 

CPU 가상화와 프로세스: OSTEP 기반 고급 요약

목차
  1. TL;DR
  2. 프로세스 추상화
  3. UNIX Process API: fork/exec/wait/FD
  4. Limited Direct Execution: 트랩·타이머·컨텍스트 스위치
  5. 프로세스 vs 스레드 핵심 비교
  6. 핵심 예제 & 실습
  7. 복습 퀴즈 & 면접질문
  8. 치트시트
  9. 참고
  • 가상화의 목표: 소수의 물리 CPU로 다수의 가상 CPU 환상 제공(타임셰어링). 정책(policy)과 메커니즘(mechanism)을 분리해 설계. 
  • 프로세스: 실행 중인 프로그램 + 주소공간/레지스터/열린 FD/프로세스 상태 등 머신 상태의 집합. 러닝↔레디↔블록드 상태 전이를 가진다.
  • API: fork(복제/COW) + exec(프로그램 교체) + wait(동기화)로 셸의 리다이렉션·파이프 구현까지 가능. 
  • LDE: 사용자/커널 모드, 트랩(syscall), 타이머 인터럽트(선점), 컨텍스트 스위치로 성능과 제어를 동시에 달성. 

1) 프로세스 추상화

정의 & 정신모델

프로세스는 “실행 중인 프로그램”으로, 주소공간(코드/데이터/힙/스택), 레지스터(PC, SP 등), 열린 파일 등의 머신 상태로 기술된다. OS는 타임셰어링으로 다수의 프로세스를 교대로 실행해 다수의 CPU가 있는 것처럼 보이게 한다. 정책(스케줄링)과 메커니즘(컨텍스트 스위치)을 분리한다.

프로세스 생성: 프로그램→프로세스

  • 코드/정적 데이터 로드(과거 eager, 현대 lazy), 스택 초기화(argc/argv), 힙 준비(malloc/free). 
  • 기본 FD(표준 입출력 3종) 세팅 → main() 진입.

상태(State)와 전이

  • Running: CPU 위에서 실행 중
  • Ready: 실행 준비 완료(스케줄 대기)
  • Blocked: I/O 등 이벤트 대기
Running ⇄ Ready
↓ I/O
Blocked → Ready (I/O 완료)

커널의 데이터구조

PCB(혹은 task 구조체)에 레지스터 컨텍스트, 상태, PID, 열린 파일, 커널스택, 트랩프레임 등을 유지한다.

2) UNIX Process API: fork/exec/wait

핵심 콤비

  • fork(): 현재 프로세스를 COW로 가볍게 복제. 부모는 자식 PID, 자식은 0을 반환. 스케줄링에 따라 출력 순서 비결정적. 
  • wait()/waitpid(): 부모가 자식 종료를 기다려 결정적 순서 보장.
  • exec*(): 현재 프로세스의 코드/정적데이터/스택/힙을 새 실행파일로 교체. 성공 시 복귀하지 않음. 

셸이 이 조합을 사랑하는 이유

  • 리다이렉션: fork 후 자식에서 close(STDOUT)open("file")exec로 표준출력을 파일에 결선. 
  • 파이프: pipe()로 두 프로세스의 표준 입/출력을 커널 파이프로 연결. grep -o foo file | wc -l 같은 조합 구현. 
  • 시그널/권한: 사용자 격리, root 권한, kill/killall, signal()로 핸들러.

3) Limited Direct Execution: 트랩·타이머·컨텍스트 스위치

아이디어

가능한 한 직접 실행해 성능을 얻고(사용자 모드), 필요한 순간에만 제한(limit)을 걸어 커널이 통제한다(커널 모드). 

특권 분리와 시스템콜

  • 사용자 모드에서는 특권 연산(I/O, MMU 등) 금지. 위반 시 예외. 
  • 트랩: 라이브러리 래퍼가 번호/인자 셋업 → trap → 커널 핸들러 → return-from-trap으로 복귀. 트랩 테이블은 부팅 시 커널이 등록.

선점 스케줄링의 열쇠: 타이머 인터럽트

비협조적 프로세스가 시스템콜을 안 해도 타이머 인터럽트로 커널이 CPU를 회수 → 스케줄러가 전환 결정 → 컨텍스트 스위치 실행. 

timer interrupt → save regs(A) → kernel → switch(A→B) → return-from-trap → run B

컨텍스트 스위치

하드웨어가 트랩 시 사용자 레지스터를 커널스택에 저장, 커널이 switch()에서 커널 레지스터/스택을 다른 프로세스로 교체 후 복귀. 

보안 주의: 시스템콜 경계에서 유저 인자를 철저히 검증해야 커널/타 프로세스 메모리 유출을 막을 수 있다. 

4) 프로세스 vs 스레드 (초간단 비교)

측면 프로세스 스레드
주소공간 분리(격리) 공유
생성/전환 무겁다(CR3/TLB 영향) 가볍다(동일 VAS)
통신 IPC 필요 메모리 직접 공유(동기화 필요)
격리/보안 우수 취약(프로세스 전체 영향)

 

5) 핵심 예제 & 실습

fork + wait (순서 보장)

#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>

int main(){
  pid_t pid=fork();
  if(pid==0){ puts("child"); }
  else if(pid>0){ wait(NULL); puts("parent"); }
  else { perror("fork"); }
}

 

리다이렉션: stdout → 파일

#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
int main(){
  if(fork()==0){
    close(1);
    open("out.txt", O_CREAT|O_WRONLY|O_TRUNC, 0644);
    execlp("wc","wc","main.c",(char*)0);
  } else { wait(NULL); }
}

 

연습 문제

  1. 프로세스의 머신 상태를 구성하는 요소를 열거하고, 각 요소가 어디에 저장되는지 설명하라.
  2. forkwait이 순서를 결정적으로 만드는 이유를 설명하라.
  3. 트랩/리턴-프롬-트랩, 타이머 인터럽트가 선점과 제어에 기여하는 과정을 단계별로 서술하라.
정답 보기 (펼치기/접기)

1) 프로세스의 머신 상태 & 저장 위치

  • 주소 공간 (코드/데이터/힙/사용자 스택): 프로세스의 가상 메모리에 존재. 물리 프레임 매핑은 페이지 테이블이 관리.
  • 레지스터 집합 (PC, SP, GPR, FLAGS 등): 실행 중엔 CPU 레지스터에, 트랩/컨텍스트 스위치 시엔 커널이 커널 스택PCB(Task/Proc 구조)에 저장.
  • 페이지 테이블/메모리 맵: 커널 내 프로세스 메모리 디스크립터(mm 구조체 등)와 하드웨어 PT에 반영. 프로세스 전환 시 CR3/TTBR 전환.
  • 열린 파일 테이블(FD 테이블): 프로세스별 커널 객체(파일 디스크립터 테이블). 각 엔트리는 오픈-파일 설명자(오프셋/플래그)와 vnode/inode를 가리킴.
  • 신호/마스크/핸들러: 커널의 per-process/ per-thread 구조체에 저장.
  • 커널 스택: 각 (커널에서 스케줄되는) 스레드마다 존재. 트랩 시 HW가 사용자 문맥 일부를 여기에 푸시.
  • 자격/리소스 한도 (UID/GID, rlimit, cgroup 등): 커널의 작업 구조체에 보관.
  • 스케줄링 정보 (상태/우선순위/실행시간): 런큐 & PCB에 유지.

2) forkwait이 순서를 결정적으로 만드는 이유

fork() 직후 부모·자식은 동시에 실행돼 출력 순서가 비결정적이다. 부모가 wait()를 호출하면 커널은 부모를 블록하고, 자식이 종료 상태가 될 때까지 깨워주지 않는다(SIGNAL/자식 상태 변화). 자식 종료 후 부모가 깨어나 순차적으로 진행하므로 “항상 자식 → 부모” 같은 결정적 순서를 보장할 수 있다.

3) 트랩/리턴-프롬-트랩 & 타이머 인터럽트의 단계

  1. 시스템 콜 진입(트랩): 사용자 코드가 래퍼를 통해 번호/인자를 레지스터/스택에 세팅 → syscall/int 0x80/svc 실행.
  2. 권한 전환: HW가 커널 모드로 전환, 커널 스택 포인터로 스택을 바꾸고 사용자 레지스터를 커널 스택에 자동 저장.
  3. 핸들러 실행: 커널이 인자 검증·자원 액세스 수행. 필요 시 스케줄러를 호출하여 다른 태스크로 전환.
  4. 리턴-프롬-트랩: 커널이 저장한 레지스터를 복원, iret/sysret/eret로 사용자 모드로 귀환.
  5. 타이머 인터럽트: 주기적으로 발생해 커널로 트랩 → 현재 태스크의 커널 문맥 저장 → 스케줄러가 선점 판단 시 현재 태스크의 컨텍스트를 PCB에 저장하고 다음 태스크의 문맥을 로드 → 리턴-프롬-트랩으로 선택된 태스크가 재개.

요점: 트랩은 “사용자→커널” 제어 이전, 타이머는 커널이 주기적으로 CPU를 회수하여 선점을 실현한다.

7) 치트시트

  • 상태 전이: Running⇄Ready, Running→Blocked(I/O), Blocked→Ready(완료)
  • API: fork(복제,COW) → exec(교체,복귀X) → wait(동기화)
  • LDE: 사용자/커널 모드, 트랩, 트랩테이블, 타이머, 컨텍스트 스위치