학습 목표
장치 컨트롤러와 레지스터·로컬 버퍼가 CPU·메모리와 어떻게 연결되는지 설명할 수 있다.
I/O 소프트웨어 계층(응용 → 시스템 콜 → 커널 I/O → 드라이버)을 설명할 수 있다.
폴링·인터럽트·DMA 방식의 차이와 OS가 왜 DMA를 쓰는지 설명할 수 있다.
블록·문자 장치와 장치 드라이버 역할을 구분할 수 있다.
문제 상황
read()한 번에 디스크가 4KB를 읽는데 CPU는 다른 프로세스를 돌리고 있다- CPU가 섹터마다 바이트를 옮기지 않는 이유는?
- NIC 패킷이 오면 인터럽트가 수천 번/초 — CPU 부하
- NAPI, 인터럽트 coalescing이 왜 필요한지
- USB 키보드와 NVMe SSD가 같은
read/writeAPI인데 내부는 전혀 다르다- 드라이버가 장치별 차이를 흡수
dmesg에 드라이버 로드·DMA zone 메시지- 커널이 장치를 어떻게 붙이는지
디스크 스케줄링은 헤드 이동 순서였다. 이번엔 OS가 장치 전체를 어떻게 다루는지 — I/O 하드웨어·소프트웨어 스택이다.
1. I/O 하드웨어
CPU·메모리와 디스크·NIC·키보드 같은 장치(device) 는 속도·인터페이스가 다르다. 사이에 장치 컨트롤러(device controller) 가 있다.

| 구성 | 역할 |
|---|---|
| 장치 | 실제 물리 동작 — 플래터 회전, 패킷 송수신 |
| 컨트롤러 | 장치 제어 회로 + 레지스터 + 로컬 버퍼 |
| 레지스터 | 상태(status), 명령(command), 데이터(data) — CPU가 MMIO로 접근 |
| 버스 | 주소·데이터·제어선 — PCI, USB, SATA 등 |
- CPU는 장치를 직접 돌리지 않고 컨트롤러 레지스터에 명령을 쓴다
- 컨트롤러가 장치를 구동하고, 데이터는 로컬 버퍼 또는 DMA로 메모리와 주고받는다
- 포트 I/O vs 메모리 매핑 I/O(MMIO) — x86 레거시 vs 현대 PCI 장치
2. I/O 소프트웨어 계층
응용은 장치 레지스터를 모른다. OS가 계층으로 추상화한다.

| 계층 | 예 | 역할 |
|---|---|---|
| 사용자 | read(), write(), ioctl() |
파일·소켓 API |
| 시스템 콜 | sys_read |
유저 → 커널 전환, 권한 검사 |
| 커널 I/O | VFS, block layer, socket layer | 공통 큐·캐시·스케줄링 |
| 장치 드라이버 | nvme, e1000, xhci |
장치별 명령·인터럽트·DMA 설정 |
| 하드웨어 | 컨트롤러 + 장치 | 실제 전송 |
- 인터럽트 핸들러는 드라이버 안에서 동작 — 완료 비트 확인, 대기 프로세스 깨움
- 블록 장치(디스크): 고정 크기 블록 단위 — block layer가 요청 병합·스케줄
- 문자 장치(키보드, 시리얼): 바이트 스트림 — 순차 접근
3. 폴링 · 인터럽트 · DMA
데이터를 옮기는 세 가지 패턴. 앞서 DMA 개요를 봤다면, 여기서는 OS 관점으로 비교한다.

| 방식 | CPU 역할 | 장점 | 단점 |
|---|---|---|---|
| 폴링 | 완료될 때까지 상태 레지스터 반복 조회 | 구현 단순 | CPU 낭비, 다른 작업 불가 |
| 인터럽트 | 요청 후 다른 일 → 완료 시 IRQ로 깨움 | CPU 활용 | IRQ 빈번 시 오버헤드 |
| DMA | 버퍼 주소·길이만 설정 → 컨트롤러가 메모리↔장치 전송 | 대용량에 유리 | 설정·캐시 일관성 관리 필요 |
전형적 흐름 (DMA + 인터럽트)
- 드라이버가 DMA 컨트롤러에 소스·목적지·바이트 수 프로그래밍
- CPU는 다른 프로세스 실행 (또는 sleep)
- 전송 완료 → 하드웨어 인터럽트 → 드라이버 핸들러가 I/O 완료 표시
read()를 호출했던 프로세스를 ready 로 올려 스케줄
- PIO(Programmed I/O): CPU가 데이터 레지스터로 바이트 단위 복사 — 소량·레거시
- 버퍼 캐시 일관성: DMA 전후
dma_map_single등으로 CPU 캐시와 메모리 동기화
4. 장치 드라이버
드라이버는 커널 모듈(또는 내장)로, 특정 컨트롤러를 다룬다.
| 개념 | 설명 |
|---|---|
| 초기화 | 부팅·modprobe 시 레지스터 탐색, IRQ·DMA 채널 할당 |
| open/read/write | VFS·block layer가 호출하는 ops 테이블 |
| 인터럽트 핸들러 | top half: 최소 작업, bottom half: workqueue로 지연 처리 |
| hotplug | USB·PCI 장치 삽입 시 udev가 노드(/dev/nvme0n1) 생성 |
- Linux
/sys,/dev: 장치 노드·드라이버 바인딩 확인 - 유저 공간 드라이버: 일부 장치는 커널이 최소 인터페이스만 제공하고 데몬이 처리 (예: 일부 프린터 스택)
- NVMe: 큐 깊이 수천, MSI-X 인터럽트 — 디스크 스케줄링과 맞물림
5. I/O 요청 한 번의 여정
read(fd, buf, 4096) — 블록 장치 예시.
앱 read() → libc → syscall trap
→ VFS가 fd → inode → block device
→ block layer: 페이지 캐시 hit? → 있으면 복사 후 return
→ miss: bio 요청 큐 → I/O 스케줄러(디스크 편) → 드라이버 submit
→ DMA 설정 → 전송 → IRQ → wake process
| 단계 | 누가 | 비고 |
|---|---|---|
| 캐시 hit | page cache | 디스크 안 감 — 이후 버퍼링 편 |
| 큐잉 | block layer + elevator | 디스크 스케줄링 알고리즘 적용 |
| 완료 | 인터럽트 + 드라이버 | iowait 시간에 포함 |
- 동기 vs 비동기 I/O:
read는 보통 블로킹 —io_uring,aio는 완료를 나중에 통지 - spurious interrupt, IRQ affinity: 멀티코어에서 NIC·NVMe IRQ를 코어에 묶어 캐시 locality
6. 정리
- I/O = 컨트롤러 + 레지스터 + 버스 위에서 동작하는 장치
- OS는 시스템 콜 → VFS/block → 드라이버로 장치 차이를 숨긴다
- 폴링은 CPU 낭비, 인터럽트로 완료 알림, DMA로 대량 전송
- 드라이버가 IRQ·DMA·장치 명령을 담당 — 블록/문자 장치 구분
- 다음: 버퍼링과 캐싱, 페이지 캐시·블록 I/O
다음에 다룰 것
- 버퍼링과 캐싱
- 페이지 캐시, 블록 I/O,
sync
해당 내용은 Operating System Concepts, 10/E (Avraham Silberschatz, Peter Baer Galvin, Greg Gagne) 의 내용을 기반으로 합니다.