☰
CPU与I/O设备数据传输控制方式解析:轮询、中断、DMA与通道
2026/10/24 4:44:53 网站建设 项目流程

我最早意识到数据传输控制方式的重要性,是在调一个块设备驱动的时候。当时同一块存储设备,用某平台自带的一套示例代码跑,CPU占用率居高不下,而换成另一套基于DMA实现的方案后,同样吞吐量的情况下CPU占用几乎可以忽略不计。后来把中断频率、数据搬运路径摊开一看,才发现问题全都集中在这个标题所指向的地方——CPU与I/O设备之间交换数据,到底用哪种机制来完成,不只是教科书上的一个四选一知识点,而是直接决定系统实时性、吞吐量和CPU空转成本的底层设计决策。

这篇文章我想把设备管理中几种经典的数据传输控制方式完整拆开讲一遍,包括程序直接控制、中断驱动、DMA和通道控制,以及它们在今天的高性能网卡、NVMe SSD、多队列设备里是怎么演变和存活的。适合正在准备计算机基础考试的同学、刚接触驱动开发的工程师,以及想搞明白“为什么设备越来越快但CPU还是被拖累”的爱好者。每一个环节我都会尽量把自己的实际经验和计算过程放进去,而不是只给一张干巴巴的对比表。

1. 为什么数据传输方式决定了整个系统的性能天花板

CPU和I/O设备之间天然存在一个矛盾:CPU的执行速度以GHz计,而很多外设的数据产生或消费速率要低几个数量级,且数据往往不是CPU计算需要处理的,只是要被搬运到内存或者从内存搬走。每一次搬运,都会消耗CPU的指令周期、总线周期和额外的时间开销。所以设备管理里讨论数据传输控制方式,本质上讨论的是:谁来搬数据、什么时候搬、搬的过程中CPU要投入多少精力。

早期设计者面对这个矛盾时,思路很简单:既然程序要读写数据,就让CPU亲自去搬。于是有了程序直接控制方式,也就是轮询。这种方式下CPU循环读取设备状态寄存器,等待设备准备好之后,一次搬运一个字或一个字节。逻辑清晰、实现简单,但它有一个致命问题:CPU在整个等待过程中什么正经事都干不了,只能原地打转。设备越慢、数据量越大,浪费越明显。

为了改掉“忙等”这个毛病,中断驱动方式出现了。CPU发起I/O后可以立刻回去执行别的任务,设备准备就绪时主动发一个中断信号,CPU再暂停当前工作来处理这次数据传输。CPU不再空转,每传完一个数据单元只需要付出一次额外的上下文切换代价。但对于高速设备来说,中断频率高得吓人,每次打断都要保存现场、执行传输、恢复现场,这些开销加起来一点也不比轮询省心。

再往后发展,人们发现数据搬运这件事本身完全可以交给一个专门的硬件来做。DMA控制器接管CPU和内存之间的数据通路,按照CPU提前设好的地址和长度,直接把设备数据搬到内存里,搬完一整块再打断CPU一次。这就是DMA方式。接着又出现了通道控制方式,把管理多个I/O设备、执行复杂传输任务的工作也下放给一个更智能的I/O处理器级别部件,CPU只需要发出一个很高层的指令,后面一整批工作都由通道自己调度。

这四种方式不是谁绝对取代谁,而是按设备速率、数据粒度、CPU可承受的开销选出来的不同设计路线。串口和按键这类低速率设备可能更适合中断甚至轮询,磁盘和网卡这种高速块设备则基本依赖DMA,而一些存储阵列或大型机环境里,通道控制方式能把一堆设备的传输任务统筹调度起来。我把它们放在一起做了张初览表,后面逐一展开:

控制方式数据传送单位CPU参与程度主要优点主要缺点典型适用场景
程序直接控制(轮询)字(或字节)全程参与,忙等控制简单,硬件开销小CPU利用率极低低速设备、早期系统、系统启动早期
中断驱动字(或字节)每次传输触发一次中断CPU不必忙等,响应及时中断频率高,开销大低速到中速设备,如串口、键盘、鼠标
DMA数据块预处理和后处理参与,传送阶段由DMA控制器完成批量传输,CPU负担轻需要额外DMA控制器硬件,会占用总线磁盘、网卡、高速外设、多媒体设备
通道控制一组数据块/多个设备任务仅发I/O指令启动,传输由通道完成可管理多设备、多任务,智能调度硬件复杂,成本高大型机、存储阵列、高并发I/O场景

有了这张图之后,再往下看每类方式的具体工作机制,就不会在细节里迷路了。反正我第一次完整看完几种方式的时序图时,最直观的感受是:每次技术升级,都是在回答一个问题——CPU到底应该在这个传输过程中承担多少角色。

2. 轮询与中断:两种“搬数据”思路的代价对比

2.1 程序直接控制:CPU从头等到尾的原始方案

程序直接控制方式一般也叫查询方式或轮询方式。CPU执行一段循环程序,不断读取I/O设备的状态寄存器,判断设备是否就绪:如果没准备好,就继续读取、继续判断;如果准备好了,就执行一次数据传送。这个循环可以概括为“查状态—等就绪—传数据—继续查”的反复过程。

很多人觉得这种机制太笨,但其实它有一个至今仍在使用的优势:实现极端简单,不需要额外中断控制器,不需要复杂的上下文切换逻辑,时序完全可预测。在一些硬件初始化阶段,例如系统刚上电、中断控制器还没配置好、或者BootLoader早期阶段,轮询反而是唯一可靠的选择。我在写某个嵌入式板卡的串口引导程序时,就见过不少代码是直接死循环等串口发送寄存器为空的,这就是典型的程序直接控制方式。

不过一旦进入正常的多任务系统或高速传输场景,轮询的浪费就很扎眼了。以“每传送1字节CPU需要执行100条指令”来算一笔账:如果设备速率是50KB/s,那CPU每秒要多执行5百万条指令来完成搬运。这对2GHz的CPU来说占用率确实不高,但如果把设备换成万兆网卡级别,假设每秒要接收1.25GB数据,每字节仍然只算100条指令,CPU需要每秒处理1250亿条指令,这已经完全不可行了。更关键的是,轮询状态下CPU一直在“忙等”,即使系统里有其它任务可以执行,也无法切换出去,因为CPU的逻辑是“你还没好我就一直盯到你”。

下表把这个方式的优缺点列一下:

优点缺点
控制逻辑简单,不需要复杂的中断机制忙等,CPU利用率低
硬件实现成本低CPU与外设只能串行工作
实时响应直接,无中断处理延迟不适合高速大批量数据
启动和调试阶段非常可靠多设备场景下轮询顺序难平衡

2.2 中断驱动:从“死等”到“被打断时再处理”

中断驱动方式把“CPU一直盯着设备”改成“设备主动喊CPU”。流程大致是:CPU发出读或写命令后,立即返回执行原来的任务;设备完成准备或准备好数据后,通过中断控制器向CPU发出中断请求;CPU响应中断,暂停当前任务,保存现场,跳转到对应的中断处理程序,完成数据传送后恢复现场,回到被中断的任务继续执行。

相比轮询,中断驱动最大的进步是解放了CPU的等待时间。设想一个串口设备每秒接收几十个字节,如果用轮询方式,CPU可能每秒要查询成千上万次状态,而实际上大部分查询都是白等。改成中断方式后,CPU在两次接收之间可以安心做别的事,只有数据到达时才被短暂打断。

但中断不是免费的。每次中断都包含响应、保护现场、执行处理程序、恢复现场的过程。这些开销累积到高频场景下是非常可怕的。假设某个高速设备每秒触发10万个中断,每个中断处理需要5微秒,那么光中断处理就要占掉CPU一半的时间。中断频率越高,单次中断里真正搬运的有效数据量越小,效率就越低。这就是为什么中断方式只适合中等速率设备或数据传输粒度较小的场景,而不是高速设备的最优解。

从操作系统进程视角看,中断还意味着用户态和内核态之间的切换、上下文保存、可能触发的调度决策,这些都不是零成本操作。我在实测某个高速外设时发现,当把中断频率从每秒几千次提高到每秒几十万次后,系统整体吞吐反而下降了,因为CPU大部分时间都在切换和响应中断。所以后来的DMA方式把“每次传输打断一次”改成了“每一整块传输完成才打断一次”,这完全是切中要害的改进。

3. DMA接管:从“CPU逐字节搬运”到“硬件直接搬运”的关键转变

3.1 DMA控制器的角色与数据通路

DMA,全称Direct Memory Access,直接存储器访问。核心思想是让数据在I/O设备与内存之间直接传送,不需要CPU逐字读取再写入。承担这个任务的是一个专门硬件——DMA控制器(DMAC)。它在系统中既可以作为从设备被CPU配置,也可以作为主设备占用总线发起数据传输,这一双重身份正是DMA能批量搬运数据的关键。

DMA控制器内部通常有这些关键部分:

  • 内存地址寄存器:存放要读或写的主存起始地址,每次传送后自动加1。
  • 传送长度计数器:记录还需要传送多少字节/字,每传送一个单位减1,减到0表示一次DMA传送结束。
  • 控制寄存器:配置传送方向(读设备还是写设备)、传送方式、启动或停止DMA。
  • 状态寄存器:标识DMA是否忙、是否完成、是否有错误。

一次完整的DMA传送分三个阶段。首先是预处理,CPU对DMAC进行初始化,把内存起始地址、需要传送的数据长度、传送方向(I/O到内存还是内存到I/O)写入相关寄存器,然后向I/O设备发出启动命令。设备收到命令后开始准备数据。第二阶段是数据传送,这是DMA真正接管工作的阶段。设备每准备好一个数据单元,就向DMAC发出请求,DMAC获取总线控制权,把数据直接从设备送入内存指定地址,或从内存读出送到设备。整个过程CPU不参与数据搬运,只是总线被占用时可能暂时等待一下。第三阶段是后处理,当长度计数器归零,也就是整块数据传送完毕,DMAC向CPU发出中断请求,通知CPU“活干完了”。CPU响应中断后,可以检查状态寄存器确认结果,然后继续后续流程。

3.2 DMA与CPU谁在用总线:三种常见的总线占用方式

DMA传送时,DMAC需要占用系统总线来访问内存,而CPU同样要访问内存取指令、读写数据。两者不能同时占用总线,所以必须有一套仲裁和分配机制。教科书里常讲三种策略:

方式基本思想优点缺点适用场景
停止CPU访存DMAC请求总线后,CPU让出总线使用权,DMAC独占总线直到整块传完传输控制简单,适合大批量数据连续传送传送期间CPU被完全挂起,无法执行程序高速外设大批量传输,如早期磁盘控制器
周期挪用DMAC只占用一到几个总线周期,传完一个数据单元就释放总线;下次请求再占用对CPU的影响小,CPU能在外设准备间隙正常工作需要额外的请求应答时序,逻辑稍复杂传输频率适中、数据按单元到达的设备
交替访存CPU与DMAC分时交替访问内存CPU与DMA几乎并行工作,效率高要求内存支持双端口或特殊时序,成本高设计较新的高性能系统

我在实际项目中接触到的大多数高速设备驱动,用的都是周期挪用思路:DMAC在每个数据字准备好时申请总线,挪走一两个周期,搬完一个单元立刻让出。CPU在一个DMA传送过程中很少被完全卡死,只有当数据单元足够大、设备请求足够密集时,DMA占用比例才会明显上升。而“停止CPU访存”这种方式更适合在数据密度极高、传输时间短的场合使用,因为如果一次传送要花费很长时间,CPU被挂起太久,整个系统的响应能力都会受到影响。

注意:DMA传送完成后发出的是一个中断信号,这个中断仍然需要CPU处理,只是它处理的频率从“每个数据单元一次”降到了“每批数据一次”。中断处理和DMA并不是互斥关系,而是前后接力关系。

3.3 理解DMA最容易被带偏的一个点

有很多初学者以为DMA一旦开始传送,CPU就彻底与这次传输无关,直到传输结束才收到中断。这个理解在数据传送阶段基本正确,但只要把整个流程拉长看,CPU仍然承担了预处理和后处理两件事。它要写配置寄存器、启动设备、传输完成后检查状态、处理中断、清理缓冲区。这些工作虽然不搬运数据本身,但缺一不可。

另外,DMA能够加速传输有一个前提:数据在内存里的地址必须提前知道且物理连续。操作系统使用虚拟内存时,用户态程序看到的地址往往是虚拟地址,对应的物理页可能分散在不同位置。如果驱动直接使用虚拟地址配置DMAC,传输出错是必然的。所以实际驱动开发中要么把缓冲区锁定为连续物理页,要么用分散/聚集列表(SGL)让DMAC逐个块搬运。这些内容在后面讲现代设备时会再提到,但在这里先埋个伏笔:DMA不是万能的,它对内存管理提出了额外的要求。

4. 通道控制:从“一块数据操心”到“一批任务统一指挥”

4.1 通道比DMA多出来的“智能”在哪里

DMA解决的是“一块数据由谁搬运”的问题,每一次搬运依然需要CPU启动,只是在搬的过程中CPU可以抽身。通道控制方式则更进一步:它由通道这个独立部件执行一段通道程序,负责控制一台或多台I/O设备完成一组复杂的I/O操作。CPU只需要执行一条I/O指令来启动通道,之后整批传输、设备选择、数据格式化、校验等工作都由通道自己管理,结束时再发中断通知CPU。

可以把DMA比喻成一个只负责“把这箱货从门口搬到仓库”的搬运工,而通道是那个拿着清单、知道有三辆货车、每辆该卸到哪个区、还要处理验货和签收的调度员。通道内部有自己的处理器、指令系统和内存接口,能执行的指令叫通道指令,一组通道指令构成通道程序。普通程序跑在CPU上,通道程序则跑在通道上,两者是不同级别的执行单元。

通道方式的优势在于,一台主机往往连接多个I/O设备,如果每个设备都靠CPU-DMA方式一条一条指令去启动和等待,CPU管理成本同样很高。有了通道,CPU上操作系统的I/O调度可以把一批设备的传输任务交给通道统一执行,CPU只在通道程序启动和通道完成时参与。这种能力天然适合通道数量较多、设备较多、传输任务复杂的大型系统。

4.2 三种通道类型的传输粒度与调度差异

通道不是只有一种形态,根据数据传送单位和工作方式的不同,可以分成选择通道、数组多路通道、字节多路通道三类。

选择通道的特点是,在一段时间内只为一个设备服务,一旦选中该设备,就独占通道连续传送一批数据,直到这一批数据全部传完才切换别的设备。它适合连接高速块设备,例如磁盘和磁带,因为这类设备传输时的数据流是密集连续的,切换代价不能太频繁。缺点是如果一台设备占用通道时间太久,其它设备只能等待。

数组多路通道以数据块为单位,为多个高速设备交叉传送数据。它允许通道先为设备A传一块数据,然后切换到设备B传一块,再切回设备A接着传。这样在设备A寻找磁道、设备B等待数据缓冲的间隙里,通道都能找到活干,提高整体利用率。适合磁盘这类存在明显“寻道、等待、传输”时间片的设备集群。

字节多路通道则以字节为单位,按时间片轮转方式为多个低速设备服务。每个设备在轮到自己的极短时间里传送一两个字节,然后让出通道给下一个设备。键盘、串口、打印机这类慢速字符设备的场景最适合它,因为它们的速率低,通道可以通过极细粒度交叉,把许多设备的传输同时管理起来。

通道类型数据传送单位服务设备典型设备类型
选择通道一组/一批数据一次一个设备,独占至传完高速磁盘、磁带
数组多路通道数据块多台高速设备按块交叉磁盘阵列、多路存储设备
字节多路通道字节多台低速设备按字节交叉串口、键盘、打印机

顺便多说一句,通道方式在今天的纯PC环境里已经不常见,但它的思想并没有消失。很多存储控制器的内部都有类似通道的调度单元,用一个轻量级的I/O处理器管理多个硬盘、RAID组和缓存之间的数据流。主机侧看到的是一个标准接口,实际内部就是通道逻辑在干活。

5. 现代设备里的影子:经典机制并没有过时

5.1 高性能网络场景中轮询的“回归”与再认识

看到这里你可能会想,轮询那么笨,现代设备怎么可能还用?但恰恰是如今的高性能网络处理,让轮询以另一种形式回到了聚光灯下。当网卡速率极高且数据包到达极频繁时,中断驱动的开销会变成严重瓶颈。一个万兆甚至更高带宽的网卡,每秒可能产生几十万甚至上百万个中断,CPU大部分时间都在处理中断上下文的切换和数据包搬运,真正的报文处理反而没时间做。

于是很多高性能网络框架会主动禁用网卡中断,让应用或驱动通过轮询方式主动去接收队列里取数据。这与我们在第2章里讲到的程序直接控制方式本质上是同一套逻辑,但目的完全不同:早期轮询是因为设备太慢、CPU等不起;现代轮询是因为设备太快、中断打不过CPU的极限吞吐。这也说明传输控制方式没有绝对先进和绝对落后,只有适合场景与否。

我自己在调试某种高速网络测试环境时也验证过这一点。开着中断跑,CPU占用很高且吞吐起伏不定;改用轮询模式后,吞吐曲线明显更平滑,处理侧CPU也稳定在一个可预期的水平。代价是CPU会一直占用一个核去盯队列,这在多核系统中可以通过绑核解决,但如果CPU核数紧张,轮询对CPU资源的持续占用也需要接受。

5.2 DMA与现代设备:从SGL到IOMMU的重要升级

现代设备比教科书里的DMA要复杂许多,但底层思路一脉相承。系统内存有大量不连续物理页的时候,DMA控制器通过**分散/聚集列表(SGL)**把多个不连续的内存块组织成一个逻辑上连续的DMA描述链,可以让一次DMA传送覆盖多个物理段,既提升了效率,也解决了虚拟地址与物理地址的映射问题。

还有IOMMU的存在让DMA地址变成了可重映射的I/O虚拟地址。这相当于给设备访问内存加了一层地址转换和保护,设备只能访问分配给它的DMA区域,不能任意读写整个物理内存。现代操作系统的驱动框架大量使用这类能力,目的是安全性和隔离性,而不仅仅是效率。

多队列技术也放大DMA在高端设备上的作用。像存储控制器或高性能网卡会把队列拆成多个,每个队列可以绑定独立的DMA通道和中断向量(配合MSI-X中断),多个CPU核可以并行处理各自的队列,把单核吞吐瓶颈摊到多核上。这是我调试多队列存储驱动时感受最深的一点:硬件DMA能力取决于厂商实现,但软件侧能不能用好多个队列,直接决定分布式或数据库场景下的I/O表现。

5.3 连通道控制也在“隐姓埋名”地工作

如果你用过带有硬件RAID功能的存储控制器,或者见过某些带独立I/O处理器和缓存管理的NVMe控制器,其实就是在体验通道控制思想的现代版本。这些控制器内部有专门的运行时固件,负责仲裁多个设备访问、计算校验和、维护缓存一致性、异常处理和重试机制。主机CPU只下发一个请求,之后控制器自己调度所有子任务,完成后再通过中断或完成队列通知主机。这个模型和通道方式非常相似:CPU做宏观调度,微观I/O执行完全下沉到设备侧。

从这个角度看,四种方式的理论模型不仅没有过时,反而嵌入到了现代设备的每一层。了解它们如何工作,根本目的在于帮你判断:某个I/O瓶颈到底是CPU参与过度造成的,还是设备本身的物理属性限制,或者是驱动层对控制方式的选择不当。如果只知道“用DMA快”而不理解里面有预处理、周期挪用、后处理、SGL、IOMMU、队列与中断平衡,遇到性能问题时会非常容易找错方向。

6. 高频误区、做题经验与驱动开发中的几个实用判断

6.1 四个最容易被误读的知识点

误区一:DMA传输过程中CPU完全不参与。这个说法只对数据传送阶段成立。预处理和后处理阶段CPU都是主角,尤其是传输完成后的中断处理和数据校验,仍然需要CPU执行代码。如果驱动为了追求极致的低中断频率,把DMA块调到非常大,接收端缓冲区的延迟也会变大,这是另一层需要权衡的设计取舍。

误区二:所有设备都应该用DMA。很多朋友学完DMA之后容易走极端,认为只要用DMA就更优。但DMA需要额外硬件,需要设置缓冲区、地址映射、传输长度,对于速率很低的字符设备,触发一次DMA的初始化开销可能比直接中断方式搬运几个字节还要大。工程上到底用哪种方式,要结合设备速率、传输粒度、CPU忙闲程度综合判断。

误区三:通道控制比DMA“高级”,所以更强。从智能程度上说是升级,从复杂度和成本上说是增加。通道部件本身需要自己取指令和执行通道程序,这个能力不是每个系统都值得配备。对普通的一个U盘或一块硬盘,DMA已经足够;通道主要负责的是多设备、多任务的并发调度,用在高并发存储场景更合理,单设备单传输场景反而显得复杂。

误区四:轮询已经被淘汰。无论从系统启动早期还是从现代高性能网络处理来看,轮询都还在以不同形态服役。考试里如果问你轮询的缺点,答案是CPU忙等待浪费大;但如果你在项目里直接决定把某个中断改成轮询,这可能是优化而非退步,关键是看场景和代价。

6.2 考试答题的拆解思路与面试的常见追问

如果是准备考试或面试,遇到“数据传输控制方式”相关题目时,我建议先明确三个维度:第一是数据传送粒度,到底是按字、按块还是一批;第二是CPU参与程度,是全程忙等、每次传输被打断一次还是只在传输前后参与;第三是硬件依赖,只靠CPU一个部件,还是需要中断控制器、DMA控制器、甚至专用通道处理器。

这三个维度能快速套在所有题目的分析框架上。举个例子,问你“中断驱动方式为什么不适合高速块设备”,你可以说:高速块设备数据量大、数据到达密集,按字或小粒度中断会造成极高的中断频率,上下文切换开销吞没吞吐,因此更适合以块为单位由DMA控制器批量搬运、只在传输完成后发一次中断。这个回答既覆盖了粒度,也覆盖了CPU参与程度,还把中断频率和上下文开销的关系讲清楚了。

面试官如果追问“驱动如何决定用DMA还是中断”,我的经验是从这几件事下手:确认设备是否支持DMA和MSI-X中断;看数据量是持续的大块流还是零星的小包;评估CPU核数与中断延迟容忍度;在真机上做A/B测试时,除了看吞吐量,还要同时看CPU空闲率和延迟分布。理论模型只能帮你建立假设,最终结论还是得靠实测数据说话。

6.3 驱动开发中真正容易踩的几个工程细节

第一个细节是内存一致性。DMA搬运数据绕过CPU,意味着CPU可能有缓存里尚未写回内存的旧数据,或者DMA写回内存后CPU缓存仍是旧值。这类问题在开发调试期非常隐蔽,表现为数据“读到旧值”或者随机出错。正确做法是在DMA启动前做回写(clean)操作,在DMA完成后做无效化(invalidate)操作,让缓存和内存保持同步。

第二个细节是中断频率与CPU绑核的配合。很多现代驱动框架支持多队列和中断绑定,让每个队列的中断固定到某个CPU核上处理。这样做能提高缓存命中率、避免多核抢占同一数据结构带来的锁竞争。如果盲目把所有中断都放在同一个核上,即使DMA的传送效率很高,中断处理侧也可能成为瓶颈。

第三个细节是DMA缓冲区生命周期管理。缓冲区一定要在DMA完成之前保持有效,驱动不能提前释放或复用。这个“生效与完成”之间的窗口很容易被忽略,一旦系统在DMA进行中回收页面,轻则数据损坏,重则触发页故障甚至内核崩溃。这个问题在长时间运行、内存压力高的环境中最容易偶发,特别难复现,也特别值得提前防护。

我记得有一次处理某块存储控制器的驱动问题,整机跑高并发读写时偶尔出现数据错位。排查了很久,最后发现是DMA描述符里有几个字段的字节对齐和硬件预期不一致,导致控制器在特定边界条件下读取了错误的地址。用逻辑分析仪和调试寄存器把描述符逐个打印出来对比硬件手册,才定位到这个隐藏得很深的细节。从那以后我养成了习惯:所有DMA描述符初始化代码写完后,第一件事不是跑业务,而是构造全零、全一、随机值等边界样本,用固化测试把每个字段的读写序和字节宽度先验证清楚。

在我做过的项目里,能把经典的控制方式理解透的人,排查I/O问题时的方向感往往明显好很多。遇到性能问题先判断是中断过多还是DMA通道饱和,遇到数据错乱先看缓存一致性和描述符配置,这种直觉不是背出来的,而是在四种机制的对比中慢慢形成的。希望这篇梳理,能让你在下次面对CPU与设备之间那条数据通道时,多一分“我知道下面发生了什么”的底气。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询