入门:I/O 设备分类、控制方式与驱动
基于通用操作系统概念 · 核于 2026-08
速查
- I/O 管理的目标:让 CPU 与外设并行工作——外设慢(ms 级),CPU 快(ns 级),若 CPU 等外设将浪费百万倍时间,OS 用中断/DMA 让 CPU 在 I/O 期间做别的事。
- 设备三大类:①块设备(磁盘/SSD/U 盘,按固定大小块寻址,可随机访问);②字符设备(键盘/鼠标/串口/打印机,按字节流访问,不可寻址);③网络设备(网卡,数据包收发,介于两者之间)。
- 设备控制器:每个设备的硬件接口,有寄存器(状态/控制/数据)和设备缓冲,CPU 通过读写控制器寄存器操纵设备。
- I/O 控制三方式(演进):①程序查询(轮询/忙等待)——CPU 反复查状态,全程占用,最浪费 CPU;②中断驱动——CPU 发命令后去做别的,设备完成发中断通知 CPU,CPU 利用率高但每字节一次中断(高频小数据开销大);③DMA(直接内存访问)——DMA 控制器接管,整块数据直接在设备与内存间传输,完成才发一次中断,CPU 几乎不参与。
- 设备驱动程序:OS 内核与具体硬件之间的适配层——把"读第 N 块"翻译成"对控制器写哪些寄存器",屏蔽硬件差异。Linux 把驱动抽象成
file_operations(open/read/write/ioctl)。 - I/O 子系统分层:用户层 I/O 库(stdio)→ 系统调用(read/write)→ VFS/设备无关层→ 设备驱动→ 中断处理→ 硬件。
- 缓冲(Buffering)目的:①缓解 CPU 与设备速度失配;②减少 I/O 次数(攒够一块再传);③协调块设备与字符设备的粒度差。分单缓冲、双缓冲、循环缓冲。
- Spooling(假脱机):用磁盘 + 进程队列把独占设备虚拟化成共享设备——典型:打印机。多个进程"同时"提交打印,实际在磁盘排队,逐个输出。
- 磁盘访问三时间:寻道时间(磁头移到柱面,主导)+ 旋转延迟(盘片转到扇区)+ 传输时间(读写扇区)。寻道最慢,所以调度算法优化寻道。
- 磁盘调度算法:FCFS(先来先服务,公平但寻道长)、SSTF(最短寻道优先,可能饥饿)、SCAN(电梯算法,来回扫)、C-SCAN(只单向服务,返回不服务)、LOOK(到最远请求即返,不跑空)。
- RAID:用多个廉价磁盘组成逻辑盘,条带化提速(并行)、镜像/校验容错。RAID 0(条带,无冗余)、RAID 1(镜像)、RAID 5(条带+分布式校验,主流)、RAID 10(1+0,镜像再条带)。
- SSD vs 传统磁盘:SSD 无机械部件,无寻道/旋转延迟,随机访问快百倍;但有写入寿命(NAND 擦写次数有限)和写放大,需磨损均衡与 TRIM。
- 进阶顺序:I/O 控制方式与缓冲 → 磁盘调度与 RAID → 参考。
一、I/O 设备的分类
外设五花八门,OS 按信息传输单位与寻址方式把它们分三类:
- 块设备(Block Device):以固定大小的块为单位传输(磁盘一块 512B/4KB),支持寻址(能直接读写第 N 块),可随机访问。代表:磁盘、SSD、U 盘、光盘。文件系统建立在块设备之上。
- 字符设备(Character Device):以字节流为单位传输,不可寻址(只能顺序读,不能跳到第 N 字节),通常只能顺序访问。代表:键盘、鼠标、串口、终端、打印机。Linux 里
/dev/tty、/dev/null都是字符设备。 - 网络设备(Network Device):传输数据包(packet/frame),介于块与字符之间,有收发队列。Linux 里它不是
/dev下的文件,而是通过socket接口访问。
- 共享 vs 独占:磁盘/网卡是共享设备(多进程可并发分时使用);打印机、磁带机是独占设备(一次只能给一个进程用,否则混乱)——独占设备靠 Spooling 虚拟化成共享。
- 设备 → 控制器 → 总线:设备通过设备控制器(Controller)接总线,控制器有状态寄存器(设备忙/闲/错)、控制寄存器(启动/方向/块号)、数据寄存器(缓冲)。CPU 对设备的操作 = 对控制器寄存器的读写。
二、I/O 控制三方式:从忙等待到 DMA
CPU 怎么和设备交换数据?三种方式,越往后 CPU 占用越少:
1. 程序查询(轮询 / 忙等待)
CPU 发出 I/O 命令后,反复读状态寄存器判断设备是否完成:
CPU: while (状态寄存器 == 忙) ; // 死循环轮询,CPU 全程占用
CPU: 读数据寄存器 // 完成后取数据- 致命缺点:CPU 全程忙等待,浪费百万倍时间(设备 ms 级,CPU ns 级)。期间 CPU 无法执行其他进程。
- 仅用于极简单/早期系统或嵌入式特定场景。现代 OS 几乎不用。
2. 中断驱动
CPU 发出 I/O 命令后立即去做别的(执行其他进程或切换),设备完成后发中断信号,CPU 响应中断执行中断处理程序取数据:
CPU: 写控制寄存器(启动设备)→ CPU 去执行别的进程
设备: 完成 → 发中断
CPU: 响应中断 → 跳到中断处理程序 → 读数据寄存器 → 唤醒等待进程- 优点:CPU 与设备并行,CPU 利用率大增。
- 缺点:每传一个单位(如 1 字节/1 字)就发一次中断,高频小数据时中断开销淹没 CPU(上下文切换太频繁)。
3. DMA(直接内存访问 Direct Memory Access)
为解决中断驱动的高频中断问题,引入 DMA 控制器(DMAC):CPU 把整块传输任务(源、目的、长度)交给 DMAC,DMAC 与设备、内存直接打交道,整个块传完才发一次中断:
CPU: 设置 DMAC(源=设备,目的=内存地址,长度=N)→ CPU 去执行别的
DMAC: 接管总线,逐字节/字在设备↔内存间搬运,CPU 不参与
DMAC: 全部传完 → 发一次中断通知 CPU- 优点:大批量数据 CPU 几乎零干预,只在开始(设置)和结束(一次中断)参与。用于磁盘、网卡等大数据块传输。
- 关键:DMA 期间周期窃取(cycle stealing)——DMAC 周期性占用总线一个周期传一字,CPU 稍有停顿但远好于中断驱动。
- 进阶:通道(Channel):大型机更进一步,通道是可编程的 I/O 处理器,能执行通道程序,独立完成复杂 I/O 任务,CPU 只管启动与收尾。
三方式对比
| 方式 | CPU 参与度 | 中断频率 | 数据单位 | 适用 |
|---|---|---|---|---|
| 程序查询 | 全程忙等待 | 无 | 字/字节 | 极简单/嵌入式 |
| 中断驱动 | 每单位参与 | 每单位一次 | 字/字节 | 慢速字符设备(键盘) |
| DMA | 仅起止参与 | 整块一次 | 块 | 块设备(磁盘/网卡) |
三、设备驱动程序
设备驱动(Device Driver)是 OS 内核与具体硬件之间的适配层/翻译层。OS 上层(VFS、系统调用)是设备无关的,它只说"读第 N 块";具体怎么"读"(对哪个控制器的哪个寄存器写什么值)由驱动完成:
- 屏蔽硬件差异:同样是"读磁盘块",SCSI 盘、SATA 盘、NVMe 盘的控制器指令完全不同,但驱动把它们都翻译成统一的
read接口。OS 之所以能支持海量设备,靠的就是驱动分层。 - 运行位置:传统上驱动在内核态(Linux LKM、Windows .sys),驱动 bug 会崩内核。现代趋势是部分驱动放用户态(如 Linux 的 FUSE 文件系统、Windows 的 UMDF)以提升可靠性。
- Linux 的驱动抽象:把所有设备抽象成文件,驱动实现
file_operations结构体(open/read/write/ioctl/close),应用用统一read(fd, buf, n)访问任何设备。 - 驱动是内核崩溃的头号元凶:因为驱动多(数千个)、来自各厂商、质量参差,且运行在内核态——Linux/Windows 蓝屏/panic 多数由驱动 bug 引发。这正是微内核想把驱动移用户态的动机。
下一步
理解了 I/O 设备分类、三方式演进与驱动角色后,下一步深入I/O 控制方式与缓冲(忙等待/中断/DMA 的工程取舍、缓冲如何缓解速度失配、Spooling 如何虚拟化独占设备),以及磁盘调度与 RAID(如何优化磁盘访问、如何用冗余保证可靠)。