728x90
이 글은 OSTEP(Operating Systems: Three Easy Pieces)의 ‘Mechanism: Limited Direct Execution’ 문서를 직접 읽고 정리한 내용이다.
DevOps 관점(왜 중요한가/적용 방식)은 글 맨 마지막에 의견(Opinion) 으로 분리했다.

1) 문제 설정: “빠르게” + “통제” 둘 다 잡기
- 목표는 단일 CPU를 여러 프로세스가 시간 분할로 나눠 쓰게 해 가상화(virtualization) 를 달성하는 것. 이때 성능(오버헤드 최소화) 과 통제(악의적/버그 있는 프로그램 제어) 를 동시에 만족해야 한다.
2) 기본 해법: Limited Direct Execution (LDE)
- 아이디어: 대부분의 시간은 사용자 프로그램을 하드웨어에서 직접 돌린다.
단, 특정 시점(시스템콜, 타이머 인터럽트, 예외 등)에는 커널로 안전하게 제어권을 전환해 “올바른 일”이 일어나게 한다.
3) 보호와 전환의 토대: 모드 비트, 트랩 테이블, 특권 명령
- 유저/커널 모드: CPU는 유저 모드에서 제한된 명령만 허용한다(예: 직접 I/O, 인터럽트 비활성화 등은 금지). 커널 모드에서만 특권 명령 실행 가능.
- 트랩 테이블(Interrupt/Trap Vector): “어떤 이벤트(시스템콜 번호/예외/인터럽트) → 어떤 커널 코드”로 이어질지 부팅 시 OS가 등록한다.
- 보호된 제어 이전(Trap/Interrupt): 유저→커널로의 진입은 하드웨어가 PC/레지스터/모드 등을 보존하고 커널 스택으로 전환한 뒤, 해당 핸들러로 점프한다. 반대로 return-from-trap으로 유저 모드로 돌아간다.
4) 시스템콜 경로(유저→커널→유저)
1) 앱이 syscall/trap 명령으로 트랩을 발생(시스템콜 번호 전달).
2) 하드웨어가 현재 컨텍스트(PC/레지스터/플래그)를 커널 스택에 저장, 커널 모드로 전환, 트랩 테이블을 따라 핸들러 진입.
3) 커널은 번호로 해당 서비스 루틴(예: read, write)을 실행.
4) 완료 후 return-from-trap으로 유저 모드 복귀(저장한 레지스터/PC 복원).
의사코드(개념 흐름)
// 유저 코드
int fd = open("data.txt", O_RDONLY); // -> trap int 0x80 / syscall
// 커널 측 (매우 단순화)
void trap_handler(TrapFrame* tf) {
switch (tf->syscall_no) {
case SYS_open: tf->retval = sys_open(tf->arg0, tf->arg1); break;
// ...
}
// 하드웨어가 rft(returns from trap) 시 tf로부터 레지스터/PC 복원
}
5) 선점의 열쇠: 타이머 인터럽트
- OS는 주기적 타이머를 설정해 X ms마다 인터럽트를 발생시켜 유저 코드 실행을 중단하고 커널로 진입한다.
- 이렇게 얻은 제어권으로 OS는 스케줄러를 호출해 다른 프로세스로 전환(선점형 타임쉐어링 실현).
6) 컨텍스트 스위치: PCB·커널 스택·레지스터 보존
- 언제 스위치? 프로세스 종료/블록(예: I/O 대기), 타이머 인터럽트, 우선순위 정책 등.
- 무엇을 저장? CPU 레지스터, PC, 스택 포인터, 상태 플래그, 일부 아키텍처 자원(TLB 관련 컨텍스트 등)을 프로세스 제어 블록(PCB) 또는 커널 스택에 저장.
- 어떻게 복원? 다음에 실행할 프로세스의 PCB/스택에서 레지스터/PC를 읽어 유저 모드로 복귀.
의사코드(개념 흐름)
// 타이머 인터럽트 → 스케줄
void timer_isr() {
save_regs_to(curr->kstack); // 현재 레지스터 보존
if (curr->state == RUNNING) curr->state = READY;
enqueue(readyq, curr);
proc_t* next = pick_next(readyq); // 스케줄러 결정
next->state = RUNNING;
switch_to(next); // 스택/레지스터/주소공간 스위치
// rft: 하드웨어가 next의 유저 컨텍스트로 복귀
}
비용 인지: 스위치는 캐시/TLB 교란과 모드 전환 비용을 수반한다. 슬라이스/정책/자료구조(예: runqueue) 선택이 체감 성능에 영향을 준다.
7) 예외(잘못된 메모리 접근 등)와 인터럽트 중첩
- 예외(Exception): 불법 명령/페이지 폴트 등에서 강제 트랩으로 커널 진입, 처리 후 복귀 또는 프로세스 종료.
- 중첩 처리: 커널이 인터럽트 처리 중일 때 중첩 인터럽트가 가능하도록(또는 비활성화 구간을 짧게) 설계한다. 커널은 크리티컬 섹션에서만 인터럽트를 잠시 막는다.
8) “OS가 그냥 라이브러리였다면” 왜 안 되는가
- 무제한 직접 실행을 허용하면 유저 코드가 인터럽트를 끄거나 I/O를 직접 수행하는 등 전체 시스템 제어권을 가로챌 수 있다.
- LDE는 “대부분 직접 실행” + “핵심 지점은 커널이 강제 개입”을 결합해 성능과 통제를 동시에 달성한다.
9) 흐름 요약(텍스트 다이어그램)
(부팅) 트랩 테이블 설치/타이머 설정
↓
프로세스 생성 → 유저 모드로 진입 → main() 실행
↓
[정상 실행] ──(syscall)──▶ [트랩 진입: 커널] ──▶ 서비스 처리 ──▶ rft ──▶ 유저 복귀
│
├──(예외)──▶ [커널] → 처리/종료
│
└──(타이머 인터럽트)──▶ [커널: 스케줄러] → 컨텍스트 스위치 → 다음 프로세스
10) 핵심 체크리스트
- 유저/커널 모드 분리와 트랩 테이블을 이해했는가?
- 시스템콜 경로(trap → handler → rft)와 타이머 인터럽트의 역할을 구분할 수 있는가?
- 컨텍스트 스위치에 필요한 저장/복원 요소와 비용(캐시/TLB) 을 인지했는가?
(부록) DevOps 관점 — 왜 중요하고, 어떻게 적용하나 (Opinion)
A) 왜 중요한가
- 관측·튜닝 포인트 명확화: 선점/스위치 지점(타이머, 블록, I/O)과 컨텍스트 스위치 빈도/오버헤드는 CI 러너·빌드 팜·다중 테넌트 워크로드의 체감 성능에 직결된다.
- 보안/격리 감각: 유저/커널 모드 전환과 특권 경계를 이해하면, 컨테이너 런타임/샌드박스/에이전트 보안 정책을 합리적으로 설계할 수 있다.
B) 적용 방식
- 슬라이스/우선순위 조정: 타이머 주기·스케줄러 정책을 워크로드 패턴에 맞게 조정(짧은 I/O 중심 ↔ CPU 바운드).
- 스위치 비용 관측:
context switches/sec, 런큐 길이, TLB 미스/LLC 미스(가능 시)로 스위치 과다를 탐지. - 시스템콜 핫패스 최적화: 짧고 빈번한 syscall 경로(예: 로깅 I/O) 배치, 버퍼링·비동기화로 트랩 빈도 절감.
- 커널 경로 안정화: 드라이버/보안 모듈(예: eBPF) 삽입 시 중첩 인터럽트/latency 영향 관찰.
- 격리 정책: 루트리스/권한 제한으로 특권 남용을 차단, 필수만 허용.
728x90
'프로그래밍공부(Programming Study) > CS-운영체제(OS)' 카테고리의 다른 글
| 현대 고성능 서버의 데이터 전송 메커니즘: epoll부터 TCP BBR까지 (0) | 2026.01.18 |
|---|---|
| OSTEP: 10. Multiprocessor Scheduling (Advanced) (0) | 2025.10.10 |
| OSTEP: 5. Interlude: Process API (0) | 2025.10.08 |
| OSTEP: 4. The Abstraction: The Process (0) | 2025.10.07 |
| OSTEP: 9. Scheduling: Proportional Share (0) | 2025.10.07 |
댓글