
프로그램이 디스크 읽기를 요청했습니다. 디스크는 CPU보다 수만 배 느립니다. 요청한 프로그램은 그동안 무엇을 해야할까요. 답이 두 가지입니다.
차이는 완료된 후에 제어가 넘어가는지, 그 하나뿐입니다. 자주 오해하는 지점이 하나 있는데, 두 경우 모두 입출력 완료를 알리는 것은 인터럽트입니다. 비동기식이라고 CPU가 장치를 계속 들여다보는 것이 아닙니다.
한 가지 덧붙이면, 동기식이라 해서 CPU까지 노는 것은 아닙니다. 요청한 프로그램만 멈출 뿐, 운영체제는 그 사이 다른 프로그램에 CPU를 줍니다.
디스크 읽기 10ms, 읽기 결과와 무관한 계산 5ms가 있는 프로그램입니다.
읽기와 계산이 서로 독립적일 때 5ms를 겹쳐서 벌었습니다. 반대로 계산이 읽기 결과를 써야 한다면 비동기식으로 짜도 결국 기다려야 하므로 이득이 없습니다.
| 구분 | 동기식 입출력 | 비동기식 입출력 |
|---|---|---|
| 제어 복귀 시점 | 입출력 완료 후 | 요청 직후 |
| 요청한 프로그램 | 완료까지 멈춤 | 다른 일 수행 가능 |
| 완료 통보 방식 | 인터럽트 | 인터럽트 |
| 유리한 상황 | 결과가 바로 필요할 때 | 결과와 무관한 일이 남아 있을 때 |
| 코드 복잡도 | 낮음 | 완료 확인 처리가 필요해 높음 |
CPU는 device controller의 register와 local buffer에 어떻게 접근합니까. 방법이 둘입니다.
하나는 입출력 전용 명령을 따로 두는 것입니다. 다른 하나가 memory mapped I/O로, controller의 register와 buffer에 메모리 주소를 할당해 두는 방식입니다. CPU가 보기에는 그냥 메모리 주소이므로 평소 쓰던 메모리 접근 명령을 그대로 씁니다. 특정 주소에 값을 쓰면 그것이 장치에 대한 명령이 됩니다.
주의할 점은 그 주소가 메인 메모리의 어딘가가 아니라는 것입니다. 주소 공간의 일부를 장치에 떼어준 것입니다.
저장장치를 왜 한 종류로 통일하지 못할까요. 빠른 것은 비싸고 작고, 싼 것은 느리고 크기 때문입니다. 그래서 여러 종류를 쌓아 씁니다.
레지스터 → 캐시 메모리 → 메인 메모리 → 마그네틱 디스크 → 옵티컬 디스크 → 마그네틱 테이프 순으로, 위로 갈수록 빠르고(speed) 단위 용량당 비싸며(cost) 용량이 작습니다.
메인 메모리와 마그네틱 디스크 사이에 중요한 경계가 하나 있습니다.
| 구분 | primary (주기억장치) | secondary (보조기억장치) |
|---|---|---|
| 해당 장치 | 레지스터, 캐시 메모리, 메인 메모리 | 마그네틱 디스크, 옵티컬 디스크, 테이프 |
| CPU 직접 접근 | 가능 | 불가능 (메모리로 올려야 함) |
| 접근 단위 | 바이트 | 섹터 등 블록 |
| 휘발성(volatility) | 휘발성 | 비휘발성 |
CPU가 직접 접근할 수 있는지가 경계의 기준입니다.
계층을 쌓으면 자연스럽게 따라오는 전략이 캐싱(caching)입니다. 아래 계층의 데이터 중 자주 쓰는 것을 위쪽 빠른 공간에 복사해 두고 재사용하는 것입니다.
캐싱은 특정 부품의 이름이 아니라 계층 사이에서 반복되는 패턴입니다. 레지스터는 메인 메모리에 대한 캐시이고, 캐시 메모리도 메인 메모리에 대한 캐시이며, 메인 메모리는 디스크에 대한 캐시로 동작합니다. 빠른 공간은 항상 좁으므로 무엇을 남기고 무엇을 내보낼지 정해야 합니다.
실행 파일은 평소 파일 시스템에 얌전히 있습니다. 실행하는 순간 그 프로그램만의 주소 공간, 즉 가상 메모리(virtual memory)가 만들어집니다. 주소 공간은 code, data, stack으로 나뉩니다.
이때 주소 공간 전체를 물리 메모리에 올리지는 않습니다. 당장 필요한 부분만 올리고 나머지는 디스크의 swap area에 둡니다. swap area도 디스크지만 파일 시스템과 목적이 다릅니다. 파일 시스템은 전원이 꺼져도 내용을 보존하려는 공간이고, swap area는 메모리가 좁아서 잠시 밀어둔 공간이라 프로그램이 끝나면 의미가 없습니다.
커널도 프로그램이므로 똑같이 주소 공간을 가집니다. 커널 주소 공간 역시 code, data, stack으로 구성되며, 항상 물리 메모리에 올라가 있습니다.

프로그램이 호출하는 함수는 어디에 있느냐에 따라 성격이 다릅니다.
| 구분 | 위치 | 호출 방식 |
|---|---|---|
| 사용자 정의 함수 | 내 프로세스의 code 영역 | 같은 주소 공간 안에서 점프 |
| 라이브러리 함수 | 내 프로세스의 code 영역 | 같은 주소 공간 안에서 점프 |
| 커널 함수 | 커널 주소 공간 | 모드 전환을 거쳐 커널로 진입 |
라이브러리 함수는 내가 짜지 않았을 뿐, 실행 파일에 포함되어 내 주소 공간에 들어옵니다. 반면 커널 함수는 남의 주소 공간에 있어 그냥 점프할 수 없습니다.