第 9 章 · 输入输出系统

让慢设备不拖死快 CPU。三种交互方式(轮询/中断/DMA)= 谁来盯着设备;再串总线、外设接口、中断流程。

9.1 三种 I/O 方式:轮询 vs 中断 vs DMA

轮询 Polling中断 InterruptDMA
谁主动CPU 反复查状态寄存器设备就绪后「打断」CPUDMA 控制器搬数据,CPU 只管起停
CPU 代价忙等,全程占用只在响应时打断,可干别的几乎不参与,最后收中断
延迟取决于轮询频率快,但有中断开销搬完才通知
适用设备极快、或极低频事件驱动、通用大批量块传输(磁盘/网卡)

对比:核心权衡是「CPU 空转 vs 响应开销」。轮询适合设备永远瞬间就绪(省上下文切换);中断适合偶发事件;DMA 把「设备→内存」这块最累的活外包给 DMA 控制器。

轮询: CPU ──查状态──▶ 设备 …… CPU 空转等
中断: 设备 ──就绪,发中断──▶ CPU 打断当前任务去处理
DMA:  设备 ──DMA控制器──▶ 内存(数据直搬),完成后再中断 CPU

9.2 总线:地址 / 数据 / 控制

总线是 CPU、内存、外设间的公共通道,分三类信号:

  • 地址总线:单向(CPU→设备),宽度定可寻址范围(n 位寻址 2ⁿ 单元)。
  • 数据总线:双向,宽度定一次传多少位(带宽 = 数据宽度 × 频率)。
  • 控制总线:读/写、中断请求、总线请求、时钟等。

总线仲裁:多个设备抢总线时由仲裁器按优先级/轮转决定谁能用——否则两设备同时驱动会总线冲突。

注意:早期单总线共享,慢设备拖慢快设备(木桶效应);现代用多级总线/点对点(PCIe 点对点串行),高速设备走专属通道。「总线」如今更像历史概念——PCIe 用交换式点对点取代共享总线,但地址/数据/控制的抽象仍在。

9.3 外设接口:寄存器 + 编址

CPU 与外设交互的最小接口是设备寄存器(状态/数据/命令寄存器)。两种编址:

方式做法特点
端口映射 I/O独立 I/O 地址空间,专用 in/out 指令x86 传统
内存映射 I/O(MMIO)外设寄存器映射进内存地址空间,用普通访存指令RISC 主流,统一

对比:MMIO 把「访问外设」变「读写某段内存地址」,无需专门指令、能走 Cache 机制——但外设寄存器通常禁用 Cache(设备状态自己会变,缓存了读旧值),正是第 7 章「MMIO 强制写直达」的原因。

9.4 中断处理流程

设备就绪 → 发中断请求(IRQ)→ CPU 在当前指令边界响应:

① 设备拉高中断请求线
② CPU 完成当前指令,检查是否有中断且未屏蔽
③ 关中断,保存现场(PC、寄存器压入内核栈)
④ 根据中断向量/原因,跳转到中断服务程序(ISR)
⑤ ISR 处理(读状态、取数据、清中断)
⑥ 恢复现场,开中断,返回被中断的程序
  • 中断向量:一张「中断号→服务程序入口」的表,硬件查表跳转(比逐个判断快)。
  • 保存/恢复现场:被中断程序不知自己被打断,PC、关键寄存器必须原样恢复——中断透明性的代价,与函数调用保存寄存器(第 10 章)同构,但在硬件/内核层。
  • 关中断:处理中断时屏蔽新中断避免嵌套;或分级(高优先级可抢占低)。

对比:中断/异常/系统调用用同一套「打断+保存现场+向量跳转」,来源不同:中断来自外部设备(异步);异常来自指令执行出错(同步:除零、缺页、段错误);系统调用是程序主动请求(int 0x80/syscall)。第 8 章缺页即「异常」。

9.5 与各章 / 操作系统衔接

  • 第 8 章:缺页 = 异常→中断服务→磁盘 DMA 读入→恢复进程,一条链用上三种 I/O 方式。
  • 第 5 章:5.5 的「中断周期」即「CPU 在指令边界检查中断」那一步。
  • OS 第 7 章(设备管理):本章讲硬件怎么中断/DMA,OS 讲驱动组织、中断调度、I/O 调度算法(电梯算法)。
  • C/Java/Python:读文件/收网络包最终落到 read()/recv() 系统调用,内核走中断+DMA;asyncio、NIO 的非阻塞 I/O 本质是把等待从「阻塞线程」改成「事件通知」。