我们平时总说“磁盘快不快”、“SSD 和机械硬盘差距有多大”,但很少有人真正去想过一个问题:CPU 到底是怎么把磁盘上的数据拿过来的?这个问题拆开来看,就是标题里那串“1.3磁盘-输入输出技术-总线”真正要回答的事情。它看起来像教材目录里一个干巴巴的章节名,实际上它是计算机体系结构里最容易被忽略、却最容易让程序变慢的隐形瓶颈。无论是考研复习、面试准备,还是写代码时遇到诡异的 IO 卡顿,搞懂这块内容,很多问题会瞬间清晰。
这篇文章我会从一个最实际的场景出发:你按下回车,程序要从磁盘读一个文件,这中间的数据到底走了哪条路?谁在指挥、谁在执行、谁在等谁?把这条链路拆完,磁盘、IO 控制方式(程序查询、中断、DMA)、总线这三件事自然就串起来了。整个内容适合三类人:正在复习计算机组成原理的学生、准备硬件/底层方向面试的开发者、以及所有想弄明白“IO 密集型的瓶颈到底在哪”的工程师。阅读前只需要有一点基础:知道 CPU 有寄存器、内存里有地址、磁盘上按扇区存数据,就够了。
1. 磁盘在系统里的真实位置:它为什么不能“直接插在 CPU 上”
先抛一个问题:为什么 CPU 不直接去磁盘上读数据,非要经过内存、经过一堆控制器、经过总线?这个问题想清楚了,后面所有“为什么这么设计”的答案就都顺了。
1.1 机械硬盘的原理与速度差距
磁盘分两类,机械硬盘(HDD)和固态硬盘(SSD),但先看机械硬盘,因为它的物理性质最能说明问题。机械硬盘的内部是一张高速旋转的盘片,磁头悬浮在盘片上方几纳米的位置读写数据。读一个数据块,磁头需要先移动到对应磁道(寻道),再等盘片把目标扇区转到磁头下(旋转延迟),然后才开始真正读写。一块 7200 转的硬盘,平均寻道时间大约 8~12 毫秒,半圈旋转延迟大约是 4 毫秒,也就是说随机读一个扇区,光“定位”这个动作就要消耗 10 毫秒以上。
而 CPU 执行一条指令的时间是多少?现代 CPU 主频 3GHz 以上,一个时钟周期大约 0.3 纳秒,执行一条简单指令不过几个纳秒。两者之间差了几个数量级:CPU 等一次磁盘定位的时间,足够它执行几十万到几百万条指令。这就好比你叫外卖,骑手在小区门口绕了三圈还没找到楼栋,而你在楼上已经把明天的工作计划全部写完了。如果 CPU 直接去“取数据”,它就是在傻等这种慢三个数量级的操作,整个系统的性能会被磁盘拖死。
1.2 SSD 也没有改变问题的本质
固态硬盘没有机械运动,随机读的延迟通常在 20~100 微秒级别,比机械硬盘快了两个数量级。但跟 CPU 纳秒级的速度相比,仍然慢了几百倍。而且 SSD 的寿命特性、脏盘降速问题,决定了它也不能被 CPU 当作内存那样直接按总线周期访问。它内部有闪存转换层来做地址映射、垃圾回收、磨损均衡,这些工作本身就相当于一个小型的“内部 IO 系统”。
所以无论 HDD 还是 SSD,磁盘都只能作为一个慢速外设存在,连接在 I/O 总线上,通过专门的控制器与 CPU 间接交互。这是理解后续内容的第一块基石:CPU 不直接跟磁盘打交道,而是通过“控制器 + 总线 + IO 技术”这套中介体系。
1.3 磁盘控制器:磁盘的“代言人”
磁盘控制器(比如早期的 IDE 控制器、后来的 SATA 控制器、现在的 NVMe 控制器)是磁盘侧真正的“大脑”。CPU 不需要知道磁头怎么移动、闪存页怎么编程,它只需要把命令发给控制器——比如“把 LBA 地址 12345 起始的 8 个扇区读出来放到内存 0x20000 处”——剩下的脏活累活都是控制器干。
控制器通过中断告诉 CPU“活干完了”。所以磁盘在系统中的位置,本质上是“磁盘 → 控制器 → 主机总线 → CPU/内存”这样一条链路。总线在这里承担的角色是“物理通道”,而输入输出技术承担的是“协作协议”。两者缺一不可。
2. 输入输出技术的三种境界:从 CPU 全程盯守到彻底放手
既然 CPU 不能直接读磁盘,那它到底怎么跟外设协作?教材里通常讲三种方式:程序查询方式、中断方式、DMA 方式。我习惯把这三者理解为“队长的三种管理风格”:寸步不离地盯、被叫了才去处理、直接派助理去办。
2.1 程序查询方式:CPU 的“死等模式”
程序查询方式(Programmed I/O,PIO)的逻辑最简单:CPU 发起一个 IO 操作,然后不停地去读外设控制器的状态寄存器,看“数据好了没有”。没好的话就继续读、继续等,好了才把数据搬进内存。
这种方式在早期的串口调试、简单的 GPIO 操作里还能见到,但它的致命问题一眼就能看出来:整个等待期间 CPU 什么都干不了。假设磁盘读一个扇区要 10 毫秒,CPU 就在那循环读状态寄存器读 10 毫秒,这 10 毫秒对于以纳秒为工作单位的 CPU 来说,浪费是灾难级的。
我见过一个真实的例子:某早期开发板上用 GPIO 模拟时序读取传感器,程序员图省事用了查询方式,结果 CPU 占用率常年 100%,系统跑啥都卡。后来换成中断驱动,同样的功能 CPU 占用直接掉到 3%。查询方式不是不能用,而是只能用于外设速度很快、或者等待时间极短的场景。判断标准很简单:如果外设响应时间明显大于 CPU 执行一段普通指令的时间,就别用查询方式,否则就是在用 CPU 的寿命换代码的简单。
还有一个隐藏问题:查询方式下,如果外设一直没好,CPU 就一直在循环里空转,这时候即使有高优先级的中断来了,也要看这个循环是否允许中断嵌套。很多早期嵌入式代码里,查询循环会关中断,这就更危险了——极端情况下系统会“卡死”,实际上是 CPU 在等一个永远没完成的外设。
2.2 中断方式:CPU 的“被动响应模式”
中断方式解决了“CPU 傻等”的问题,核心思想是:CPU 发起 IO 请求之后,就回去执行自己的程序;外设准备好数据之后,通过中断控制器向 CPU 发一个中断信号;CPU 在执行完当前指令后,响应中断,转去执行中断服务程序(ISR),在 ISR 里把数据从外设缓冲区搬到内存或者直接搬进 CPU 寄存器。
这个类比很贴切:你不是守在门口等快递,而是该干嘛干嘛,快递到了门铃响一下,你再去门口取。中断方式下,CPU 的时间利用率大幅提升,可以“同时”处理多个外设——其实不是同时,而是轮流响应,但因为 CPU 速度极快,宏观上看起来就是“并发”的。
但是中断方式有一个不能回避的问题:每次中断都有开销。CPU 要保存断点(压栈)、跳转到中断向量、执行 ISR、处理完恢复现场,这一整套过程通常要几十到几百个 CPU 周期的开销。如果外设频繁地一次只传一个字节,比如老式串口 115200 波特率,每个字节都触发一次中断,那么高频中断本身就会吃掉不少 CPU 时间。这在网络传输里体现得尤其明显,所以后来才发展出中断合并、NAPI 这种批量收包机制来降低中断频率。
中断方式还有一个教材常考的点:中断处理过程中,有没有可能丢失数据?有。如果外设的下一个数据已经准备好了,而 CPU 还在处理上一个中断,那么数据可能会被覆盖或丢失。所以硬件上通常会做缓冲区(比如串口的 FIFO),软件上要尽量缩短 ISR 的执行时间,把耗时操作放到下半部/任务队列里去。这个思路从嵌入式的中断服务程序到 Linux 内核的硬中断/软中断机制,一脉相承。
2.3 DMA 方式:CPU 的“甩手掌柜模式”
中断方式虽然不再傻等,但有个关键问题没解决:数据搬运这件事本身,还是得 CPU 来干。在 ISR 里,CPU 要把数据从磁盘控制器的数据寄存器读出来,再写入内存的指定区域,这个过程叫 PIO 拷贝。一个扇区 512 字节,可能还好,但如果一次要读 1MB 数据,CPU 就要一条条指令地搬几十万次,这期间 CPU 同样没法干别的。
DMA(Direct Memory Access,直接存储器访问)就是为了解决这个问题:CPU 只负责“交代任务”,把源地址、目的地址、传输长度告诉 DMA 控制器,然后 DMA 控制器直接接管总线,自己把数据从外设搬到内存,或者从内存搬到外设,全程不需要 CPU 参与指令执行。传输完成之后,DMA 控制器再发一个中断通知 CPU“搞定了”。
这里有个非常关键的专业细节,也是很多人学这块时最容易迷糊的点:DMA 控制器到底怎么“使用”总线?DMA 控制器不是凭空多出来的一条路,它本身就是一个总线主设备。它要从磁盘控制器拿到数据,就得在总线上发起读事务;要把数据写到内存,就得在总线上发起写事务。这里面最常见的争用问题是:DMA 和 CPU 谁优先用总线?这就引出了总线仲裁的两种策略。
第一种是停止 CPU 访问主存(让路式):DMA 传输期间,CPU 完全放弃总线控制权。这种方式实现简单,但代价是 CPU 在这段时间里连内存都访问不了(指令预取、数据读写全停),相当于“为了搬数据,CPU 被迫停顿”。第二种是周期挪用(cycle stealing):DMA 在 CPU 不访问主存的周期里偷偷插入一个传输周期,一次传一个字节或一个字。这种方式对 CPU 影响小,但 DMA 的传输速度变慢,因为要等 CPU 的“空档”。实际系统里,片内 DMA 控制器通常采用周期挪用或更高级的多种模式混合。
DMA 的引入,本质上把“IO 控制”从 CPU 的指令层面,下沉到了硬件控制器的数据传输层面。这是一次非常重要的分权:CPU 负责控制流(发什么命令、怎么处理结果),DMA 负责数据流(从哪里搬到哪里)。这个思想一直到今天都没有变,只是 DMA 引擎从独立的芯片进化到了集成在 SoC 里的模块(比如 PC 上的多个 DMA 通道、网络网卡的 DMA ring buffer)。
2.4 三种方式的取舍:不止看速度,还要看场景
很多人背这三种方式的时候,只知道“查询慢、中断好、DMA最好”,这其实是对教材最大的误读。三种方式各有各的适用场景:
- 程序查询方式:外设极快(比如 GPIO 读一个引脚的电平)、数据量极小、CPU 等得起的情况下,查询方式反而最直接、最可控,因为不用处理中断上下文切换。比如 U-Boot 里的串口早期输出,基本就是查询等待。
- 中断方式:数据量较小、但事件不可预知(比如键盘按键、网卡帧到达)的场景。这时候中断能最大限度节省 CPU 轮询开销,并且响应及时。
- DMA 方式:数据量较大、数据传输以块为单位(磁盘扇区、网络报文、声卡音频流)的场景。因为大块数据逐个字节搬运会占用大量 CPU 时间,DMA 的优势是决定性的。
还有一个容易被忽略的:中断方式同样可以用来配合 DMA。现代磁盘控制器几乎都是 DMA + 中断的组合。CPU 只是给 DMA 控制器配置好描述符,然后进程睡眠;DMA 传完数据后发中断,CPU 在中断里只是简单确认一下,然后把 DMA 完成的通知交给内核继续处理。这就是“一次大传输 + 一次中断通知”的模式,也是效率最高的主流方案。
3. 总线:连接一切的那根“公共走廊”
说完了 IO 技术,轮到“总线”登场。总线这个词听起来很抽象,其实就是一组共享的物理线路(导线),用于在计算机的各个部件之间传输数据。它相当于一座城市里的主干道:CPU、内存、磁盘控制器、网卡、显卡,这些部件都通过总线连在一起,任何两个部件要通信,都得走这条公共通道。
3.1 三总线结构:各自分工,各管一摊
教材里最经典的是三总线结构:数据总线(Data Bus)、地址总线(Address Bus)、控制总线(Control Bus)。不要把它们当成三条物理上完全独立的线,实际上它们是在同一组逻辑线上按功能划分的角色。
数据总线负责传数据本身。它的宽度直接决定了一次能传多少个 bit。32 位数据总线一次传 4 字节,64 位一次传 8 字节。CPU 的“位宽”通常就跟这个相关。数据总线是双向的,CPU 可以读数据也可以写数据。
地址总线负责传“我要访问哪里”的地址信息。它的宽度决定了寻址空间上限。比如 32 位地址总线,理论上最多寻址 2 的 32 次方即 4GB 地址空间。注意,老一些的 32 位 CPU 配的地址总线可能只有 20 根(早期 8086 有 20 根地址线,寻址 1MB),所以“32 位 CPU 就一定能寻址 4GB”这个说法并不严谨,要看具体地址总线宽度。
控制总线负责传各种控制信号,比如读信号、写信号、中断确认、总线请求、总线允许。它一个是速度慢(控制信号本身就是为了协调),另一个是方向不统一:有的信号是 CPU 发出的,有的是外设发给 CPU 的。这也是很多考生容易混淆的地方:控制总线里有输入型信号,也有输出型信号。
3.2 总线事务的完整流程:一个读操作是怎么“走完全程”的
我们来完整走一遍“CPU 要读内存地址 0x20000 处数据”这件事在总线上的过程:
首先是申请阶段。CPU 通过控制总线发出总线请求信号(BR),总线仲裁器(通常集成在芯片组或 CPU 内部)根据优先级决定把总线使用权交给谁。如果 CPU 已经在用总线,那就直接进入下一步。
然后是地址阶段。CPU 把目标地址 0x20000 放到地址总线上,同时控制总线上发出“读”信号。内存控制器看到地址和读信号之后,明白 CPU 要读这个地址。
接着是数据阶段。内存控制器从对应地址取出数据放到数据总线上。CPU 在一个约定好的时序点上采样数据总线,把数据读进自己的寄存器。
最后是结束阶段。控制总线上的读信号撤销,总线事务完成。
这个过程非常快,但里面有一个重要的玄机:谁来决定“数据已经准备好”?在同步总线里,大家约定好固定周期数(比如等待固定 4 个周期后数据一定有效);在异步总线里,外设通过一个“准备好”信号(READY)来告诉 CPU 数据已经就绪。磁盘这类慢速外设通常用异步方式,不然 CPU 就要按最慢的设备来等。这个细节理解透了,你就明白为什么总线有“等待周期 wait state”这种机制——慢设备还没有准备好,总线就得插入等待周期让 CPU 等等。而这正是数据波特率、总线频率与外设速度博弈的微观场景。
3.3 总线仲裁:多设备抢用总线,怎么保证公平又高效
总线上接的设备很多,但物理上只有一组线路,同一时刻只能有一个设备发起传输(总线主设备)。谁来决定“我现在可以用了”?这就靠总线仲裁,有集中式和分布式两类策略。
集中式仲裁有一个专门的仲裁器。我以最常见的链式查询为例:仲裁器把总线请求信号从优先级最高的设备开始逐个向下传递,按顺序询问。第一个请求总线的设备拿到使用权,后面的继续排着。这种方式硬件简单,但坑在于:如果优先级最高的设备一直占用总线,后面的设备可能“饿死”(永远没机会用)。所以后来有了轮询方式,仲裁器轮流给每个设备分一个时间片,大家公平使用,但调度的开销会大一些。
分布式仲裁则不依赖中央仲裁器,每个设备自己判断能否用总线。比如以太网里的 CSMA/CD 思路(先听后发、冲突检测)就是一种分布式决策。但在计算机内部总线上,分布式用的通常是“菊花链请求/允许”方式,信号像接力棒一样从一个设备传到下一个,优先级由物理位置决定。
仲裁机制的细节在真实系统里非常重要:比如在 PCIe 上,虽然没有传统意义上的“总线仲裁”了,但交换机内部仍然有流量调度、仲裁和流控机制,只是在更高层次上实现的。我们常说的“总线忙导致卡顿”,本质上就是设备抢不到总线的使用权,数据只能在缓冲区里排队。
3.4 局部总线与总线标准化演进:从 ISA 到 PCIe
早期的 PC 总线是 ISA(Industry Standard Architecture),8/16 位数据宽,频率极低,只能满足键盘、串口这类慢速设备。后来 VESA 局部总线(VLB)把总线直接连到 CPU 局部总线上,专门给显卡这种高速设备用,但它依附于 CPU 时序,设备兼容性很差。再后来是 PCI(Peripheral Component Interconnect),采用独立于 CPU 的频率和协议,支持即插即用(设备通过配置空间自我描述),带宽提升巨大。而现代主流是 PCI Express(PCIe),它把所有并行总线改成了串行差分信号,通过多 Lane(通道)扩展带宽,每次传输是点对点的分组交换——这已经非常像“计算机内部的网络”了。
这里我想强调一个点:总线的演进其实是性能和通用性之间的不断权衡。ISA 简单、通用,但慢;VLB 快,但只适配 CPU;PCI 快又通用,但并行信号在高频率下干扰严重;PCIe 用差分串行信号彻底绕开了并行干扰问题,还引入了类似网络的信用流控机制。这种演进思路,在面试里常被问“为什么 PCIe 性能比 PCI 高”,其实答案并不只是“串行比并行快”,而是更根本的物理层差异和策略差异。
从磁盘的角度看,老式 IDE 走的是 ATA 并行总线,SATA 走串行点对点,后来 NVMe 公司直接“杀”到了 PCIe 通道上。磁盘的“总线接口”变化,实际上也是跟着总线的演进走的。现在你看一块 NVMe SSD 为什么能跑到 7GB/s,除了闪存本身快,更关键的是它走的是 PCIe 4.0 的总线(每通道约 2GB/s,x4 就是 8GB/s),而且 NVMe 协议把队列深度推高到了 65535 个并行命令、64K 个队列——这种并行度彻底解放了 IO 链路。
4. 实操视角:把“磁盘读数据”三步曲推向实战
理论说了一堆,我们来一个跟实战结合的推导。假设你要读磁盘上一个 4KB 的文件,现代系统的链路怎么走?
第一步,CPU 执行到“读文件”的系统调用,陷入内核。内核发一个读命令给磁盘控制器(经过驱动),这个命令里包含源 LBA、目标内存地址、字节数。然后 CPU 继续执行其他进程。磁盘控制器拿到命令后,如果是机械硬盘,就开始寻道、转盘,如果是 SSD,就查映射表、读闪存。
第二步,磁盘控制器把数据读到自己内部的缓冲区。此时它向 DMA 引擎发起传输请求。DMA 控制器获得总线使用权后,把数据从磁盘控制器的缓冲区搬到内存指定的页缓存里。搬运期间,CPU 和 DMA 按总线仲裁规则共享总线带宽。
第三步,DMA 传输完成,发出中断。CPU 响应中断,更新内核的 IO 完成状态,唤醒等待这个文件的进程。进程拿到数据后,从内核态切回用户态。
整个过程里,CPU 在中途只做了一个“交代任务”和“接收结果”的动作,真正的大块数据搬运全在 DMA + 总线上完成。这就是为什么高 IO 场景下,总线带宽往往比 CPU 频率更先成为瓶颈。
实操中我用过一个比较有意思的判断指标:当你发现某个应用程序的 CPU 占用率不高,但系统 I/O 很慢,优先怀疑是不是总线带宽被占满了。在 Linux 上可以用perf top看 CPU 是否在等 DMA 完成,用iostat -x 1看%util和svctm,但有一点很重要——iostat 的 %util 高不代表总线坏,要看 r/s 和 w/s 是否已经接近物理极限;而遇到大量“中断风暴”的时候,要先看网卡是否开了 interrupt coalescing。
我曾经排查过一个线上问题:某台机器上只要跑一个大文件拷贝任务,其他服务的响应时间就剧增。刚开始以为是 CPU 不够,加了 CPU 也没用。后来才发现,问题出在 SATA 控制器的 DMA 老旧的驱动程序没有开启 64-bit DMA,导致它做了大量“低效 32 位分裂”传输,把总线的传输次数翻了好几倍,直接造成延迟飙升。后来升级驱动和固件,情况就彻底缓解了。这个案例说明,总线和 DMA 的“配置细节”,比硬件本身的标称带宽更能决定真实性能。
再分享一个小工具层面的心得:如果你要测一块 SSD 的随机读性能,直接跑fio这类工具没什么技术含量,但注意队列深度(QD)不同,结果天差地别。因为现代总线和 NVMe 协议都强调并行度,QD=1 的随机读可能只有 200MB/s,QD=32 可能就能到 6GB/s。这不能只怪“硬盘质量”,总线协议支持并发事务的能力本来就是带宽的重要组成部分。用队列深度去衡量 IO 能力,比单看 MB/s 更有参考价值。
5. 常见问题与排查技巧实录(备考 + 实操双重场景)
这块我结合自己学的时候踩的坑,以及后来实际调优时的经验,把几个高频问题和排查思路整理成速查形式,方便直接抄作业。
5.1 概念层面:为什么 DMA 之前 CPU 要“暂停一段时间”?
很多教材里说 DMA 传输前,CPU 要把总线控制权交出来,这个“暂停”到底是指什么?其实分情况:早期用“停止 CPU 访问主存”的方式时,DMA 会把 CPU 完全挂起,CPU 的指令周期都会停,这个阶段 CPU 不执行任何内存访问相关的指令。但现在现代系统里更多是“周期挪用”,CPU 基本感觉不到被暂停。还有更复杂的呈现:Intel 芯片组里有一个“DMA 独占”的模式,允许 DMA 引擎独占主存带宽一段时间,把一批数据都传完再放行 CPU。
问题在于,“DMA 期间 CPU 的 cache 要不要失效或写回”。如果 DMA 往内存里写数据,CPU 的 cache 里可能还保存着旧的数据,等 CPU 再读时,拿到的可能还是缓存的旧值。所以硬件上有 cache 一致性机制,或者软件上需要显式地invalidate对应 cache 行。这正是“DMA 传输完成”后,不能简单“以为内存里的数据一定正确”的原因——你必须确保 cache 与主存同步。这个问题在裸机编程和内核驱动里非常常见。
5.2 概念层面:中断和 DMA 谁通知 CPU?是不是有“中断风暴”?
DMA 工作完成后,通常是 DMA 控制器发一个中断给 CPU;但有些控制器设计成 DMA 完成时给 CPU 置一个标志位,由 CPU 主动去查。无论哪种,都会产生“一次传输结束一次中断”的问题。当磁盘块很小、传输频率很高时,中断频率也会很高,这就是“中断风暴”。解决方案和网卡类似:积累多个完成通知再统一上报(中断合并、延迟中断)。这也是为什么现在 NVMe 的完成队列机制里专门有中断聚合的开关,内核里也有blk-mq的多队列配合,就是为了缩减中断数量。
5.3 实操层面:怎么判断总线的带宽是否真的出现了瓶颈?
我的经验步骤大概是这样的:
先看 CPU 的%iowait,如果长期偏高(比如超过 20%),说明 CPU 大量时间在等 IO。然后看磁盘的 I/O 请求队列,如果队列深度长期满,可能是磁盘本身或链路上限受限。再看一个容易被忽视的指标:总线事务数,比如走 SATA 时,如果发生大量短小的 PIO(非 DMA)传输,那说明可能驱动没有正确启用 DMA 模式,数据搬运全在 CPU 上,导致总线利用率虚高、CPU 实际很忙。
排查工具上,Linux 下我会组合使用iostat -x 1、vmstat 1、sar -d和perf top。如果怀疑中断异常,cat /proc/interrupts直接看每个 CPU 上的中断分布,有没有集中到某一个核上(中断不均会严重影响扩展性)。再有一件重要的事:看 BIOS/固件配置里 AHCI 模式是否开启(而不是 IDE 兼容模式),没开 AHCI 的话,NCQ 和多队列特性全没有,DMA 效率会大打折扣。
5.4 实操层面:磁盘读慢,怎么区分是磁盘的问题、总线的问题、还是控制器的问题?
把问题拆成三段:盘片本身 → 控制器 → 总线(含驱动)。
先用hdparm -Tt /dev/sdX这类工具测纯读带宽。如果带宽和标称相差不大,说明磁盘本身问题不大。再用smartctl查盘的健康度,排除坏道、重映射异常。接着查看接口协商速率,SATA 盘看看是不是被降速到了 SATA 2.0,PCIe 盘用lspci -vvv看链路宽度和速度,是不是从 x4 Gen3 掉到了 x1 Gen1——接口降速是数据线接触不良、插槽供电不足或者固件 bug 的常见后果。这一步非常值得做,因为很多“SSD 变慢”的案例,本质是总线链路降级导致的。
最后,如果磁盘和接口都正常,但大文件拷贝仍然慢,再考虑驱动和操作系统层的配置,比如 IO 调度器是否该改成 none,挂载参数是否开启了noatime,这些都会影响读写链路的效率。
6. 一些备考和面试中特别容易踩的概念坑
这一节给正在准备考试或面试的同学单独列出来。这些点全是教材里有、但是很多人第一遍学的时候没注意到的细节,也是我做模拟题和面试复盘时反复遇到的。
第一个坑:“中断方式比程序查询方式一定好”。这句话是错的。中断方式增加了上下文切换开销,如果外设每次事件的数据量极小、频率极高,中断的开销反而可能超过查询。很多面试官会故意拿这个点来测深度。
第二个坑:“DMA 不经过 CPU,所以 CPU 完全不用管”。DMA 是指数据传输过程不需要 CPU 逐条指令搬运,但 DMA 的启动配置(源地址、目的地址、长度)、传输结束后的处理,仍然需要 CPU 参与。而且 DMA 访问内存时,依然要走总线,会与 CPU 竞争总线带宽。
第三个坑:“总线带宽就是数据位宽乘频率”。这个只在理想情况下成立。实际还要考虑:总线事务的协议开销(地址阶段、控制信号准备)、wait state、仲裁等待、传输的效率(比如 PCIe 的有效载荷比例只有约 75%-88%,因为还要传包头和 CRC),所以实际的可用带宽远低于这个“理论峰值”,这也是为什么标称 8GB/s 的 PCIe 4.0 x4 你在实际中永远跑不到 8GB/s 的原因。
第四个坑:“地址总线宽度 = CPU 位宽 = 寻址空间”。前面讲过,三者关系在经典体系结构里密切,但不是强绑定。实测中很多题目会告诉你一个 CPU “几位的”、“地址总线几根”,然后问你最大寻址空间,这时答案只跟地址总线根数有关,跟 CPU 通用寄存器位数没关系。
第五个坑:“总线上的设备都可以主动发起传输”。不是。总线主设备(比如 CPU、DMA 控制器)才能发起读/写事务,从设备(比如内存、磁盘控制器)只能响应。如果题目里问“磁盘控制器是主设备还是从设备”,要看它在什么上下文中:作为外设响应 CPU 命令时是从设备;但当它配合 DMA 进行数据传输时,DMA 是主设备,磁盘控制器通常是数据源/数据宿。很多时候“设备”的主动性和“总线主/从角色”是两个维度,不能混淆。
7. 个人实操经验里的几个小建议
理论部分说完了,最后分享几个我在实际项目中沉淀下来的经验,不一定成体系,但都是真实踩过的路。
第一,做嵌入式或驱动开发时,多画总线事务时序图。程序查询、中断、DMA 这三种 IO 方式的差异,如果只看文字很容易忘。我当时是拿一张纸,把读磁盘 4KB 的过程按“时间轴”画出来,标清楚谁在用总线、谁在等、谁在搬数据。画完三张图,区别一目了然,以后再遇到面试问“为什么 DMA 能提高吞吐”,我都是从“总线被谁占用”这个角度答,而不是背定义。
第二,调优的时候永远不要只看一个指标。比如你在 Linux 上看到%util=100%,可能第一反应是磁盘满了,但只要再查询一下r/s和w/s,就会发现每秒 IO 次数并不高,那么高 %util 大概率是设备在等待总线或固件内部处理;这完全是不同维度的瓶颈,解法差很远。我会习惯性地把 CPU 占用、IO 队列、总线事务、接口带宽四个层面的数字放在一起看,才能定位到真正卡住的那个环节。
第三,遇到 IO 性能诡异的问题,先查总线链路质量。很多“磁盘变慢”的真相是 PCIe 链路从 x4 掉到了 x1,或者 SATA 降速到 1.5Gbps。这时候你跑到操作系统层怎么调都没用,必须回到硬件层排查物理链路。我有一次就是清理一下插槽灰尘、重插一次 SSD,性能直接翻了几倍。这类经验看着不“高级”,但往往最见效。
第四,学习时要主动建立“链路思维”。单独背“磁盘”“IO”“总线”其实都容易,难的是把它们拼成一条完整的数据通路。我后来给人讲这块知识,一直强调一件事:数据从磁盘到 CPU 的寄存器,中间要经过控制器、DMA、总线、内存、Cache,每一段都有它的时间消耗和瓶颈可能。你每次看到一个 IO 性能指标,不妨在心里走一遍这条链路,问自己“数据现在在哪一段”,排查思路自然就清晰了。
8. 一个可复现的速查表:三种 IO 方式对比
为了方便复习和日常查阅,我把三种 IO 方式的关键属性压成一张表,建议收藏。
| 对比维度 | 程序查询方式 | 中断方式 | DMA 方式 |
|---|---|---|---|
| CPU 是否参与数据搬运 | 是,逐字节/逐字搬运 | 是,在中断服务程序中搬运 | 否,DMA 控制器搬运 |
| CPU 等待期间的状态 | 循环等待,阻塞 | 处理其他任务,被中断打断 | 处理其他任务,结束后收中断 |
| 数据传输单位 | 字节/字 | 字节/字 | 数据块 |
| 适用场景 | 外设极快、数据量小 | 数据量小、事件随机 | 批量大块数据 |
| CPU 开销 | 高,全程占用 | 中,每次中断有上下文开销 | 低,仅配置和收尾有开销 |
| 总线占用特征 | CPU 发起总线事务 | CPU 在 ISR 中发起总线事务 | DMA 作为总线主设备发起总线事务 |
| 中断的次数 | 无(轮询) | 每次传输一次中断 | 每次 DMA 传输完成后一次中断 |
这张表也回答了一个很多学生问过我的问题:DMA 方式下 CPU 是不是完全不用管了?答案很明确,不是。CPU 要发起 DMA 请求、配置 DMA 控制器、在 DMA 完成中断里做后续处理,只是传输过程它不用逐字节搬运而已。真正“完全不用管”的传输是不存在的,除非引入更高层的智能控制器,比如把整个 IO 重放机制都下沉到设备固件里,这已经是更往后的话题了。
这篇文章所讲的磁盘、输入输出技术和总线,其实是一条链路上的三个环节。如果你需要往深处继续扩展,下一步可以研究“内存映射 IO 与端口 IO 的区别”“IO 寻址的统一编址与独立编址”“Cache 一致性与 DMA 的关系”,以及现代系统里的中断控制器(比如中断号重映射、MSI-X),这些都是我提到的链路思维的延伸。按照我个人的经验,把这些串成一条线去学,比零散地背术语有效得多。