140 / 163 · C11 · 约 8 分钟
I/O 设备
本章讲解操作系统如何将输入输出设备融入整体系统,涵盖层次化总线架构、设备对外接口与内部实现、基于寄存器的轮询协议,以及用中断实现计算与I/O重叠的方法。
层次化系统架构
CPU 通过专用高速互连紧挨主存。图形卡等对延迟敏感的部件挂在通用 I/O 总线(如 PCI Express)上。磁盘、键盘、鼠标则落在更外围、更便宜的总线(SATA、USB)上。物理规律决定了越快的总线必须越短,同时高速互连的工程成本也更高,因此设计师把慢速、数量多的设备放到较远的层次,既满足性能又控制成本。
典型设备的组成
设备对外呈现一组寄存器:状态寄存器供软件查询忙闲,命令寄存器接收操作码,数据寄存器用于搬入或搬出字节。内部则是微控制器、SRAM/DRAM 以及专用电路;复杂设备甚至运行数万行固件来实现 RAID 或协议处理。操作系统只需遵守公开接口,不必知道芯片内部细节。
轮询式交互协议
软件反复读状态寄存器直到设备空闲,再把数据写入数据寄存器(由 CPU 亲自搬运,称为编程 I/O),接着把命令写入命令寄存器启动硬件,最后再次轮询直到完成。协议简单可靠,但两次忙等待会让处理器空转,尤其当设备本身很慢时浪费巨大。
用中断降低处理器开销
发出请求后操作系统立刻让发起进程睡眠并切换到其他可运行任务。设备完成后拉高硬件中断线,处理器跳进预先登记的中断服务例程,读取结果或错误码,再唤醒等待者。计算与 I/O 因此可以真正重叠,处理器不再空转。
常见误区
- 在慢速设备上持续轮询会让 CPU 利用率接近零。
- 命令完成后忘记检查状态寄存器中的错误位会导致静默数据损坏。
- 大块数据仍用编程 I/O 搬运会把处理器变成搬运工,应改用 DMA。
运行一个例子
最低标准 C11 · 完整程序 · 下载 .c
#include <stdio.h>
int main(void) {
printf("=== Simulating Canonical I/O Protocol ===\n");
printf("Step 1: OS polls STATUS register until not BUSY\n");
printf(" (in reality this loop wastes CPU)\n");
printf("Step 2: OS writes data to DATA register (PIO)\n");
printf("Step 3: OS writes command to COMMAND register\n");
printf("Step 4: OS polls STATUS until complete\n");
printf("I/O done. Using interrupts would overlap this wait with other computation.\n");
return 0;
}
在本地编译
gcc -std=c11 -Wall -Wextra -Wpedantic -Werror ostep-36-io-devices.c -o example && ./example预期结果
=== Simulating Canonical I/O Protocol ===
Step 1: OS polls STATUS register until not BUSY
(in reality this loop wastes CPU)
Step 2: OS writes data to DATA register (PIO)
Step 3: OS writes command to COMMAND register
Step 4: OS polls STATUS until complete
I/O done. Using interrupts would overlap this wait with other computation.
CHECK YOUR UNDERSTANDING
合上答案,试着解释。
为什么系统不把所有设备直接接在高速内存总线上,而要采用层次化总线?
查看参考答案
高速互连受电气特性限制必须很短,设计成本也高。把低性能设备放到更长、更便宜的外围总线上,既能连接大量慢速部件,又不会拖累内存子系统。
继续查证
标准草案与官方章节会更新;版本标记只说明示例最低要求。