Appearance
I/O 系统:程序查询、中断方式、DMA 方式
概念
I/O 系统负责把数据在 CPU / 主存 与 外设 之间搬来搬去。它要解决的核心问题是:外设又慢又杂(速度差几个数量级、接口五花八门),CPU 不能傻等它们。
一句话说清它是什么:I/O 系统就是"CPU 与外设之间的全部中介"——接口负责把外设的信号翻译成 CPU 能认的总线信号,控制方式决定"谁来盯着这件事"。
三种控制方式的分界线只有一条:"谁在盯着数据搬运这件事?"程序查询:CPU 亲自盯着(死等)。中断方式:设备搬完一块喊 CPU 一声(CPU 与设备并行)。DMA:DMA 控制器自己盯到底,搬完一整块才喊一声(CPU 完全不参与)。
原理
一、I/O 接口的组成
┌──────────── I/O 接口 ────────────┐
CPU / 系统总线 ──→│ 设备选择电路 │ 数据缓冲寄存器 │──→ 外设
│ (地址译码) │ (DBR) │
│ │ 状态寄存器/控制 │←──
│ │ 寄存器 (命令/状态)│
└─────────────────┴─────────────────┘| 部件 | 作用 |
|---|---|
| 设备选择电路 | 对地址译码,判断"这次总线操作是不是找我" |
| 数据缓冲寄存器 DBR | 暂存要传的数据,匹配 CPU 与外设的速度差 |
| 状态寄存器 / 控制寄存器 | 状态位(就绪、忙、错误、中断允许)+ 命令位(启动、复位) |
为什么要"缓冲":CPU 总线上的数据随时可能被换掉,而外设需要时间处理。DBR 是"速度差的仓库"。
两种编址方式(必考):
| 方式 | 做法 | 优点 | 缺点 |
|---|---|---|---|
| 统一编址(存储器映射 I/O) | I/O 端口与主存共用同一地址空间,用同样的访存指令(lw/sw)访问 | 不需要专门的 I/O 指令,寻址方式丰富 | 占用主存地址空间;无法用地址区分是内存还是设备 |
| 独立编址(I/O 映射 I/O) | I/O 端口单独一套地址空间,用专门的 I/O 指令(如 x86 的 IN/OUT) | 不占用主存空间;程序里一眼能看出是 I/O 操作 | 必须有专门的 I/O 指令;需要额外的控制线区分 |
二、三种 I/O 控制方式总览
程序查询 CPU ──[不停读状态]──> 设备 设备就绪才搬一个字
└─ CPU 全程被占用(死等)
中断方式 CPU ──[发出命令, 然后去干别的]──> 设备
CPU ←────[搬完一个字, 中断通知]── 设备
└─ CPU 与设备并行, 但每个字都要打扰一次
DMA 方式 CPU ──[设置好: 源/目的/长度, 然后完全不管]──> DMA 控制器 ──> 设备
CPU ←──[整块搬完, 只中断一次]───────────────── DMA 控制器
└─ CPU 只在开头和结尾露面三、程序查询方式(轮询 polling)
做法:CPU 发出启动命令后,反复读设备的状态寄存器,直到"就绪"位为 1,才做一次数据传送。
① CPU 发启动命令
② loop: 读状态寄存器 -> 检查就绪位
if 未就绪: 跳回 loop (★ CPU 在这里空转)
③ 就绪: 从 DBR 取/存一个字 -> 修改地址与计数器
④ 未传完: 回到 ②致命缺点:CPU 与设备完全串行——设备准备数据的那段时间,CPU 什么都不干,只是反复读状态。
唯一的优点:硬件最简单、不需要中断机制。适合"设备随时就绪、数据量很小"的场合(如读几个开关的状态)。
四、程序中断方式(详见第 33 篇)
做法:CPU 发出启动命令后立刻返回去执行原程序;等设备把数据准备好,设备主动发中断请求,CPU 响应中断、在服务程序里搬一个字,然后返回原程序。
关键改进:CPU 与设备可以并行工作——设备准备数据的同时,CPU 在干别的活。
两个固有代价:
- 每个字都要中断一次——中断开销(响应 + 保存现场 + 恢复现场)与"字数"成正比。数据量一大,开销就爆炸(见例 2)。
- 每次数据传送都经过 CPU——
设备 → DBR → CPU 寄存器 → 主存(或 CPU 直接设备 → 主存,但总线必须被 CPU 掌握)。
五、DMA 方式(直接存储器存取)
核心思想:让 DMA 控制器(DMAC)直接在主存与外设之间搬数据,CPU 完全不参与数据搬运。
DMAC 的组成:
| 部件 | 作用 |
|---|---|
| 地址寄存器(主存地址) | 当前要读写的主存地址,每传一次自动 +1(或 -1) |
| 字计数器(传送长度) | 还剩多少个字要传,每传一次 -1,减到 0 就结束 |
| 数据缓冲寄存器 | 暂存一个字 |
| 控制/状态寄存器 | 保存设备地址、传送方向、启动位等 |
| 中断机构 | 整块传完后发一次中断通知 CPU |
DMA 的完整流程:
① CPU 初始化 DMAC: 写主存起始地址 / 传送长度 / 设备地址 / 方向, 置启动位
② CPU 完全撒手, 继续执行原程序
③ DMAC 与设备逐字搬运: 每搬一个字, 地址+1, 计数-1
④ 计数器减到 0 -> DMAC 发一次中断, 通知 CPU "整块搬完了"
⑤ CPU 响应中断, 做收尾DMA 与中断的三点本质区别(必考):
| 对比项 | 中断方式 | DMA 方式 |
|---|---|---|
| 数据传送路径 | 经过 CPU(设备 ↔ DBR ↔ CPU ↔ 主存) | 不经过 CPU(设备 ↔ 主存直连) |
| 打断的是谁 | 打断程序(当前指令执行完就响应) | 打断的是存取周期(抢总线,不打断指令) |
| 请求的时机 | 每个字准备好就请求一次 | 整块传完才请求一次 |
| 优先级 | 低 | 高(因为不及时响应会导致数据丢失) |
| 适用场合 | 少量、低速设备 | 大块、高速设备(磁盘、网卡、显卡) |
DMA 的三种传送方式(谁来让总线):
| 方式 | 做法 | CPU 影响 | 效率 |
|---|---|---|---|
| 停止 CPU 访存 | DMA 传输期间完全接管总线,CPU 停摆 | 整块数据传输期间 CPU 完全不能访存 | 简单,但CPU 被浪费(适合高速大块) |
| 周期挪用 / 周期窃取(最常用) | DMA 每次只抢一个存储周期,抢完立刻还给 CPU | CPU 只被推迟一拍(DMA 优先) | 效率高,是主流做法 |
| 交替访问 | 把一个存储周期劈成两半,前一半给 CPU、后一半给 DMA | CPU 完全不受影响(各走各的半拍) | 要求主存速度足够快(CPU 时间片与 DMA 时间片都缩短) |
周期挪用为什么是主流:它让 CPU 与 DMA 分时用同一个主存,各让一步——DMA 抢到的那一拍 CPU 停一下,抢完立即归还。代价小、实现简单、效率高。
⚠️ DMA 请求的优先级必须高于 CPU:因为磁盘等高速设备的数据一旦就绪不被立即取走就会丢失(覆盖)——这跟中断"可以晚几拍再处理"完全不同。
六、三种方式的量化对照
| 对比项 | 程序查询 | 程序中断 | DMA |
|---|---|---|---|
| CPU 参与程度 | 全程死等 | 每个字参与一次 | 只参与开头与结尾 |
| CPU 与设备并行 | 不能 | 能(准备期间) | 能(整块期间) |
| 数据传送经过 CPU | 是 | 是 | 否 |
| 打断的对象 | 无(主动查询) | 程序 | 存取周期(总线) |
| 每块的中断次数 | 0(但 CPU 全程占用) | 字数次 | 1 次 |
| 硬件复杂度 | 最低 | 中(需中断系统) | 最高(需专用控制器) |
| 适用场合 | 低速、少量 | 中低速、随机 | 高速、大块 |
一句话总结:"谁在盯着搬运"决定了一切——CPU 死盯(查询)→ CPU 每字盯一下(中断)→ DMAC 全程盯,CPU 只收尾(DMA)。
示例
例 1:程序查询方式的时间账
某机 CPU 主频 1 GHz。某 I/O 设备传输率 4 MB/s,每次准备 1 个字(4 B)。CPU 每查询一次状态寄存器需要 10 个时钟周期,查到就绪后搬运 1 个字需要 5 个时钟周期。求搬 1 个字期间 CPU 有多少是"有效工作"。
完整计算过程:
第一步,设备准备 1 个字需要的时间:
第二步,换算成 CPU 周期(1 GHz → 1 周期 = 1 ns):
第三步,这段时间里 CPU 要查询多少次(每次 10 周期):
第四步,搬 1 个字 CPU 总共花掉:
第五步,其中真正有效的部分:
结论:CPU 有 99.5% 的时间在做无用功——读状态 → 判断 → 跳回 这个循环。这就是程序查询方式的本质缺陷:它的开销不取决于数据量,而取决于"设备的慢"。
⚠️ 常见错误:
- 把"查询次数"算成 1 次(以为查一次就能读到)。设备准备 1000 ns,CPU 每 10 ns 查一次,必然要查 100 次。
- 忘了加"搬数据"的 5 周期(虽然它相对很小,但漏掉会让"有效率"变成 0——那是概念错)。
例 2:中断方式的账——为什么它会"撞墙"
承接例 1。改用中断方式。每次中断 CPU 开销:响应与现场保护 40 周期,中断服务程序 50 周期。分别求设备传输率为 4 MB/s 与 40 MB/s 时,CPU 被中断机制占用的比例(每次传 1 个字 = 4 B)。
完整计算过程:
第一步,单次中断的 CPU 开销:
第二步,情形(1):设备 4 MB/s。设备准备 1 个字的时间:
CPU 开销占比(每 1000 周期就要花 90 周期处理中断):
第三步,情形(2):设备 40 MB/s(提速 10 倍)。设备准备 1 个字的时间:
对比:
| 设备传输率 | 每字准备时间 | 每次中断开销 | CPU 占用率 |
|---|---|---|---|
| 4 MB/s | 1000 周期 | 90 周期 | 9.00% |
| 40 MB/s | 100 周期 | 90 周期 | 90.00% |
结论:设备提速 10 倍,CPU 占用率涨了 10 倍——因为中断开销是固定的 90 周期,而"摊它的时间"却缩短了 10 倍。
这才是中断方式的真问题:它的开销与"中断次数"成正比,而中断次数与"每次搬的字节数"成反比。 把每次搬的字节数加大(如每 1 KB 中断一次)确实能降开销,但块越大、CPU 的响应越迟——在实时与高速场景里,这个取舍是致命的。DMA 正是为了绕开这个取舍而生。
例 3:DMA 的账——CPU 只出 1%
某机 CPU 主频 1 GHz。用 DMA 方式从高速设备(传输率 40 MB/s)把 4 KB 数据块读入主存。DMA 采用周期挪用方式:每传 1 个字(4 B)挪用 1 个存储周期(= 1 个 CPU 时钟周期)。DMA 初始化需要 50 周期,结束中断开销 90 周期。求 DMA 传输 4 KB 时 CPU 的占用比例,并与中断方式对比。
完整计算过程:
第一步,设备侧完成 4 KB 传输需要的时间:
第二步,块内共有多少个字:
第三步,DMA 挪用总线占用的 CPU 时间:
第四步,加上初始化与结束中断:
第五步,CPU 占用比例:
与中断方式对比(同样传 4 KB,中断方式每字一次):
节省倍数:
结果汇总表:
| 方式 | CPU 占用时间 | CPU 占用率 | 每块中断次数 |
|---|---|---|---|
| 程序查询 | 102.4 μs | 100% | 0(但 CPU 全程空转) |
| 程序中断 | 92.2 μs | 90.0% | 1024 |
| DMA | 1.164 μs | 1.137% | 1 |
结论:传同一块 4 KB 数据,DMA 只用掉 CPU 的 1.14%,中断方式却要 90%——省了 79.2 倍。
再看一个"根本做不到"的边界:若用中断方式传 4 KB,CPU 开销 92.2 μs 几乎等于设备传完这一块所需的 102.4 μs——这意味着 CPU 几乎全程都在处理中断,别的程序基本跑不动。而设备传输率再往上提(如 400 MB/s),中断方式的 CPU 占用率会直接超过 100%——物理上不可能实现。这就是"高速大块传输必须用 DMA"的硬理由。
例 4:DMA 三种传送方式的取舍
承接例 3。若把"周期挪用"分别换成"停止 CPU 访存"与"交替访问",分析 CPU 的可用时间。
三种方式的定性对比:
| 方式 | 总线使用 | CPU 可用时间 | 说明 |
|---|---|---|---|
| 停止 CPU 访存 | DMA 独占整块传输期间 | 约 0(102.4 μs 内完全不能访存) | 简单;只适合"传输期间 CPU 无事可做"或极高优先级的场景 |
| 周期挪用(例 3 口径) | 每字抢 1 拍,抢完即还 | 1024 拍被推迟,其余正常 | 主流做法:CPU 只被"减速"一点点 |
| 交替访问 | 存储周期劈两半 | 完全不受影响 | 要求主存速度至少翻倍——因为半拍给 CPU、半拍给 DMA |
"停止 CPU 访存"的浪费量化:
结论:"停止 CPU 访存"浪费的时间是"周期挪用"的 100 倍(本例中恰好等于"块大小 ÷ 字大小"的口径)。
"交替访问"的代价:
即主存必须比"只伺候 CPU"时快一倍。这是用钱(更快的主存)换"CPU 完全不受影响"。
⚠️ 常考辨析:
- "周期挪用"也叫"周期窃取"——同一个东西的两个名字(
cycle stealing),别当成两种方式。 - "停止 CPU 访存"不是"停止 CPU":CPU 仍在执行不需要访存的指令(纯寄存器运算),只是访存请求会被 DMA 挡住。严格说是"CPU 失去总线控制权"。
- "交替访问"只适用于"CPU 与 DMA 都在持续访存"的场景(如 CPU 跑内存密集程序 + DMA 搬大块);若 CPU 本来就不访存,交替访问的"半拍"就白分了。
例 5:C 实现——三种方式的 CPU 开销对照
#include <stdio.h>
/* 三种 I/O 控制方式的 CPU 占用估算
* 口径: CPU 1 GHz(1 周期 = 1 ns), 每传 1 个字 = 4 B
* 中断: 响应+现场 40 周期, 服务程序 50 周期
* DMA : 每字挪用 1 个存储周期, 初始化 50 周期, 结束中断 90 周期
*/
int main(void) {
const double rate = 40e6; /* 设备传输率 B/s */
const int block = 4096; /* 一次搬 4 KiB */
const int chunk = 4; /* 每次搬 1 个字 = 4 B */
const int irq_overhead = 40 + 50; /* 单次中断开销 */
const int dma_setup = 50; /* DMA 初始化 */
const int dma_steal = 1; /* DMA 每字挪用周期 */
long n_word = block / chunk;
double t_block = (double)block / rate * 1e9; /* 设备侧耗时 ns */
double irq_ns = (double)n_word * irq_overhead;
double dma_ns = (double)n_word * dma_steal + dma_setup + irq_overhead;
printf("设备 %.0f MB/s, 块 %d B -> 设备侧耗时 %.1f us\n",
rate / 1e6, block, t_block / 1000);
printf("PIO 查询: CPU 全程空转 = %8.1f ns (%.1f%%)\n", t_block, 100.0);
printf("中断 方式: %ld 次 x %d = %.0f ns (%.1f%%)\n",
n_word, irq_overhead, irq_ns, irq_ns / t_block * 100);
printf("DMA 方式: %ld x %d + %d + %d = %.0f ns (%.2f%%)\n",
n_word, dma_steal, dma_setup, irq_overhead,
dma_ns, dma_ns / t_block * 100);
printf("DMA 比中断省 CPU %.1f 倍\n", irq_ns / dma_ns);
/* 敏感度: 设备提速到 400 MB/s 时, 中断方式还撑得住吗 */
double t_fast = (double)block / 400e6 * 1e9;
printf("--- 设备提速到 400 MB/s ---\n");
printf("设备侧耗时 %.1f us, 中断方式需 %.1f us -> CPU 占用 %.1f%%\n",
t_fast / 1000, irq_ns / 1000, irq_ns / t_fast * 100);
printf("DMA 方式 CPU 占用 %.2f%% (几乎不变)\n", dma_ns / t_fast * 100);
return 0;
}
c 本站为静态站,不提供在线运行;可复制到本地用 gcc / python 执行
预期输出:
设备 40 MB/s, 块 4096 B -> 设备侧耗时 102.4 us
PIO 查询: CPU 全程空转 = 102400.0 ns (100.0%)
中断 方式: 1024 次 x 90 = 92160 ns (90.0%)
DMA 方式: 1024 x 1 + 50 + 90 = 1164 ns (1.14%)
DMA 比中断省 CPU 79.2 倍
--- 设备提速到 400 MB/s ---
设备侧耗时 10.2 us, 中断方式需 92.2 us -> CPU 占用 900.0%
DMA 方式 CPU 占用 11.37% (几乎不变)最后一行的 900.0% 是这场对比的关键:设备提速到 400 MB/s,中断方式"需要的 CPU 时间"是设备传输时间的 9 倍——物理上不可能(一个 CPU 不能同时干 9 件事)。而 DMA 的占用率只从 1.14% 涨到 11.37%(涨了 10 倍,因为设备时间缩短了 10 倍,但绝对开销几乎没变)。这就是 DMA 存在的量化证明。
(double)block / rate里的(double)不能省:block和chunk都是int,若写成block / rate,int会先转double参与除法(这里恰好没问题);但写成block / chunk求字数时是整数除法——那正是我们要的(1024)。什么时候要(double)什么时候不要,取决于你要的是"精确值"还是"商",这是 C 计算题最容易翻车的地方。
例 6:Python——三种方式的开销全表验算
def overhead(rate, block=4096, chunk=4, irq_ovh=40 + 50, dma_setup=50, dma_steal=1):
"""返回 (设备侧耗时 ns, 查询占用, 中断占用, DMA 占用) —— 单位都是 ns"""
t_block = block / rate * 1e9
n_word = block // chunk
irq = n_word * irq_ovh
dma = n_word * dma_steal + dma_setup + irq_ovh
return t_block, t_block, irq, dma
print('=== 例 1~3: 设备 40 MB/s, 块 4 KiB ===')
t, pio, irq, dma = overhead(40e6)
print('设备侧块传输时间 = 4096 / 40e6 = %.1f us' % (t / 1000))
print(' 程序查询: CPU 全程空转 -> %.1f ns (%.1f%%)' % (pio, 100.0))
print(' 中断方式: 1024 次 x 90 = %.0f ns (%.1f%%)' % (irq, irq / t * 100))
print(' DMA 方式: 1024x1 + 50 + 90 = %.0f ns (%.3f%%)' % (dma, dma / t * 100))
print(' DMA 比中断省 CPU %.1f 倍' % (irq / dma))
print('\n=== 例 2: 中断方式随设备提速 ===')
for rate in [4e6, 40e6, 400e6]:
ready = 4 / rate * 1e9
print(' 设备 %6.0f MB/s -> 每字就绪 %6.0f ns -> 中断占用率 %6.2f%%'
% (rate / 1e6, ready, 90 / ready * 100))
print('\n=== 例 3: 设备提速到 400 MB/s 时三种方式 ===')
t2, pio2, irq2, dma2 = overhead(400e6)
print(' 设备侧耗时 %.2f us' % (t2 / 1000))
print(' 程序查询 %.1f%% | 中断 %.1f%% (不可能!) | DMA %.2f%%'
% (100.0, irq2 / t2 * 100, dma2 / t2 * 100))
print(' -> CPU 占用超过 100% 意味着物理上做不到, 必须换 DMA')
print('\n=== 例 4: DMA 三种传送方式对比 (块 4 KiB) ===')
print(' 停止 CPU 访存: CPU 让出 %.1f us (整块期间)' % (t / 1000))
print(' 周期挪用: CPU 只被推迟 %.3f us' % (1024 / 1000))
print(' 交替访问: CPU 不受影响, 但主存要快一倍')
print(' 停止/挪用 的浪费比 = %.0f 倍 (= 块大小/字大小)' % (t / 1024))
print('\n=== 附: 统一编址 vs 独立编址 ===')
for tag, detail in [
('统一编址', 'I/O 端口与主存共用地址空间, 用 lw/sw 访问, 不占额外指令, 但吃掉主存地址空间'),
('独立编址', 'I/O 端口单独地址空间, 用专门 I/O 指令(IN/OUT), 不占主存空间, 但需专门指令与信号线'),
]:
print(' %s: %s' % (tag, detail))
python 本站为静态站,不提供在线运行;可复制到本地用 gcc / python 执行
输出对照:例 1~3 得设备侧 102.4 μs,查询 100%、中断 90.0%、DMA 1.137%(省 79.2 倍);例 2 显示中断占用率随设备提速而线性上升(4 / 40 / 400 MB/s → 9.00% / 90.00% / 900.00%);例 3 在 400 MB/s 下中断方式需 900%(不可能),DMA 仍只有 11.37%;例 4 得"停止 CPU 访存"的浪费是"周期挪用"的 100 倍(= 块大小 ÷ 字大小)。全部与手算一致。
口径提醒:例 2 的"就绪间隔 90/ready"与例 1~3 的"整块开销率"是两个不同口径——前者是"设备准备一个字的时间",后者是"整个块的时间"。答题时务必写清用的是哪一个,同一道题里不要混用。
考点
考点
1. 必背结论
- I/O 接口三件套:设备选择电路(地址译码)+ 数据缓冲寄存器 DBR + 状态/控制寄存器。
- 统一编址:与主存共用地址空间,用访存指令访问,占主存空间;独立编址:单独空间,用专门 I/O 指令,不占主存空间。
- 三种控制方式的分界 = "谁在盯着搬运":CPU 死等(查询)、CPU 每字看一眼(中断)、DMAC 全程盯着(DMA)。
- DMA 的组成:主存地址寄存器 + 字计数器 + 数据缓冲寄存器 + 控制/状态寄存器 + 中断机构。
- DMA 三种传送方式:停止 CPU 访存、周期挪用(= 周期窃取,最常用)、交替访问。
- DMA 请求的优先级高于中断请求(不及时响应会丢数据)。
- DMA 与中断的三点区别:数据不经过 CPU;打断的是存取周期而非程序;整块传完才请求一次(中断是每个字一次)。
2. 高频陷阱
- 把"中断方式"和"DMA"的请求频率搞混:中断是每字一次,DMA 是每块一次。这是最核心的区别,也是全部计算题的起点。
- 说"DMA 不产生中断":错。DMA 结束时会产生一次中断(通知 CPU 收尾),只是传输过程中不中断。
- 说"DMA 不需要 CPU 参与":错。CPU 必须初始化 DMAC(写源地址、长度、方向),并在结束时处理中断。
- 把"周期挪用"当成"停止 CPU 访存":一个是"抢一拍就还",一个是"整块期间全占"。名字相近,代价差 100 倍(例 4)。
- 以为"停止 CPU 访存 = 停止 CPU":CPU 仍可执行不访存的指令,失去的只是总线控制权/访存能力。
- 中断方式的占用率算成"与块大小无关":它与每字一次的固定开销成正比,与设备速度成正比(例 2:4 MB/s 是 9%,40 MB/s 就变成 90%)。
- "CPU 占用率超过 100%"当成能算出来的数:一旦超过 100%,说明该方案在物理上不可实现——这恰恰是答题时应该主动指出的一点(例 3 的 900%)。
- 统一编址说成"节省内存":恰恰相反,统一编址会占用主存地址空间;节省的是"指令"(不需要专门 I/O 指令)。
- 单位换算:设备速率
与 差 8 倍; 与 差 1000 倍。例 3 里 102.4 μs = 102400 ns,这个换算是全题基础。
3. 解题模板("I/O 方式对比计算题")
① 先算"设备侧时间": t = 数据量 / 设备传输率
② 程序查询: CPU 占用 = t (100%) <- 全程空转
③ 中断方式: 中断次数 = 数据量 / 每次字节数
CPU 占用 = 中断次数 x 单次中断开销
单次开销 = 响应 + 现场保护 + 服务程序 + 现场恢复
④ DMA 方式: CPU 占用 = (字数 x 每字挪用周期) + 初始化 + 结束中断
<- DMA 只有 1 次中断!
⑤ 三者之比 = DMA : 中断 : 查询
⑥ 若某方式 CPU 占用 > 100% -> 明确写出"该方案物理上不可实现"4. 与相邻章节的接口
- 第 33 篇(异常与中断):中断处理程序(关、存、引 + 保存/恢复现场)就是本篇"中断方式"的成本来源;例 2 的 90 周期开销直接从第 33 篇例 3 的 445 周期缩放而来,口径一致。
- 第 40 篇(总线):DMA 的"周期挪用"抢的就是总线/存储周期;本篇例 3 里"每字挪用 1 个存储周期"就是第 40 篇总线仲裁机制的落地场景。总线带宽也直接决定 DMA 能跑多快。
- 第 12 篇(Cache):DMA 直接访问主存会绕过 Cache → 造成缓存一致性问题(主存被 DMA 改了,Cache 里还是旧值)。这是现代系统里"DMA + Cache 一致性"需要专门处理的根源。
- 第 32 篇(流水线):DMA 抢存储周期 = 给流水线的访存段插入停顿,表现为"流水线偶发卡顿";这就是"周期挪用"的隐性成本。
- L4 操作系统(
os):DMA 与中断正是 OS 设备管理的硬件基础——设备驱动要注册中断处理程序、要申请 DMA 缓冲区;os/33-disksched.md讲的磁盘调度,最终都要落到 DMA 传输上。下一篇我们正式进入操作系统层。
小结
- 三种 I/O 控制方式的分界线是一条:谁在盯着数据搬运——CPU 死等(查询)、CPU 每字看一眼(中断)、DMAC 全程盯着(DMA)。
- 程序查询:CPU 与设备完全串行,例 1 里有效率只有 0.50%。硬件最简单,但只适合低速少量。
- 中断方式:CPU 与设备可并行,但每个字都要中断一次。例 2 显示它随设备提速而线性恶化(4 → 40 MB/s 时占用率 9% → 90%)。
- DMA 方式:数据不经过 CPU,整块只中断一次。例 3 里传 4 KB 只占 CPU 1.137%,比中断方式省 79.2 倍;设备提速到 400 MB/s 时中断方式需 900%(不可能),DMA 仍只有 11.37%。
- DMA 三种传送方式:停止 CPU 访存(整块占用,浪费是周期挪用的 100 倍)、周期挪用(主流)、交替访问(主存要快一倍)。
- DMA 请求优先级高于中断(不响应就丢数据);DMA 结束仍会中断一次(只是传输过程不中断)。
arch 到此收口。回头看这条线:数据怎么表示(02/03)→ 怎么运算(04)→ 存在哪里(10~13)→ 指令怎么说(20~22)→ CPU 怎么执行(30~31)→ 怎么更快地执行(32)→ 出事了怎么办(33)→ 部件之间怎么连(40)→ 慢设备怎么接(41)。整门课的落点是同一句话:硬件把"能力"给出来,但怎么用好这些能力,是软件的事。 这正是下一门课要回答的问题。
下一篇:指令集与寄存器组织(进入 L3 语言层 · 汇编与 C)
评论(0)
当前浏览器不允许本地存储,评论无法保存。
还没有评论,来说两句。