Skip to content
Published on

[OS Concepts] 12. I/O Systems

Authors

I/O Systems

One of the core roles of an operating system is to manage various I/O devices and provide a consistent interface to applications. This article examines everything from I/O hardware architecture to the kernel I/O subsystem.


1. I/O Hardware

Ports, Buses, and Controllers

┌─────────────────────────────────────────────┐
CPU│              ┌──────────┐                    │
│              │ Memory   │                    │
│              └────┬─────┘                    │
│                   │ System Bus│         ┌─────────┼─────────┐                │
│         │         │         │                │
│    ┌────┴───┐ ┌───┴────┐ ┌─┴──────┐         │
│    │ SATA   │ │ USB    │ │ PCIe   │  ← Controllers
│    │Controller│ │Controller│ │Controller│     │
│    └────┬───┘ └───┬────┘ └─┬──────┘         │
│         │         │         │                │
│     ┌───┴──┐  ┌───┴──┐  ┌──┴───┐            │
│     │ HDD  │  │ Mouse│GPU  │  ← Devices│     └──────┘  └──────┘  └──────┘            │
└─────────────────────────────────────────────┘
  • Port: Connection point to a device (e.g., USB port, SATA port)
  • Bus: A shared communication path that carries signals (e.g., PCIe bus)
  • Controller: Electronic circuitry that manages ports, buses, and devices

Device Controller Registers

Each device controller typically has the following registers:

RegisterRole
data-inData for the host to read
data-outData for the host to write
statusDevice state (busy, ready, error)
commandCommands issued by the host

2. Polling

The CPU repeatedly checks the device's status register to detect I/O completion.

// Polling-based I/O example (pseudo code)
void polling_write(char data) {
    // 1. 장치가 준비될 때까지 busy-wait
    while (read_status_register() & BUSY_BIT)
        ; // 바쁜 대기

    // 2. 데이터 레지스터에 데이터 쓰기
    write_data_register(data);

    // 3. 명령 레지스터에 쓰기 명령 설정
    write_command_register(WRITE_COMMAND);

    // 4. 완료될 때까지 다시 대기
    while (read_status_register() & BUSY_BIT)
        ;
}

Advantages: Simple implementation, efficient for short I/O operations Disadvantages: Wastes CPU cycles (busy waiting), inefficient for long I/O operations


3. Interrupts

The device sends a signal to the CPU upon I/O completion. The CPU handles it after being interrupted from other work.

Interrupt Handling Flow

┌──────┐      1. I/O Request        ┌──────────┐
CPU  │ ──────────────────────→    │ Device│      │                            │Controller│
│      │  Performing other work     │          │
│      │                            │ I/O in│      │                            │ progress │
│      │  ←── 2. Interrupt signal ──│ Done!│      │                            └──────────┘
│      │  3. Save current state
│      │  4. Execute interrupt handler
│      │  5. Restore state, resume work
└──────┘

Interrupt Vector Table

// Interrupt vector table concept (pseudo code)
typedef void (*interrupt_handler_t)(void);

interrupt_handler_t interrupt_vector[256];

// Register handlers during initialization
void init_interrupts() {
    interrupt_vector[0]  = divide_error_handler;
    interrupt_vector[1]  = debug_handler;
    interrupt_vector[14] = page_fault_handler;
    interrupt_vector[32] = timer_handler;
    interrupt_vector[33] = keyboard_handler;
    interrupt_vector[46] = disk_handler;
    // ...
}

// Dispatch on interrupt occurrence
void dispatch_interrupt(int vector_num) {
    interrupt_vector[vector_num]();
}

Interrupt Priority

The operating system assigns priorities to interrupts so that important ones are processed first.

High Priority
  │  ┌────────────────────────┐
  │  │ NMI (Non-Maskable)     │ ← Hardware errors
  │  │ Timer Interrupt        │ ← Scheduling
  │  │ Disk Interrupt         │ ← I/O completion
  │  │ Network Interrupt      │ ← Packet arrival
  │  │ Keyboard/Mouse         │ ← User input
  ▼  └────────────────────────┘
Low Priority

4. DMA (Direct Memory Access)

A method for transferring data directly between a device and memory without CPU intervention during bulk data transfers.

DMA Operation Process

┌──────┐                    ┌──────────┐
CPU1. DMA request     │ DMA│      │    setup            │ Controller│
│      │ ─────────────────→ │          │
│      │                    │ 2. Bus│      │  Free to do other  │  control  │
│      │  work              │  acquired │
│      │                    │          │
│      │                    │ 3. Device ↔│
│      │                    │  Memory│      │                    │  direct   │
│      │                    │  transfer │
│      │  ←── 4. CompletionTransfer│      │      interrupt     │ complete  │
└──────┘                    └──────────┘
// DMA transfer setup (pseudo code)
void setup_dma_transfer(
    void *buffer,        // 메모리 버퍼 주소
    int   device_id,     // 대상 장치
    int   byte_count,    // 전송 바이트 수
    int   direction      // READ 또는 WRITE
) {
    dma_controller.address  = buffer;
    dma_controller.count    = byte_count;
    dma_controller.device   = device_id;
    dma_controller.command  = direction;

    // DMA 전송 시작 - CPU는 다른 작업 수행 가능
    dma_controller.start    = 1;
}

5. Application I/O Interface

The operating system abstracts various devices into several categories to provide a consistent interface.

Device Type Interfaces

┌───────────────────────────────────────┐
Applicationread()  write()  ioctl()├───────────────────────────────────────┤
Kernel I/O Subsystem├──────┬──────┬──────┬──────┬──────────┤
Block│ Char │Network│Clock │ Other│Device│Device│Socket │Timer │          │
├──────┼──────┼──────┼──────┼──────────┤
Disk │Keybd │ NICRTC  │          │
SSD  │Mouse │      │ PIT  │          │
└──────┴──────┴──────┴──────┴──────────┘
Device TypeCharacteristicsKey OperationsExamples
Block DeviceFixed-size block accessread, write, seekDisk, SSD
Character DeviceByte streamget, putKeyboard, serial
Network DeviceSocket interfacesend, receiveNIC
Clock/TimerTime measurement, alertsget_time, set_timerRTC, HPET

Blocking vs Non-blocking I/O

// 블로킹 I/O - 완료될 때까지 프로세스 대기
ssize_t bytes = read(fd, buffer, size);
// 이 줄은 read가 완료된 후에 실행됨

// 논블로킹 I/O - 즉시 반환
fcntl(fd, F_SETFL, O_NONBLOCK);
ssize_t bytes = read(fd, buffer, size);
if (bytes == -1 && errno == EAGAIN) {
    // 데이터가 아직 준비되지 않음
}

// 비동기 I/O - 요청 후 완료 시 통지
struct aiocb cb;
cb.aio_fildes = fd;
cb.aio_buf    = buffer;
cb.aio_nbytes = size;
aio_read(&cb);  // 즉시 반환
// 나중에 완료 확인
while (aio_error(&cb) == EINPROGRESS)
    do_other_work();

6. Kernel I/O Subsystem

The kernel provides several services for efficient I/O management.

I/O Scheduling

Optimizes the execution order of multiple I/O requests.

Request Queue:  [Disk Read A] [Disk Write B] [Disk Read C]
I/O Scheduler (reorders)
Execution Order: [Disk Read A] [Disk Read C] [Disk Write B]
Minimizes disk head movement

Buffering

Uses temporary storage to smooth out speed differences during data transfer.

Producer (Device)                   Consumer (Process)
    │                                  │
    │  ┌──────────┐                    │
    ├→ │ Buffer 1  (filling)    │  └──────────┘                    │
    │  ┌──────────┐                    │
    │  │ Buffer 2 │ ────────────────→  ├→ Processing
└──────────┘  (draining)    │                                  │
Double buffering: fill and      │
    │  drain simultaneously            │

Caching

Keeps copies of frequently accessed data in faster storage.

ApplicationCheck cache → Hit?Return from cache
                  └→ MissRead from disk → Store in cache → Return

Spooling

An output queuing mechanism for devices that can handle only one job at a time (e.g., printers).

Process A ─→ ┌────────────┐
Process B ─→ │ Spool Queue│ ─→ Printer (one at a time)
Process C ─→  (disk)              └────────────┘

7. I/O Performance

I/O is a major bottleneck in overall system performance.

Performance Improvement Principles

┌────────────────────────────────────────┐
I/O Performance Optimization│                                        │
1. Reduce context switch count        │
2. Reduce data copy count             │
     (Zero-copy technique)3. Reduce interrupt frequency         │
     (Interrupt coalescing)4. Use DMA to reduce CPU load         │
5. Appropriate mix of polling         │
│     and interrupts                     │
6. Offload functionality to hardware  │
     (Hardware Offloading)└────────────────────────────────────────┘

Zero-copy Transfer Example

// 전통적 방식: 4번의 데이터 복사
// 디스크 → 커널 버퍼 → 사용자 버퍼 → 소켓 버퍼 → NIC

// sendfile()을 이용한 Zero-copy (Linux)
#include <sys/sendfile.h>

// 파일에서 소켓으로 직접 전송 (커널 내에서만 복사)
ssize_t sent = sendfile(socket_fd, file_fd, &offset, count);
// 디스크 → 커널 버퍼 → NIC (사용자 공간 복사 없음)

8. Summary

  • Polling: Simple but wastes CPU. Suitable for short I/O
  • Interrupts: CPU-efficient but has overhead. Used for most I/O
  • DMA: Essential for bulk data transfer. Minimizes CPU load
  • Kernel I/O Subsystem: Ensures performance and compatibility through scheduling, buffering, caching, and spooling
  • Performance Optimization: Various techniques including zero-copy, interrupt coalescing, and hardware offloading
Quiz: I/O Systems

Q1. What is the difference between polling and interrupt-driven I/O, and when is each suitable?

A1. Polling is a method where the CPU repeatedly checks the device status, and it can be more efficient than interrupt overhead for very short I/O operations. Interrupts are a method where the device notifies the CPU upon completion, allowing the CPU to perform other work while waiting, making it more suitable for most I/O operations.

Q2. How does DMA benefit CPU performance?

A2. The DMA controller handles data transfer between memory and devices directly, so the CPU does not have to wait for the transfer to complete and can perform other computations. This significantly reduces CPU utilization, especially during large disk I/O or network transfers.

Q3. What is the difference between buffering and caching?

A3. Buffering is temporary storage to smooth out speed differences between producers and consumers; data is removed from the buffer once consumed. Caching maintains copies of frequently accessed data in faster storage for reuse.