운영체제 부팅 과정은 BIOS가 아니라 UEFI에서 시작한다

전원 버튼을 누르면 ROM에 저장된 BIOS가 실행되고, 하드웨어를 점검한 뒤 하드디스크 첫 섹터의 마스터 부트 레코드에서 부트로더를 읽어 메모리로 올린다. 나도 이 순서로 외웠고 면접에서도 이렇게 답했다. 그런데 지금 팔리는 PC에는 이 경로가 없다

Intel은 2020년까지 클라이언트와 데이터센터 플랫폼에서 레거시 BIOS를 제거하고 플랫폼을 UEFI Class 3로만 출하하겠다고 밝혔다(Last Mile Barriers to Removing Legacy BIOS). Windows 11의 펌웨어 요구사항은 한 줄로 “UEFI, Secure Boot capable”이다(Windows 11 requirements). 내가 외운 운영체제 부팅 과정은 틀린 것이 아니라 유효 기간이 지났다

운영체제가 하는 일, 커널과 인터페이스, 폰 노이만 구조, 부팅, 인터럽트까지 개론 첫 장에 해당하는 내용을 지금 기준으로 정리한다. 인터럽트는 “폴링보다 빠르다”는 문장으로만 넘어가는 경우가 많아서 비용을 직접 재고 숫자를 붙였다

이 글에서 자주 나오는 용어

  • ROM(Read-Only Memory): 전원이 꺼져도 저장된 데이터가 지워지지 않는 비휘발성 기억장치
  • BIOS(Basic Input/Output System): 컴퓨터 전원을 켰을 때 가장 먼저 실행되어 하드웨어를 초기화하고 운영 체제를 부팅하는 기본 입출력 시스템 펌웨어
  • UEFI(Unified Extensible Firmware Interface): 컴퓨터 전원을 켤 때 가장 먼저 실행되어 하드웨어를 점검하고 운영체제(OS)를 연결해 주는 통일 확장 펌웨어 인터페이스
  • 커널(Kernel): 운영체제에서 CPU·메모리·저장장치를 실제로 관리하는 부분. 운영체제 전체가 아니라 그 핵심이다
  • 펌웨어(Firmware): 메인보드에 붙은 칩에 담겨, 운영체제보다 먼저 실행되는 소프트웨어. BIOS와 UEFI가 여기 해당한다
  • 부트로더(Boot Loader): 펌웨어가 실행한 뒤 운영체제 커널을 메모리로 올리는 프로그램
  • 시스템콜(System Call): 사용자 프로그램이 커널의 기능을 요청하는 통로
  • 특권 수준(Privilege Level): CPU가 어떤 명령어까지 실행할 수 있는지를 정하는 상태. 커널은 높은 쪽에서, 사용자 프로그램은 낮은 쪽에서 돈다
  • 인터럽트(Interrupt): 장치가 CPU에게 “일이 끝났다”고 알리는 신호. CPU는 하던 일을 멈추고 처리 함수로 점프한다
  • 폴링(Polling): CPU가 장치에게 “끝났냐”고 주기적으로 물어보는 방식
  • ESP(EFI System Partition): UEFI가 부트로더 파일을 찾는 전용 파티션

운영체제가 없어도 컴퓨터는 동작한다

“컴퓨터는 운영체제가 있어야 동작하는가”라는 질문의 답은 “없어도 동작한다”다. 다만 처음 설계한 그대로만 동작한다. 예전 유선 전화기는 통화만 되고 그 외에는 아무것도 못 했지만, 운영체제가 있는 스마트폰은 앱을 설치해 기능을 늘린다. 운영체제가 있느냐 없느냐의 차이는 “동작하느냐”가 아니라 “나중에 프로그램을 얹을 수 있느냐”다

그래서 운영체제가 관리하는 대상도 프로그램을 얹기 위해 필요한 것들로 정해진다

운영체제가 관리하는 것
├── 프로세스    여러 프로그램이 CPU를 나눠 쓰게 한다
├── 메모리     여러 프로그램을 메모리에 함께 올려 두고 서로 침범하지 못하게 한다
├── 하드웨어    사용자 프로그램이 장치에 직접 손대지 못하게 막고 대신 처리한다
└── 파일 시스템 저장장치의 데이터를 이름 있는 파일로 다룬다

네 항목이 따로 있는 것처럼 보이지만 하나의 요구에서 갈라져 나온다. 프로그램을 여러 개 동시에 돌리려니 CPU를 나눠야 하고(프로세스), 메모리에 여럿을 올려야 하고(메모리), 그 프로그램들이 장치와 저장소를 서로 망가뜨리지 않게 중재해야 한다(하드웨어·파일 시스템). 운영체제 과목 전체가 “여러 프로그램을 동시에 돌린다”는 한 문장의 부작용을 처리하는 방법이라고 봐도 크게 틀리지 않는다

역사는 CPU를 놀리지 않으려는 시도의 연속이다

운영체제 역사를 훑으면 오늘날 운영체제가 왜 이렇게 복잡해졌는지가 설명된다. 시작은 단순하다. 하드웨어가 사람보다 훨씬 비쌌다

시기방식해결한 문제남은 문제
1940년대스위치·배선 직접 연결(ENIAC)프로그램 하나 바꾸는 데 몇 주
1950년대 초펀치카드 입력, 라인프린터 출력배선 작업 제거오퍼레이터가 카드를 넣고 결과를 빼는 시간
1950년대 중후반싱글 스트림 배치오퍼레이터 오버헤드 제거입력 대기 중 CPU가 논다
1960년대시분할 시스템입력 대기 중 다른 프로그램 실행메모리 침범, 주소 문제
1970년대 이후개인용 컴퓨터, GUI하드웨어 가격 하락

표의 맨 오른쪽 열이 다음 행의 왼쪽으로 이어진다. 각 단계는 앞 단계에서 CPU가 놀던 시간을 하나씩 회수한 것이다

첫 줄의 ENIAC은 미 육군 탄도연구소(Ballistic Research Laboratory)가 감당하지 못하던 포병 사격표(firing table) 계산을 위해 만들어졌다. 하루 여섯 건씩 들어오는 사격표 요청을 사람과 책상 계산기로는 따라갈 수 없었고, 책상 계산기로 20시간 걸리던 탄도 하나를 ENIAC은 30초에 계산했다(ENIAC: The Army-Sponsored Revolution, US Army Research Laboratory). 같은 문서 기준으로 계약은 1943년 6월, 최종 조립은 1945년 가을, 공개 행사는 1946년 2월이다. 프로그래밍은 특정 명령에 맞춰 스위치와 배선을 직접 연결하는 방식이라 문제 하나를 바꾸는 데 몇 주가 걸렸다. 하드웨어는 비싸고 인건비는 쌌기 때문에, 설계자들의 관심사는 “어떻게 하면 CPU를 최대한 많이 쓸까” 하나였다

1950년대 초에 진공관을 대신한 부품은 트랜지스터다. 집적회로는 그보다 뒤에 나온다. 최초로 동작을 보인 집적회로는 1958년 9월 12일 텍사스 인스트루먼츠의 잭 킬비가 만든 것이고(Computer History Museum, 1958), 오늘날 반도체 공정으로 이어지는 모놀리식 집적회로 개념은 1959년 페어차일드의 로버트 노이스가 특허로 정리했다(Computer History Museum, 1959). 이 순서를 알아야 표의 두 번째 줄이 이해된다. 1950년대 컴퓨터가 여전히 방 하나를 차지했고, 키보드와 모니터 없이 펀치카드와 라인프린터로만 입출력을 한 것은 부품을 아직 작게 만들지 못했기 때문이다

1960년대에 메모리에 여러 프로그램을 올리기 시작하자 두 가지 문제가 동시에 터졌다. 프로그램이 남의 메모리를 침범하는 문제, 그리고 자기가 몇 번지에 올라갔는지 모르는 문제다. 두 번째 문제는 하드웨어에 base register를 두어 프로그램의 시작 주소를 저장하고, 모든 프로그램은 0번지에서 시작한다고 가정하게 만드는 것으로 풀었다. 이 가정은 지금도 유지되고 있고, 가상 메모리 편에서 다시 나온다

커널로 들어가는 문은 세 개다

운영체제의 핵심은 커널이다. 커널은 프로세서와 메모리, 저장장치를 관리한다. 그리고 커널에는 아무나 직접 들어갈 수 없다. 들어가는 문이 세 개 있고, 누가 들어오느냐에 따라 문이 다르다

사용자 ──── GUI / CLI ──────┐
                          │
애플리케이션 ── 시스템콜 ──┼──▶ 커널 ──── 드라이버 ────▶ 하드웨어
                          │

사용자는 GUI나 CLI로 들어간다. 그래픽이냐 텍스트냐만 다르고 커널에 닿는다는 목적은 같다. 애플리케이션은 시스템콜로 들어간다. 하드웨어는 반대 방향에서 드라이버로 붙는다. 운영체제가 세상의 모든 장치를 미리 알 수는 없으니, 장치를 만든 제조사가 드라이버를 만들어 커널에 끼우는 구조다

“커널에 직접 접근할 수 없다”는 문장을 강제하는 것은 운영체제가 아니라 CPU다. CPU는 특권 수준을 나눠 놓고, 낮은 특권에서는 애초에 실행되지 않는 명령어를 둔다. 커널은 높은 특권에서 돌고 사용자 프로그램은 낮은 특권에서 돈다. 사용자 프로그램이 장치를 직접 건드리지 못하는 것은 운영체제가 막아 주기 때문이 아니라 그 명령어가 그 특권 수준에서 실행되지 않기 때문이다

그래서 시스템콜은 함수 호출이 아니라 특권 수준을 넘는 전용 명령어다. x86-64에서는 syscall, arm64에서는 svc #0이 그 명령어다(syscall(2) man page). 프로그램이 이 명령어를 실행하면 CPU가 특권 수준을 올리고 커널이 미리 등록해 둔 진입점으로 점프한다. 어디로 점프할지는 프로그램이 정하지 못한다. 커널을 보호하는 것은 규칙이 아니라 하드웨어의 성질이다

드라이버 쪽에도 같은 이야기가 붙는다. 리눅스나 윈도우의 커널 모드 드라이버는 커널과 같은 주소 공간에서 같은 특권으로 돈다. 빠른 대신 드라이버 버그 하나가 시스템 전체를 내린다. 그래서 윈도우는 사용자 모드에서 도는 드라이버 프레임워크를 따로 제공하고, 이쪽의 이점으로 “격리된 주소 공간을 쓰기 때문에 운영체제 안정성이 높아진다”를 첫 줄에 적어 두었다(Advantages of Writing UMDF Drivers). 드라이버가 커널에 들어간다는 설명은 절반만 맞다. 들어가는 드라이버와 밖에서 도는 드라이버가 따로 있다

printf 한 줄짜리 프로그램이 시스템콜 서른네 번을 부른다

시스템콜이 실제로 몇 번 일어나는지는 눈으로 볼 수 있다. strace는 프로세스가 부르는 시스템콜을 전부 가로채 출력한다. 다음 C 코드를 컴파일해 추적해 봤다

#include <stdio.h>

int main(void) {
    printf("hello, syscall\n");
    return 0;
}

printf가 한 번 있으니 시스템콜도 하나쯤일 것 같다. 실제로 write는 한 번 나온다

$ gcc -O0 -o hello hello.c
$ strace -e trace=write ./hello
write(1, "hello, syscall\n", 15)        = 15
+++ exited with 0 +++

write(1, ...)의 1은 표준 출력의 파일 디스크립터이고, 반환값 15는 실제로 쓰인 바이트 수다. 화면에 글자를 찍는 일은 결국 커널의 write 하나로 수렴한다

전체를 세어 보면 이야기가 달라진다

$ strace -c ./hello
 51.18    0.000239         239         1           execve
 21.20    0.000099          12         8           mmap
  4.71    0.000022          22         1           write
  4.28    0.000020           6         3           brk
  4.07    0.000019           6         3           fstat
  3.85    0.000018          18         1         1 access
  3.43    0.000016           5         3           mprotect
  2.78    0.000013          13         1           munmap
  1.07    0.000005           2         2           openat
  0.64    0.000003           3         1           read
  0.64    0.000003           1         2           close
  0.64    0.000003           1         2           pread64
  0.43    0.000002           2         1           prlimit64
  0.21    0.000001           1         1           arch_prctl
  0.21    0.000001           1         1           set_tid_address
  0.21    0.000001           1         1           set_robust_list
  0.21    0.000001           1         1           getrandom
  0.21    0.000001           1         1           rseq
------ ----------- ----------- --------- --------- ----------------
100.00    0.000467          13        34         1 total

세 줄짜리 프로그램이 시스템콜 34번을 부른다. 내가 쓴 코드에서 나온 것은 write 하나뿐이고 나머지 33번은 프로그램이 시작되고 끝나는 과정에서 나온다. execve로 프로그램이 올라가고, openat으로 libc.so.6을 열고, mmap으로 그 라이브러리를 주소 공간에 붙이고, mprotect로 구역별 권한을 조정한다

이 목록은 뒤에 나올 편들의 예고편이기도 하다. mmapmprotect는 가상 메모리 편에서, execve는 프로세스 편에서, openat은 파일 시스템 편에서 다시 나온다. 커널이 관리한다는 네 가지 대상이 프로그램 하나 실행하는 0.5밀리초 안에 전부 등장한다

폰 노이만 구조가 정한 것은 CPU와 메모리 두 개다

오늘날 대부분의 컴퓨터는 폰 노이만 구조를 따른다. 핵심은 프로그램을 하드웨어로 만들지 않고 메모리에 올려서 실행한다는 것이다. ENIAC처럼 배선으로 프로그램을 만들면 프로그램이 바뀔 때마다 배선을 다시 해야 하지만, 메모리에 올리면 소프트웨어만 바꾸면 된다

이 구조가 강제하는 필수 부품은 CPU와 메모리, 그리고 둘을 잇는 버스다. 나머지는 전부 보조장치다. 메인보드는 그 부품들을 꽂는 판이고, 장치 사이 데이터 전송은 메인보드의 버스가 맡는다

CPU 안은 세 부분으로 나뉜다

구성역할
산술논리연산장치(ALU)실제 데이터 연산을 수행한다
제어장치모든 장치의 동작을 지시한다
레지스터연산에 쓸 데이터를 CPU 안에 임시 보관한다

레지스터는 변수라고 생각하면 이해가 빠르다. 5와 7을 더하는 연산은 제어장치가 메모리의 값을 레지스터로 옮기고, ALU가 두 레지스터를 더해 결과를 다시 레지스터에 넣고, 제어장치가 그 값을 메모리에 되돌리는 순서로 진행된다. CPU가 메모리 값을 직접 더하지 않고 레지스터를 거치는 이유는 메모리 계층을 다루는 편에서 나온다

메모리는 RAM과 ROM으로 나뉜다. RAM은 어느 위치를 읽든 속도가 같고, 전력이 끊기면 내용이 사라진다. ROM은 전력이 끊겨도 내용이 남는다. 이 구분은 지금도 유효하다. 다만 “ROM이니까 다시 쓸 수 없다”는 등식은 더 이상 성립하지 않고, 그 사실이 부팅 이야기의 출발점이다

부팅 코드가 담긴 칩은 다시 쓸 수 있다

ROM이라는 이름은 제조 시점에 내용을 굽고 나면 바꿀 수 없는 마스크 ROM에서 출발했다. 부팅 코드처럼 절대 바뀌면 안 되는 것을 여기 두는 것은 자연스러운 선택이었다. 지금 메인보드에서 그 자리를 차지하고 있는 것은 다시 쓸 수 있는 플래시다

NIST의 펌웨어 보안 지침은 저장 위치를 이렇게 적는다

The system BIOS is typically stored on electrically erasable programmable
read-only memory (EEPROM) or other forms of flash memory, and is modifiable
by end users.

출처는 NIST SP 800-147, BIOS Protection Guidelines이고 발행은 2011년 4월이다. 2011년 기준으로도 시스템 펌웨어는 최종 사용자가 수정할 수 있는 저장소에 있었다는 뜻이다. 메인보드 제조사가 배포하는 BIOS 업데이트 파일은 이 칩의 내용을 다시 쓰는 것이고, 리눅스 쪽 플래싱 도구인 flashrom의 문서도 SPI 플래시 칩의 쓰기 보호를 풀고 칩 전체를 다시 쓰는 절차를 설명한다. 쓰기 보호라는 개념이 필요하다는 사실 자체가 그 칩이 쓸 수 있는 물건이라는 증거다

초고에는 “부팅 코드가 ROM에 있다”는 문장을 통째로 틀렸다고 적었다가 되돌렸다. 저장소를 부르는 이름에는 ROM이 여전히 들어 있고, 위 인용문의 EEPROM도 read-only memory를 펼쳐 쓴 것이다. 성립하지 않는 것은 이름이 아니라 “한 번 쓰면 수정할 수 없다”는 성질이다. 이름과 성질을 구분하지 않으면 펌웨어 업데이트라는 일상적인 작업이 설명되지 않는다

UEFI는 부트로더를 섹터가 아니라 파일로 찾는다

“펌웨어 → MBR → 부트로더”는 레거시 BIOS 경로다. 현대 PC는 UEFI 경로로 부팅한다. 두 경로를 나란히 두면 차이가 분명하

[레거시 BIOS]
전원 → BIOS(POST) → 디스크 첫 섹터(MBR, 512바이트) 로드
                   → MBR 안의 부트 코드 실행 → 부트로더 → 커널

[UEFI]
전원 → UEFI 펌웨어(POST) → NVRAM의 BootOrder / Boot#### 변수를 읽음
                          → 지정된 파티션(ESP)의 .efi 파일을 파일 이름으로 로드
                          → 부트로더(.efi) → 커널

핵심 차이는 “부트로더를 어떻게 찾는가”다. 레거시 BIOS는 디스크 맨 앞 512바이트에 있는 코드를 조건 없이 실행한다. UEFI는 펌웨어의 비휘발성 변수(BootOrder, Boot####)에 적힌 순서대로 부팅을 시도하고, 각 항목은 디스크와 그 안의 파일 경로를 가리킨다. 파일 이름이 지정되지 않은 경우 펌웨어는 \EFI\BOOT\BOOT{아키텍처}.EFI 형태의 기본 경로를 붙이며, x64에서는 BOOTx64.EFI가 된다(UEFI Specification 2.11, 3. Boot Manager). USB로 리눅스를 설치할 때 EFI/BOOT/BOOTX64.EFI라는 경로가 보이는 이유가 이것이다

부트로더가 512바이트 섹터에서 파일로 옮겨 가면서 따라온 결과가 몇 가지 있다

  • 파티션 방식이 GPT로 바뀐다. MBR은 논리 섹터 번호에 32비트만 쓸 수 있어 512바이트 섹터 기준 약 2.2TB가 상한이다. GPT는 64비트를 써서 이론상 9.4ZB까지 표현한다(Windows support for hard disks exceeding 2 TB). 4TB 디스크에 윈도우를 설치하려면 GPT여야 하고, GPT로 부팅하려면 UEFI여야 한다
  • 부트로더에 서명을 검증할 수 있다. 512바이트 바이너리에는 서명을 붙일 자리가 없지만 파일에는 붙일 수 있다. Secure Boot가 성립하는 전제이고, Windows 11이 “UEFI, Secure Boot capable”을 요구하는 이유다
  • 레거시 경로는 호환 모듈(CSM)로만 남아 있었고 그마저 사라졌다. Intel의 발표 자료는 “플랫폼은 엄격하게 UEFI Class 3가 된다”, “16비트 OpROM 없음”, “‘UEFI 끄기'(CSM ON)에 의존하는 모든 고객 절차가 깨진다”고 적는다(Last Mile Barriers to Removing Legacy BIOS)

MBR 경로는 2020년 이전 하드웨어에서는 정확했고, 그 이후 출하된 클라이언트 PC에서는 재현되지 않는다. 512바이트 안에 부트 코드를 우겨넣던 제약이 어떤 문제를 만들었는지 이해하는 데는 MBR 이야기가 여전히 유용하다. 다만 “부팅 과정을 설명해 보라”는 질문에 MBR까지만 답하면 5년 넘게 지난 기준으로 답하는 것이 된다

UEFI 부팅은 두 대 모두에서 확인하지 못했다

리눅스는 UEFI로 부팅했을 때 /sys/firmware/efi 디렉터리를 만든다. 이 글을 쓰면서 쓸 수 있었던 두 대에서 모두 이 디렉터리가 없었다

$ ls /sys/firmware/
acpi  memmap

x86-64 쪽은 하이퍼바이저가 커널 이미지를 직접 올리는 방식이라 펌웨어 단계 자체가 없고, arm64 쪽은 디바이스 트리로 하드웨어 정보를 넘겨받아 /sys/firmwaredevicetreefdt만 있다. 둘 다 UEFI 경로를 타지 않는다. 그래서 위에서 설명한 ESP와 BootOrder 변수는 스펙 문서와 벤더 문서로만 확인했고, efibootmgr로 실제 부트 항목을 뽑아 코드 블록에 넣지는 못했다. UEFI로 부팅하는 물리 장비에서는 efibootmgr -v로 같은 내용을 직접 볼 수 있다

인터럽트 수는 전송량이 아니라 요청 수를 따라간다

CPU가 입출력을 시키는 방법은 두 가지다. 폴링은 CPU가 “끝났냐”고 주기적으로 물어보는 방식이고, 인터럽트는 장치가 끝났을 때 CPU에게 신호를 주는 방식이다. 인터럽트가 폴링보다 낫다는 설명은 보통 여기서 끝난다. 숫자가 없으면 인터럽트가 공짜처럼 읽힌다

리눅스는 /proc/interrupts에 인터럽트가 몇 번 들어왔는지를 장치별로 세어 둔다. 디스크에 쓰기를 시키고 그 전후를 비교하면 인터럽트 발생 횟수를 직접 잴 수 있다. 총 전송량을 64MiB로 고정하고 요청 크기만 바꿔 봤다. oflag=direct는 페이지 캐시를 우회해 요청이 실제로 블록 장치까지 내려가게 한다

run() {
  local bs=$1 cnt=$2
  local b=$(awk '/virtio1-req.0/{print $2+$3}' /proc/interrupts)
  local t0=$(date +%s.%N)
  dd if=/dev/zero of=/tmp/iotest bs=$bs count=$cnt oflag=direct status=none
  local t1=$(date +%s.%N)
  local a=$(awk '/virtio1-req.0/{print $2+$3}' /proc/interrupts)
  printf "bs=%-5s count=%-6s  interrupts=%-6s  elapsed=%.2fs\n" \
         "$bs" "$cnt" "$((a-b))" "$(echo "$t1-$t0"|bc)"
  rm -f /tmp/iotest
}
run 1M 64
run 64K 1024
run 4K 16384

결과는 이렇다

bs=1M    count=64      interrupts=67      elapsed=0.05s
bs=64K   count=1024    interrupts=1024    elapsed=0.08s
bs=4K    count=16384   interrupts=16384   elapsed=0.91s

세 줄 모두 같은 64MiB를 썼다. 인터럽트 수는 전송한 바이트가 아니라 요청 개수를 거의 그대로 따라갔고, 요청을 256배로 쪼갠 마지막 줄은 시간이 18배 늘었다. 인터럽트 한 번은 실행 중이던 코드를 멈추고 레지스터를 저장하고 처리 함수로 점프했다가 되돌아오는 비용이다. 그 비용이 요청 하나당 한 번씩 붙는다

이 숫자에는 범위가 있다. 측정한 장치는 물리 디스크가 아니라 가상화된 virtio 블록 장치이고, CPU는 2코어짜리 가상 머신이다. 절대값은 하드웨어마다 다르다. 세 줄 사이의 관계 — 인터럽트 수가 요청 수를 따라가고 시간도 같이 늘어난다 — 가 이 측정에서 확인한 것이다

그래서 인터럽트가 폴링보다 항상 낫다는 결론은 성립하지 않는다. 요청이 아주 잦은 장치에서는 인터럽트가 쏟아져 CPU가 인터럽트 처리만 하게 된다. 리눅스 네트워크 스택이 쓰는 NAPI가 정확히 이 문제에 대한 답이다. 패킷 하나마다 인터럽트를 받는 대신, 인터럽트가 한 번 오면 드라이버가 napi_schedule()을 부르고 그 장치의 인터럽트를 즉시 마스킹한 뒤 폴링으로 전환해 정해진 budget만큼 처리하고, 다 처리한 뒤에야 인터럽트를 다시 푼다(NAPI, The Linux Kernel documentation)

        패킷 도착 → 인터럽트 → 인터럽트 마스킹 → 폴링으로 budget만큼 처리
                                    ▲                     │
                                    └──── 인터럽트 재활성 ◀───┘

인터럽트로 시작해서 폴링으로 처리한다. 폴링을 성능이 나쁜 옛날 방식으로만 기억하고 있으면 이 설계가 이상해 보인다. 폴링이 나쁜 것이 아니라 요청 빈도에 따라 유리한 쪽이 뒤집힌다

마무리

개론 첫 장의 개념은 대부분 지금도 그대로 맞다. 폰 노이만 구조도, 커널과 시스템콜의 관계도, 인터럽트가 무엇인지도 바뀌지 않았다. 바뀐 것은 그 개념이 실제 하드웨어에 구현된 형태다. BIOS는 이름만 남고 UEFI로 대체됐고, MBR은 GPT로 밀려났고, ROM은 다시 쓸 수 있는 플래시가 됐다

개념과 구현을 분리해서 외워야 하는 이유가 여기 있다. 개념은 40년을 버티고 구현은 5년 만에 바뀐다


다음 편

다음 편은 프로그램이 프로세스가 되는 과정과 스레드를 다룬다. fork가 부모 프로세스에서 실제로 무엇을 복사하는지, 좀비 프로세스가 무엇을 차지하는지, 크롬과 파이어폭스가 탭을 프로세스에 어떻게 배치하는지가 주제다


출처와 범위

이 시리즈는 인프런의 그림으로 쉽게 배우는 운영체제(감자님) 강의를 따라 정리한 것이고, 강의 전체를 일곱 편으로 나눈 첫 편이다. 개념 구성과 설명 순서의 출처는 강의이며, 값과 시점을 말하는 문장은 본문에 건 공식 문서와 스펙으로 확인해 지금 기준으로 맞췄다. 코드 블록에 실은 strace, /proc/interrupts, dd 출력은 x86-64 리눅스 가상 머신에서 직접 실행한 결과 그대로이고, 물리 장비에서 다시 재면 절대값은 달라진다

참고 자료