Skip to content

Chapter 7 I/O Systems

Overview

I/O 管理是操作系统设计与运行的核心组成部分,I/O 设备是计算机和用户、其他系统交互的媒介。

I/O 设备种类差异巨大,控制方式各不相同,性能表现参差不齐,设备驱动必须屏蔽硬件细节,向上提供统一接口。

1 I/O Hardware

1. 1 Basic Concepts

概念 含义
Bus 连接 CPU、memory、controllers 等组件的互连结构
Port 设备与系统连接的接入点
Controller 控制设备的硬件部件,可集成在设备中或作为独立电路板
Device driver OS 中控制设备的软件模块

Controller 通常包含自己的 processor、microcode、private memory 和 bus controller。OS 不直接操作设备内部所有细节,而是通过 controller 暴露的寄存器和协议发出命令。

设备通常提供若干寄存器,用于传输数据、发出命令和读取状态,设备驱动将指令与数据(或数据指针)写入寄存器。

寄存器分为输入数据(data-in)寄存器、输出数据(data-out)寄存器、状态(status)寄存器、控制/命令(control / command)寄存器,通常为 1~4 字节,也可使用 FIFO 先进先出缓冲区。设备的寄存器、板载内存都会被分配硬件地址

I/O Ports on PCs (Partial)

有些 CPU 架构提供专门 I/O instructions,例如 x86 的 inoutinsouts

方式 做法 适用场景
Direct I/O instructions 使用专门 I/O 指令访问 I/O ports 多用于小寄存器访问
Memory-mapped I/O 把设备寄存器或设备内存映射到 physical address space 适合大块设备内存,如显存

1. 2 Polling and Interrupts

轮询(Polling)是最直接的 I/O 控制方式,CPU / driver 主动反复读取 status register,直到设备可用或命令完成。

轮询基于忙等待实现,高速设备使用轮询性价比高,低速设备使用则效率低下。

中断(Interrupt)让设备在操作完成时主动通知 CPU,从而避免 CPU 长时间 busy-wait。但它不是免费的,一次 interrupt 需要保存现场、切换到内核处理、执行 handler,再恢复被打断的上下文,若中断频次过高,频繁上下文切换会消耗大量 CPU。

如果中断频率极高,系统可能改用 polling 或混合策略。Linux 网络栈中的 NAPI 就会在高网络负载下启用 polling,减少过高频率 interrupt 带来的 CPU 浪费。

CPU 需要根据 interrupt / exception 类型找到对应处理入口,这通常通过 interrupt vector table 完成。

Intel Pentium Interrupt Vector Table

Linux vector table on ARM64

定义 vector table,把 vector table 地址写入 VBAR(Vector Base Address Register)。

Linux vector table on RISC-V64

定义 exception table,把 handle_exception 写入 stvec(Supervisor Trap Vector Base Address Register),handle_exception 再跳到具体 exception table entry。

Interrupt 也常被用来处理 exceptions。

常见 Exception
  • protection error:访问违反保护规则
  • page fault:内存访问找不到合法映射或权限不满足
  • software interrupt:用于 system calls

在多 CPU 系统中,不同 CPU 可以并发处理中断,有时会专门指定某个 CPU 处理某类 interrupt。

SMP 中断亲和性(IRQ Affinity)

Linux 从 2.4 kernel 开始支持把特定 IRQ 分配到指定处理器或处理器组,这称为 SMP IRQ affinity,用户可控制系统对各类硬件事件的响应方式。

它能够限制、重新划分服务器负载,提升服务器运行效率,在多处理器设备中实现多网卡负载均衡,把不同 NIC 的 interrupt 绑到不同 CPU,提升服务器可承载的网络流量上限。

对同时有高磁盘、高网络负载的数据库服务器,可让某些 CPU 更偏向磁盘控制器中断,另一些 CPU 更偏向 NIC,优化响应耗时。

1. 3 Direct Memory Access

直接内存访问(DMA, Direct Memory Access)让 I/O device 和 memory 直接传输数据,CPU 不再逐字节搬运,数据以大块形式传输。

DMA 需要设备或系统中有 DMA controller。OS 向 DMA controller 发出指令(包含 operation、memory address、byte count 等信息),传输完成后,设备 interrupt CPU。

真实系统中,driver 常把 command descriptor 的 pointer 写入设备 command register,而不是把所有数据逐字节写入寄存器。

2 Application I/O Interface

Application I/O interface 把设备行为封装成若干通用类别,应用通过系统调用访问设备,而不是直接操作硬件。

Device-driver layer 隐藏 I/O controller 的差异,每个 OS 都有自己的 I/O subsystem 和 driver framework。若新设备遵循已有协议,就可能复用已有 driver 或 framework。

Characteristics of I/O Devices

OS 通常把设备归入若干宽泛的类别里:

类别 典型操作 示例
Block I/O readwriteseek disks、SSDs
Character I/O / Stream 按字符或字节流读写 keyboard、mouse、serial port
Memory-mapped file access 文件映射到 address space mmap()
Network sockets 网络协议读写 TCP/UDP sockets、Unix sockets
Clocks and timers 当前时间、elapsed time、timer system timer

Linux 的设备接口 ioctl

Linux 中很多设备可像文件一样访问。例如 /dev/tty/dev/sda 都是设备文件。

标准 read/write/seek 无法覆盖所有设备特有能力,Linux 提供 ioctl 这类 escape hatch,让应用把特定命令传给 device driver,但也会扩大接口复杂度和安全风险。

  • Block devices:以 block 为单位访问数据,例如 disk drives。
    • 支持 readwriteseek
    • 可通过 raw I/O、direct I/O 或 file-system access 使用
    • 支持 memory-mapped file access
    • 常配合 DMA 提高吞吐
  • Character devices:包括 keyboards、mice、serial ports 等,差异非常大,通常更像顺序 byte stream。
  • Network devices:与 block / character devices 差异很大,因此常有自己的接口,最常见的是 socket interface

Socket Interface

Socket 把 network protocol 与具体网络设备操作分离,应用使用 socket API 时不需要直接知道网卡寄存器或驱动细节。某些非网络通信也可用 socket 抽象实现,例如 Unix domain socket。

  • Clocks and timers:也可被视作 character devices,提供 current time、elapsed time 和 timer event。普通 timer resolution 可能约为 1/60 second,某些 OS 提供更高分辨率的 timer。
类型 行为 特点
Blocking I/O 进程挂起直到 I/O 完成 简单,但可能低效
Non-blocking I/O 调用立即返回当前可用数据或状态 需要应用自己检查 readiness
Asynchronous I/O I/O 执行期间进程继续运行,完成后被通知 高效但更难使用

3 Kernel I/O Subsystem

  • I/O Scheduling:OS 维护 per-device request queue,并对请求排序。
  • Buffering:在设备间传输数据时临时把数据放入内存。
    • 处理设备速度不匹配,例如从 network 接收数据,再写入 SSD
    • 处理设备传输粒度不匹配,例如网络分片重组成完整 message
    • 维护 copy semantics,例如 write() 返回后应用缓冲区可被修改,OS 仍能完成后续写入
    • 使用 double buffering 让一个 buffer 被设备使用时,另一个 buffer 可被应用或内核处理
  • Caching:保存一份数据副本以便快速再次访问,是 I/O 性能的关键。

Buffering V.S. Caching

  • Buffering 主要解决传输过程中的临时存放和速率 / 粒度 mismatch
  • Caching 主要解决重复访问的性能
  • 实际系统中同一块内存 buffer 可能同时承担 buffering 和 caching 角色
  • Spooling:为一次只能服务一个请求的设备维护输出队列。典型例子是 printing,多个进程的打印任务先进入 spool,再由 printer 顺序处理。
  • Device reservation:提供对某些设备的 exclusive access。OS 需要提供 allocation / de-allocation system calls,但也要注意 deadlock。

Error Handling & I/O Protection

I/O 错误可能来自设备不可用、瞬时写失败、连接断开、坏块等,常见错误处理策略有:

  • 重试 read / write
  • 记录 error frequency,对错误频繁的设备停止使用
  • 向应用返回 error number / error code,把问题写入 system error logs

OS 必须保护 I/O devices,其基本原则为:

  • 将全部 I/O 指令设定为特权指令,所有 I/O 操作必须经由系统调用完成
  • Memory-mapped I/O pages 必须通过页表权限保护,I/O ports 也必须受 CPU privilege 和 OS 控制
Use System Call to Perform I/O

Kernel 需要保存大量 I/O 状态,某些 OS 使用 message passing 实现 I/O,例如 Windows。包含 I/O 信息的 message 从 user mode 进入 kernel,再经过 driver,最后返回给 process。

UNIX I/O Kernel Structure

系统资源访问最终必须映射到具体硬件操作,以进程读取磁盘文件为例,其大致路径如下:

  • 确定存放文件的硬件设备
  • 把文件名转换为设备标识(device representation),FAT / UNIX 中常涉及 major / minor number
  • 从磁盘实际读取数据到缓冲区,把数据提供给发起请求的进程,将运行控制权交还进程
Life Cycle of An I/O Request

I/O 是系统性能的重要瓶颈之一,即使 CPU 很快,也可能被 I/O 路径上的等待、复制和中断拖慢。

Network Communications: high context switch

远程登录是一个典型例子,本地机器上敲下的每个字符都要传到远程机器,远程处理后再把结果传回来。若每个字符都触发多次上下文切换和网络发送,开销会非常高。

Improve Performance
  • 减少 context switches 数量
  • 减少 data copying
  • 使用 large transfers、smart controllers、polling 减少 interrupts
  • 使用 DMA
  • 使用更智能的硬件设备
  • 平衡 CPU、memory、bus 和 I/O 性能,提高整体 throughput
  • 将部分 user-mode daemons 移入 kernel threads,减少边界切换

Linux tty and ioctl

/dev/tty 的创建与 tty_init 相关,初始化时内核注册 tty 设备,并在 /dev 下创建对应设备文件。

/dev/tty 执行 write,系统调用会进入 VFS 通用路径,最后调用 tty driver 的具体写函数。

ioctl 用于把设备特有命令传给 driver,有路径 ioctl syscall -> vfs_ioctl -> indirect call -> tty_ioctl

ioctl 接口往往命令多、参数复杂、类型检查困难,且经常通过间接调用进入具体 driver,因此容易成为安全漏洞来源。