07 输入输出系统
硬件速览里埋了两个伏笔:"键盘按一下,CPU 怎么知道?"(中断)和"搬大块数据还要劳烦 CPU 吗?"(DMA)。本章一次回收——这也是计组的收官篇:CPU 和外设的三种相处方式,从"死等"到"有事叫我"到"快递员直接送货"。
OS 视角的 I/O 软件栈(缓冲、驱动、设备分配)在 I/O 管理,本篇聚焦硬件层机制。
一、接口与端口:CPU 怎么"摸到"设备
外设通过接口(I/O Controller)挂到总线,接口里有一排寄存器,即端口(port):数据端口、状态端口、控制端口。
CPU 访问端口有两种编址:
| 维度 | 统一编址(存储器映射 MMIO) | 独立编址(专用 I/O 指令) |
|---|---|---|
| 地址空间 | 端口占用主存地址空间的一段 | 端口独立于主存,另有小地址空间 |
| 访问方式 | 普通访存指令(mov 直接读写设备) |
专门指令(x86 的 IN/OUT) |
| 优点 | 指令丰富(所有访存指令都能操作设备)、无需额外控制线 | 地址空间不占主存、指令短、程序意图清晰(看到 IN/OUT 就知道在摸设备) |
| 缺点 | 主存空间被"吃"一块;地址译码要区分内存和设备 | 需专门的 I/O 指令和控制线(多一根 IO/M̅ 信号) |
| 代表 | ARM、RISC-V(MMIO 为主) | x86(同时支持两种) |
💡 GPU 显存(显卡)就是 MMIO 的活例子:cudaMalloc 返回的"显存指针",其实是主存地址空间里映射的一段——CPU 用普通访存指令就能摸到它。
二、程序查询方式:最笨也最直白
loop: 读状态端口 → 忙?
忙 → 跳回 loop 死等
闲 → 读/写数据端口
- CPU 100% 轮询等待,设备慢时全在空转
- 优点:接口简单、无额外硬件
- ⚠️ 效率极低——"CPU 等一行字符输入"就是这种模式的绝罚。面试里"忙等待(busy waiting)"的反面教材
三、中断方式:有事叫我
全流程(408 必背)
设备准备好 → 接口发中断请求(INTR 线)→ CPU 每条指令执行结束前查一次请求 → 允许且开中断则响应:
- 关中断(保护现场期间不再被打断)
- 保存断点:PC、PSW 压栈(有的硬件自动存,有的靠中断程序)
- 引出中断服务程序:送中断向量(中断类型号 × 向量长度 → 向量表找入口),PC ← 服务程序入口
- 保存现场:通用寄存器压栈(软件干)
- 执行中断服务程序本体(如从键盘缓冲区取字符)
- 恢复现场、开中断、中断返回(IRET:弹回断点)
⚠️ 1、2 步是中断隐指令——由硬件自动完成,不在任何指令集里,却真实存在。408 选择题爱考"哪一步属于隐指令":关中断、保存断点、引出服务程序这三件。
中断分类一张图
中断(CPU 外部事件,异步)
├── 外中断(狭义的"中断")
│ ├── 可屏蔽(INTR 线:键盘、打印机…PSW 的 IF 位可挡)
│ └── 不可屏蔽(NMI 线:掉电、内存校验错…必须响应)
└── 内中断(异常,CPU 执行指令时自己产生的,同步)
├── 故障 Fault(缺页、除 0——可修复,修复后重执行)
├── 自陷 Trap(系统调用、断点——**故意**的,[[01-操作系统概述|OS 概述]]的用户态入口)
└── 终止 Abort(硬件错误——程序已救不了)
⚠️ 核心区分:异常(内中断)与当前指令同步,中断(外中断)与指令流异步。缺页是异常不是中断——408 最爱出这道题。
中断嵌套与屏蔽字
- 运行服务程序时又来了更急的请求 → 中断嵌套:高优先级打断低优先级(条件:开中断状态下响应)
- 屏蔽字技术:每个中断源有一个屏蔽字,1 = 屏蔽(不许打断我)。把"自己 + 比自己低的"都置 1,就实现了动态调整优先级
- 手算例题:中断 A~D,响应优先级 A>B>C>D。若想让处理优先级变成 B>D>A>C,问各屏蔽字?
- 规则:处理优先级高的,屏蔽"自己及所有处理优先级比自己低的"(1=屏蔽),不屏蔽比自己高的(0=放行)
- B 最高:屏蔽 B、D、A、C → 1111
- D 次之:屏蔽 D、A、C,放行 B → 0111(按 B,D,A,C 位序)
- A 再次:屏蔽 A、C,放行 B、D → 0011
- C 最低:只屏蔽自己 → 0001
- ⚠️ 响应优先级(谁先被 CPU 看)由硬件排队器固定;处理优先级(谁能打断谁)由屏蔽字软件设——两套体系别混
多重中断 vs 单重中断
单重中断:服务期间关中断,拒绝一切打扰;多重中断:服务期间开中断,允许更高级的插队。现代 OS 全是多重。
四、DMA 方式:大块数据别劳烦 CPU
中断解决了"有事叫我",但一字一中断搬运 1MB 数据 = 100 万次中断——还是太亏。DMA(Direct Memory Access)让 DMA 控制器直接掌管总线,在设备 ↔ 主存之间整块搬运,CPU 只在开头出力。
DMA 的三种传送方式(争总线的方式)
| 方式 | 怎么用总线 | 特点 |
|---|---|---|
| 停止 CPU 访存 | DMA 传输期间 CPU 完全让路 | 控制最简单;CPU 有一段"纯空窗" |
| 周期挪用(周期窃取) | DMA 一个周期一个周期地"偷"总线 | 最常用;CPU 与 DMA 交替,DMA 优先 |
| 交替访问 | 时间上轮流,各走各拍 | 需要总线周期比存储周期长一倍,硬件复杂 |
DMA 流程(写盘为例)
- 预处理(CPU 干):设置 DMA 控制器的传送参数——主存起始地址、设备地址、传送字数,然后启动设备,CPU 转去干别的
- 数据传送(DMA 干):块级搬运,需要总线就申请(周期挪用),完全不经过 CPU 寄存器
- 后处理(CPU 干):传完后 DMA 发中断,CPU 校验数据、决定后续——⚠️ DMA 结束仍要一次中断,只是不再"一字一中断"
DMA vs 中断——必背对比表
| 维度 | 中断方式 | DMA 方式 |
|---|---|---|
| 数据流向 | 设备 → CPU 寄存器 → 主存(过路 CPU) | 设备 ↔ 主存(绕开 CPU) |
| 打断时机 | 指令执行周期结束后响应 | 总线周期(存取周期)结束后即可让路 |
| 响应速度 | 指令粒度(较慢) | 总线周期粒度(更快) |
| 适用 | 低速设备(键盘、鼠标) | 高速块设备(磁盘、网卡、SSD) |
| 硬件 | 接口 + 中断逻辑 | DMA 控制器(含地址/计数寄存器) |
| 中断次数 | 每单位数据一次 | 整块传完一次(后处理) |
💡 收伏笔:总线与接口说的"IRQ/DMA 通道"就是本篇的机制实体——每个设备申请一条 IRQ 线(中断源)或 DMA 通道。而"CPU 让出总线给 DMA"的本质是总线仲裁(06-总线:DMA 控制器也是总线主设备,链式/独立请求里的一员)。
五、通道与 IO 处理机(了解)
- 通道:能执行通道程序的专用处理器,进一步把"组块搬运的调度逻辑"也从 CPU 挪走——CPU 只发一条"启动通道"指令
- 层级:程序查询 < 中断 < DMA < 通道,CPU 介入程度逐级下降
- 大型机(IBM z 系列)用通道;个人电脑用 DMA + 多核中断分发就够了
六、盲点自测
- MMIO 和独立编址的本质区别?GPU 显存属于哪种?(是否占用主存地址空间;MMIO)
- 中断隐指令包含哪三件事?(关中断、保存断点、引出中断服务程序——硬件自动做)
- 缺页是中断还是异常?(异常/内中断,同步于指令)
- 响应优先级和处理优先级各由谁决定?(硬件排队器固定 / 屏蔽字软件设)
- DMA 三种方式里最常用的是?(周期挪用/窃取)
- DMA 结束后 CPU 为什么还要被打断一次?(后处理:校验、收尾)
- 为什么磁盘用 DMA、键盘用中断?(数据粒度:块设备整块搬,字符设备一字节一事件)
七、动手玩
# 看你机器的中断分配(macOS 用 dmesg 不直观,Linux 更清晰)
cat /proc/interrupts # Linux:每个 IRQ 号挂在哪个 CPU、是什么设备
# 注意观察:中断被"摊"到多个核上——多核中断分发的现实版
参考资料
- 王道《计算机组成原理考研复习指导》第 7 章
- CSAPP 第 8 章《异常控制流》——异常/中断/信号/进程上下文切换的统一视角
- 08-IO管理——OS 怎么在硬件之上组织 I/O(缓冲、驱动、假脱机)
💬 评论