12. I/O Devices

  • Haram Lee
  • 2026-05-25
  • studies / 26-1 / operating-systems

I/O Devices

Reading

  • 이 단원부터는 운영체제의 세 번째 큰 주제인 persistence, 즉 영속성을 다룬다.

  • 앞에서는 주로 다음 내용을 배웠다.

    • CPU virtualization

      • 여러 process가 CPU를 나누어 쓰는 것처럼 보이게 함.
    • Memory virtualization

      • 각 process가 자기만의 address space를 가진 것처럼 보이게 함.
    • Concurrency

      • 여러 thread가 동시에 실행될 때 shared data를 안전하게 다루는 법을 봄.
  • 이제부터는 데이터를 일시적으로 메모리에 두는 것이 아니라, 전원이 꺼져도 유지되는 저장 장치와 파일 시스템을 다룬다.

  • Persistence 파트의 첫 관문은 I/O device다.

  • I/O가 중요한 이유

    • input이 없으면 프로그램은 매번 같은 결과만 만들 수 있다.
    • output이 없으면 프로그램을 실행한 의미가 사라진다.
    • 따라서 컴퓨터 시스템이 유용하려면 input과 output이 모두 필요하다.
  • 이 단원의 핵심 질문은 다음과 같다.

    • OS는 I/O device를 시스템에 어떻게 통합하는가?
    • OS는 device와 어떤 방식으로 통신하는가?
    • device가 느릴 때 CPU 낭비를 어떻게 줄이는가?
    • 많은 종류의 device를 OS 안에서 어떻게 일반적으로 다루는가?

Big Picture

  • I/O device는 CPU나 memory와 달리 종류가 매우 다양하다.

    • disk
    • keyboard
    • mouse
    • network card
    • graphics card
    • USB device
    • NVMe storage
  • 각 device는 속도, 동작 방식, 데이터 전송 방식, 오류 처리 방식이 다르다.

  • OS 입장에서는 모든 device의 세부사항을 직접 다 알고 싶지 않다.

  • 그래서 OS는 다음 두 가지를 동시에 해결해야 한다.

    • device마다 다른 세부 동작은 감추기
    • application과 file system에는 일반적인 interface 제공하기
  • 이 단원의 핵심 문장

    • OS는 device의 복잡한 세부사항을 device driver에 숨기고, interrupt와 DMA를 이용해 I/O를 효율적으로 처리한다.

System Architecture

  • 컴퓨터 시스템은 보통 계층적인 bus 구조를 가진다.

  • 대표적인 구조

    • CPU
    • Memory
    • Memory bus
    • General I/O bus
    • Peripheral I/O bus
  • memory bus

    • CPU와 main memory를 연결한다.
    • 가장 빠른 쪽에 속한다.
    • 빠른 대신 길게 만들기 어렵고, 많은 장치를 붙이기 어렵다.
  • general I/O bus

    • 상대적으로 고성능 I/O device를 연결한다.
    • 예를 들어 PCI, PCIe 계열이 여기에 해당한다.
    • graphics card, network card, 고성능 storage 등이 연결될 수 있다.
  • peripheral I/O bus

    • 더 느린 장치들을 연결한다.
    • 예를 들어 SATA, USB, SCSI 등이 있다.
    • disk, keyboard, mouse 같은 장치가 연결된다.
  • 왜 계층 구조를 쓰는가?

    • 빠른 bus는 물리적으로 짧아야 한다.
    • 빠른 bus는 설계 비용이 크다.
    • 모든 device를 CPU 가까이에 붙이면 비용과 구조가 너무 복잡해진다.
    • 고성능 device는 CPU 가까이에 두고, 느린 device는 더 아래쪽 bus에 둔다.
  • 핵심

    • 빠른 장치는 CPU 가까이, 느린 장치는 더 멀리 둔다.
    • 성능, 비용, 물리적 제약 때문에 bus는 계층적으로 구성된다.

Modern System Architecture

  • 현대 시스템은 고전적인 단일 bus 구조보다 더 복잡하다.

  • CPU는 memory와 직접 빠르게 연결된다.

  • CPU는 graphics card 같은 고성능 장치와도 빠른 연결을 가질 수 있다.

  • CPU는 I/O chip과 연결되고, I/O chip 아래에 여러 device가 붙는다.

  • 예시

    • disk는 SATA/eSATA 같은 storage interface로 연결될 수 있다.
    • keyboard, mouse는 USB로 연결될 수 있다.
    • network interface나 NVMe storage는 PCIe로 연결될 수 있다.
  • 핵심

    • 현대 시스템도 기본 아이디어는 같다.
    • 성능이 중요한 장치는 더 빠른 interconnect에 연결된다.
    • 느린 장치는 더 저렴하고 범용적인 bus에 연결된다.

A Canonical Device

  • OSTEP에서는 실제 특정 장치가 아니라, 이해를 돕기 위한 canonical device를 사용한다.
  • 모든 device는 크게 두 부분으로 볼 수 있다.

1. Interface

  • device가 외부에 제공하는 하드웨어 API다.

  • OS는 이 interface를 통해 device를 제어한다.

  • 대표적으로 다음 register들이 있다.

    • Status register
    • Command register
    • Data register

2. Internals

  • device 내부 구현이다.

  • device마다 다르다.

  • 단순한 device는 몇 개의 chip만 가질 수 있다.

  • 복잡한 device는 다음을 가질 수 있다.

    • micro-controller
    • internal memory
    • device-specific chip
    • firmware
  • 예를 들어 RAID controller 같은 장치는 내부적으로 상당히 복잡한 firmware를 가질 수 있다.


Device Registers

  • Status register

    • device의 현재 상태를 알려준다.
    • 예를 들어 busy인지, ready인지, error가 있는지 확인할 수 있다.
  • Command register

    • OS가 device에게 어떤 일을 하라고 명령하는 데 사용한다.
    • 예를 들어 read, write 같은 명령을 전달한다.
  • Data register

    • OS와 device 사이에서 데이터를 주고받는 데 사용한다.
    • 작은 device에서는 직접 data register로 데이터를 주고받을 수 있다.
    • 큰 데이터 전송에서는 DMA 같은 방식이 더 효율적이다.
  • 핵심

    • OS는 device register를 읽고 쓰면서 device를 제어한다.

The Canonical Protocol

  • OS가 device와 상호작용하는 가장 단순한 protocol은 다음과 같다.
text
while (STATUS == BUSY)
    ; // device가 준비될 때까지 기다림

write data to DATA register

write command to COMMAND register

while (STATUS == BUSY)
    ; // 작업이 끝날 때까지 기다림
  • 단계별 의미

    • 1단계

      • status register를 계속 확인한다.
      • device가 busy가 아니고 준비될 때까지 기다린다.
    • 2단계

      • data register에 데이터를 쓴다.
      • 예를 들어 disk write라면 쓸 데이터를 device 쪽으로 보낸다.
    • 3단계

      • command register에 명령을 쓴다.
      • 이때 device는 실제 작업을 시작한다.
    • 4단계

      • 다시 status register를 계속 확인한다.
      • device 작업이 끝날 때까지 기다린다.
  • 이 방식의 장점

    • 단순하다.
    • 이해하기 쉽다.
    • 작은 device나 간단한 상황에서는 작동한다.
  • 이 방식의 문제점

    • CPU가 계속 device 상태를 확인해야 한다.
    • device가 느리면 CPU 시간이 낭비된다.
    • OS가 다른 process를 실행할 수 있는 시간까지 polling에 써버릴 수 있다.

Polling

  • Polling은 OS가 device 상태를 반복적으로 확인하는 방식이다.

  • 예시

    • “device 끝났어?”
    • “아직 busy야?”
    • “이제 ready야?”
    • 이런 식으로 계속 묻는 방식이다.
  • 장점

    • 구현이 단순하다.
    • device가 매우 빠르면 오히려 괜찮을 수 있다.
    • 작업이 거의 바로 끝나는 장치라면 interrupt보다 polling이 더 싸게 먹힐 수 있다.
  • 단점

    • device가 느리면 CPU를 낭비한다.
    • CPU가 다른 일을 하지 못하고 기다리는 데 시간을 쓴다.
    • 특히 disk 같은 느린 장치에서는 비효율적이다.
  • 핵심

    • polling은 단순하지만, 느린 device에서는 CPU utilization을 떨어뜨린다.

Interrupt

  • polling의 CPU 낭비를 줄이기 위해 사용하는 방식이 interrupt다.

  • interrupt 방식

    • OS가 device에게 I/O 요청을 보낸다.
    • 요청한 process는 sleep 상태로 들어간다.
    • OS는 CPU를 다른 runnable process에게 넘긴다.
    • device가 작업을 끝내면 hardware interrupt를 발생시킨다.
    • CPU는 OS의 interrupt handler 또는 ISR로 진입한다.
    • interrupt handler는 I/O 완료 처리를 한다.
    • 기다리던 process를 깨운다.
  • 장점

    • CPU와 I/O를 overlap할 수 있다.
    • device가 작업하는 동안 CPU는 다른 process를 실행할 수 있다.
    • 느린 device에서 CPU utilization이 좋아진다.
  • 예시

    • Process 1이 disk read 요청을 보냄.
    • disk가 데이터를 읽는 동안 OS는 Process 2를 실행함.
    • disk 작업이 끝나면 interrupt 발생.
    • OS가 Process 1을 깨움.
  • 핵심

    • interrupt는 CPU가 device를 계속 기다리지 않게 해준다.

Interrupt가 항상 좋은 것은 아님

  • interrupt는 polling보다 항상 좋은 방식이 아니다.

  • device가 매우 빠른 경우

    • polling 한 번이면 작업 완료를 확인할 수 있다.

    • 그런데 interrupt를 쓰면 다음 비용이 생긴다.

      • context switch
      • interrupt handling
      • 다시 원래 process로 돌아가는 비용
    • 이런 경우 interrupt가 오히려 느릴 수 있다.

  • device가 느린 경우

    • interrupt가 유리하다.
    • CPU가 기다리지 않고 다른 일을 할 수 있기 때문이다.
  • device 속도가 애매하거나 상황에 따라 달라지는 경우

    • hybrid 방식이 좋을 수 있다.
    • 처음에는 잠깐 polling한다.
    • 그래도 안 끝나면 interrupt 방식으로 전환한다.
  • network처럼 interrupt가 너무 많이 발생하는 경우

    • interrupt flood가 생길 수 있다.
    • OS가 user process를 실행하지 못하고 interrupt 처리만 하게 될 수 있다.
    • 이런 상태를 livelock이라고 볼 수 있다.
  • interrupt coalescing

    • device가 interrupt를 바로 보내지 않고 잠깐 기다린다.
    • 여러 I/O 완료 interrupt를 하나로 묶어서 CPU에게 보낸다.
    • interrupt 처리 overhead를 줄일 수 있다.
    • 대신 너무 오래 기다리면 latency가 늘어난다.
  • 핵심

    • 느린 device에는 interrupt가 좋고, 빠른 device에는 polling이 더 나을 수도 있다.
    • 실제 시스템은 polling과 interrupt를 섞어 쓰기도 한다.

Programmed I/O, PIO

  • canonical protocol에서는 CPU가 직접 data register에 데이터를 넣거나 꺼낸다.

  • 이렇게 CPU가 직접 데이터를 옮기는 방식을 Programmed I/O, 줄여서 PIO라고 한다.

  • PIO의 문제

    • 큰 데이터를 옮길 때 CPU가 너무 많은 시간을 쓴다.
    • CPU는 단순히 memory에서 device로 데이터를 복사하는 일을 반복한다.
    • 이 시간 동안 다른 process를 실행하지 못한다.
  • 예시

    • disk에 4KB block을 쓰려고 한다.
    • CPU가 word 단위로 data register에 계속 데이터를 써야 한다.
    • device 자체 작업뿐 아니라 데이터 이동에도 CPU 시간이 많이 소모된다.
  • 핵심

    • PIO는 단순하지만 큰 데이터 전송에서는 CPU를 낭비한다.

DMA

  • PIO의 비효율을 해결하는 방식이 DMA, 즉 Direct Memory Access다.

  • DMA는 CPU 대신 memory와 device 사이의 데이터 이동을 담당하는 장치 또는 기능이다.

  • DMA 동작 방식

    • OS가 DMA engine에게 필요한 정보를 준다.

      • 데이터가 memory 어디에 있는지
      • 얼마나 많은 데이터를 옮길지
      • 어느 device로 보낼지 또는 어느 device에서 받을지
    • DMA engine이 직접 memory와 device 사이에서 데이터를 전송한다.

    • CPU는 그동안 다른 일을 할 수 있다.

    • DMA 전송이 끝나면 DMA controller가 interrupt를 발생시킨다.

    • OS는 interrupt를 받고 I/O 완료를 처리한다.

  • 장점

    • CPU가 직접 데이터를 복사하지 않아도 된다.
    • CPU utilization이 좋아진다.
    • 큰 데이터 전송에 특히 유리하다.
  • 핵심

    • interrupt는 기다리는 비용을 줄이고, DMA는 데이터 이동 비용을 줄인다.
    • 둘 다 I/O 효율을 높이기 위한 중요한 기법이다.

Methods of Device Interaction

  • OS가 device register와 통신하는 방식은 크게 두 가지다.

1. Explicit I/O Instructions

  • CPU가 device와 통신하기 위한 별도 instruction을 제공하는 방식이다.

  • 예를 들어 x86에는 in, out instruction이 있다.

  • OS는 이 instruction을 사용해 특정 port나 device register에 접근한다.

  • 보통 privileged instruction이다.

  • 이유

    • user program이 마음대로 disk나 device를 제어하면 시스템이 망가질 수 있다.
    • 따라서 device 제어는 OS만 할 수 있어야 한다.

2. Memory-Mapped I/O

  • device register를 memory address처럼 보이게 만드는 방식이다.

  • OS는 일반적인 load/store instruction으로 device register에 접근한다.

  • hardware는 해당 address에 대한 load/store를 main memory가 아니라 device로 전달한다.

  • 장점

    • 새로운 I/O instruction이 필요 없다.
    • memory 접근 방식과 비슷하게 device를 다룰 수 있다.
  • 둘 중 하나가 절대적으로 우월한 것은 아니다.

  • 실제 시스템에서는 두 방식이 모두 사용된다.

  • 핵심

    • explicit I/O instruction은 전용 명령으로 device에 접근한다.
    • memory-mapped I/O는 device register를 memory처럼 접근한다.

Device Driver

  • device는 종류마다 너무 다르다.

  • OS 전체가 모든 device의 세부 동작을 직접 알면 구조가 너무 복잡해진다.

  • 이를 해결하기 위해 사용하는 것이 device driver다.

  • device driver

    • 특정 device의 세부 동작을 아는 OS 내부 소프트웨어다.
    • device-specific protocol을 처리한다.
    • 상위 OS layer에는 일반적인 interface를 제공한다.
  • 예시

    • file system은 SCSI disk인지, IDE disk인지, USB storage인지 자세히 몰라도 된다.
    • file system은 generic block layer에 block read/write 요청을 보낸다.
    • generic block layer는 해당 요청을 적절한 device driver로 전달한다.
    • device driver가 실제 device-specific 명령을 수행한다.
  • Linux file system stack의 대략적인 흐름

    • Application

    • POSIX API

      • open
      • read
      • write
      • close
    • File System

    • Generic Block Layer

    • Device Driver

    • Actual Device

  • 핵심

    • device driver는 device의 구체적인 차이를 숨기는 abstraction layer다.
    • 덕분에 OS의 나머지 부분은 device-neutral하게 설계될 수 있다.

Device Driver의 장점과 단점

  • 장점

    • OS 상위 계층을 단순하게 유지할 수 있다.
    • file system이나 application은 device 종류를 몰라도 된다.
    • 새로운 device가 추가되어도 driver만 추가하면 된다.
  • 단점

    • generic interface 때문에 device의 특별한 기능을 제대로 활용하지 못할 수 있다.
    • 예를 들어 어떤 device는 rich error reporting을 제공해도, 상위 계층에는 단순한 generic error만 전달될 수 있다.
    • device driver가 kernel code의 큰 비중을 차지한다.
    • driver는 버그가 많을 수 있고, kernel crash의 원인이 될 수 있다.
  • 핵심

    • driver는 abstraction을 제공하지만, abstraction 때문에 정보가 손실되거나 성능/기능이 제한될 수 있다.

Raw Interface

  • 대부분의 application은 file abstraction을 통해 storage를 사용한다.

  • 하지만 일부 프로그램은 file system을 거치지 않고 block device에 직접 접근해야 할 수 있다.

  • 예시

    • file-system checker
    • disk defragmentation tool
    • low-level storage management tool
  • 이런 경우 raw interface가 사용된다.

  • raw interface

    • file abstraction을 우회한다.
    • device의 block을 직접 read/write할 수 있게 한다.
  • 핵심

    • 일반 application은 file system을 통해 device를 사용한다.
    • 하지만 특수한 system tool은 raw block access가 필요할 수 있다.

Case Study: IDE Disk Driver

  • OSTEP은 실제 device 예시로 간단한 IDE disk driver를 다룬다.

  • IDE disk driver는 여러 register와 protocol을 사용한다.

  • 주요 register 예시

    • control register
    • data port
    • error register
    • sector count
    • LBA 관련 register
    • command/status register
  • status register는 다음 같은 상태를 알려준다.

    • BUSY
    • READY
    • FAULT
    • DRQ
    • ERROR
  • IDE disk 요청 처리 흐름

    • drive가 준비될 때까지 기다린다.
    • sector count를 설정한다.
    • 접근할 LBA를 설정한다.
    • drive number를 설정한다.
    • command register에 READ 또는 WRITE 명령을 쓴다.
    • write의 경우 data port에 데이터를 전달한다.
    • interrupt를 처리한다.
    • 작업 후 status register를 확인한다.
    • ERROR bit가 켜져 있으면 error register를 읽는다.
  • 핵심

    • 실제 device driver는 단순히 read() 한 줄로 끝나지 않는다.
    • 여러 register를 조작하고, 준비 상태를 확인하고, interrupt와 error를 처리해야 한다.

xv6 IDE Driver 흐름

  • OSTEP은 xv6의 IDE driver를 간단히 설명한다.

  • 주요 함수 흐름

    • ide_rw()

      • 요청을 queue에 넣는다.
      • disk가 비어 있으면 바로 요청을 시작한다.
      • 요청이 완료될 때까지 calling process를 sleep시킨다.
    • ide_start_request()

      • 실제 disk에 요청을 보낸다.
      • read/write command를 register에 쓴다.
      • write라면 data도 같이 보낸다.
    • ide_wait_ready()

      • disk가 ready 상태가 될 때까지 기다린다.
    • ide_intr()

      • disk interrupt가 발생하면 실행된다.
      • read 요청이면 data를 device에서 가져온다.
      • 기다리던 process를 깨운다.
      • queue에 다음 요청이 있으면 다음 I/O를 시작한다.
  • 핵심

    • device driver는 요청 queue, sleep/wakeup, interrupt handling을 함께 사용한다.
    • 느린 I/O를 기다리는 동안 process를 잠재우고, 완료되면 interrupt로 다시 깨운다.

이 단원에서 중요한 비교

Polling vs Interrupt

  • Polling

    • CPU가 device 상태를 계속 확인한다.
    • 단순하다.
    • 빠른 device에는 괜찮을 수 있다.
    • 느린 device에서는 CPU 낭비가 크다.
  • Interrupt

    • device가 끝났을 때 CPU에게 알린다.
    • CPU와 I/O를 overlap할 수 있다.
    • 느린 device에 좋다.
    • 너무 자주 발생하면 interrupt overhead나 livelock 문제가 생길 수 있다.

PIO vs DMA

  • PIO

    • CPU가 직접 데이터를 옮긴다.
    • 단순하지만 큰 전송에서는 비효율적이다.
  • DMA

    • DMA controller가 memory와 device 사이의 데이터를 옮긴다.
    • CPU는 다른 일을 할 수 있다.
    • 큰 data transfer에 유리하다.

Explicit I/O vs Memory-Mapped I/O

  • Explicit I/O

    • device 접근용 특별한 instruction을 사용한다.
    • 예: x86 in, out.
  • Memory-Mapped I/O

    • device register를 memory address처럼 다룬다.
    • load/store로 device에 접근한다.

Generic OS Layer vs Device Driver

  • Generic OS layer

    • file system, generic block layer처럼 일반적인 interface를 다룬다.
  • Device driver

    • 특정 device의 세부 protocol을 안다.
    • 실제 register 조작, command 전달, interrupt 처리 등을 담당한다.

Summary

  • 12단원은 persistence 파트의 시작으로, OS가 I/O device와 어떻게 상호작용하는지를 다룬다.
  • I/O device는 input과 output을 가능하게 하므로 컴퓨터 시스템에서 필수적이다.
  • 시스템은 보통 CPU와 memory를 빠른 bus로 연결하고, 더 느린 device는 general I/O bus나 peripheral bus 아래에 둔다.
  • device는 외부에 interface를 제공하고, 내부에는 device-specific 구현을 가진다.
  • 가장 단순한 device interface는 status, command, data register로 생각할 수 있다.
  • OS는 status register로 device 상태를 확인하고, data register로 데이터를 전달하며, command register로 작업을 시작한다.
  • polling은 device 상태를 반복적으로 확인하는 방식이다.
  • polling은 단순하지만 느린 device에서는 CPU 시간을 낭비한다.
  • interrupt는 device가 작업 완료를 CPU에게 알려주는 방식이다.
  • interrupt를 쓰면 CPU가 I/O를 기다리는 동안 다른 process를 실행할 수 있다.
  • 하지만 interrupt가 항상 좋은 것은 아니다.
  • 빠른 device에서는 polling이 더 나을 수 있고, 너무 많은 interrupt는 livelock을 만들 수 있다.
  • PIO는 CPU가 직접 데이터를 옮기는 방식이다.
  • DMA는 CPU 대신 DMA controller가 memory와 device 사이의 데이터를 전송하는 방식이다.
  • device와 통신하는 방식에는 explicit I/O instruction과 memory-mapped I/O가 있다.
  • device driver는 device-specific detail을 감추고 OS 상위 계층이 일반적인 interface로 device를 사용할 수 있게 해준다.
  • 실제 disk driver는 queue 관리, register 조작, interrupt handling, error handling을 모두 처리해야 한다.
  • 결국 이 단원의 핵심은 이것이다.

OS는 다양한 I/O device를 효율적으로 사용하기 위해 polling, interrupt, DMA, memory-mapped I/O, device driver 같은 기법을 사용한다.


시험/복습 포인트

  • Persistence는 데이터를 전원이 꺼져도 유지하는 문제를 다룬다.
  • I/O device는 input과 output을 담당하는 장치다.
  • bus는 보통 계층적으로 구성된다.
  • 빠른 bus는 짧고 비싸며, 느린 bus는 많은 장치를 연결하기 쉽다.
  • canonical device는 interface와 internals로 나눌 수 있다.
  • interface에는 status, command, data register가 있다.
  • polling은 status register를 계속 확인하는 방식이다.
  • polling은 단순하지만 CPU를 낭비할 수 있다.
  • interrupt는 device가 작업 완료를 CPU에게 알리는 방식이다.
  • interrupt는 CPU와 I/O overlap을 가능하게 한다.
  • 빠른 device에서는 interrupt보다 polling이 나을 수 있다.
  • interrupt가 너무 많으면 livelock이 생길 수 있다.
  • interrupt coalescing은 여러 interrupt를 묶어 overhead를 줄이는 방식이다.
  • PIO는 CPU가 직접 데이터를 옮기는 방식이다.
  • DMA는 CPU 대신 DMA controller가 데이터를 옮기는 방식이다.
  • explicit I/O instruction은 device 접근용 특별한 명령을 사용한다.
  • memory-mapped I/O는 device register를 memory address처럼 다룬다.
  • device driver는 device-specific detail을 숨기는 OS 내부 소프트웨어다.
  • generic block layer는 file system과 device driver 사이에서 일반적인 block read/write interface를 제공한다.
  • raw interface는 file abstraction을 우회해 block device에 직접 접근할 수 있게 한다.
  • 실제 device driver는 request queue, sleep/wakeup, interrupt, error handling을 함께 다룬다.
Discussion