计算机组成原理里关于I/O控制方式的考点,说来说去绕不开DMA。很多同学学到这一章,前脚刚把程序查询和中断方式理清楚,后脚就被DMA控制器的各种寄存器、周期挪用、总线请求给搞晕了。这太正常了,因为DMA跟前面两种方式在思路上是完全不同的——程序查询和中断,本质上是CPU在“干活”;而DMA,是CPU把“干活权”暂时让给了一个专门的硬件控制器。
这篇文章就围绕408计算机组成原理里I/O设备与主机信息传送控制方式中的DMA方式展开。我会先把DMA为什么会出现讲清楚,再把DMA控制器的结构、三种传送方式、完整工作流程拆开揉碎,最后用一整个章节讲做题和复习时最容易踩的坑,再延伸一点真实硬件里的DMA应用。这篇内容对准备408考研的同学来说可以直接当复习笔记用,对正在接触STM32、嵌入式DMA开发的同学也有参考价值。
1. 为什么非要有DMA:先看前两种方式把CPU折腾成什么样
1.1 程序查询方式的痛点:CPU成了“人肉轮询器”
程序查询方式是最原始的I/O控制方式。它的逻辑非常简单:CPU不断读取外设的状态寄存器,检查设备是否准备好,准备好就传一个数据,没准备好就继续查。简单归简单,效率低到令人发指。
我习惯用一个比喻:程序查询方式相当于你坐在工位上,每隔几秒钟就抬头看一眼门口,看快递员来了没有。在快递员没来的这段时间里,你什么都干不了,因为你需要持续监控状态。CPU在这种方式下做I/O,意味着CPU从外设启动到数据传完的整个过程中,几乎全程都在“陪着”外设转。以最典型的主机向打印机输出一个字符为例,CPU要反复发出读状态命令、判断状态位、写数据、改状态,这中间每一个环节都需要CPU执行若干条指令。每个字符的传送,背后是成百上千条指令。
更难受的是,CPU的执行速度和外设的速度差距极大。CPU主频几个GHz,而慢速外设(比如键盘)每秒钟也就产生几十个字符。如果让CPU用查询方式去伺候键盘,CPU的绝大部分算力都消耗在“查询”本身,真正用于计算的时间几乎没有。所以在讨论I/O方式的时候,第一条要记住的就是:程序查询方式虽然控制简单、硬件代价低,但CPU的利用率被拉到了极低的水平,而且CPU和外设只能串行工作。
1.2 中断方式的改进与短板:每个字都喊一次救兵
中断方式改进了程序查询“死等”的问题。CPU不再主动轮询,而是先告诉外设“你准备好就喊我”,然后CPU转头去执行主程序。外设准备好一个数据后,通过中断请求线向CPU发信号,CPU响应中断,暂停当前程序,跳转到中断服务程序,完成一次数据传送,再返回断点继续执行。
这个方案的进步在于:CPU不再为空闲等待买单,外设没准备好的时间里,CPU可以干别的事情。但中断方式仍然有个绕不开的毛病——每一次数据传送,都要经历一次完整的中断响应过程。中断响应要做的事情非常多:关中断、保存断点、保存现场、识别中断源、跳转中断服务程序、恢复现场、开中断、返回断点。这一整套流程,少说也要几十条指令。
问题就出在这里:外设每传一个数据,CPU就要被中断一次。如果外设传一个128字节的数据块,CPU就要被中断128次。每次中断都有保存和恢复现场的固定开销,而且这些开销是纯“浪费”的——它们不产生任何计算结果。对于低速设备,中断次数少,开销还能接受;但对于磁盘、网卡这类一次性要传大量数据的设备,中断方式就显得非常吃力,大量CPU时间被耗在中断现场保护和恢复上。再加上频繁的中断还会打乱CPU的流水线执行,实际的性能损失比账面上的几十条指令更大。
1.3 DMA的破局思路:让硬件去批量搬运
程序查询方式是“CPU全程陪跑”,中断方式是“每传一个数据,CPU被叫醒一次”。这两种方式的本质都是:数据传送必须由CPU执行指令来完成。问题是,为什么数据传送一定要CPU亲自做?能不能设计一个专门的硬件控制器,让CPU只在最开始告诉它“从哪来、到哪去、传多少”,然后就撒手不管,由这个硬件控制器自己把整块数据传完?
这——就是DMA(Direct Memory Access,直接存储器存取)的核心思想。DMA方式下,外设与主存之间的数据传送,由DMA控制器(DMAC)直接负责,数据不经过CPU内部寄存器,而是直接在内存和外设之间建立数据通路。CPU要做的只有两件事:传送前做预处理(设置首地址、传送字数、启动命令),传送结束后做后处理(响应DMA的中断请求,检查传送是否正确)。
这样做的收益非常明显:传送一个数据块,CPU只在开始和结束的时候介入,中间的整段数据搬运完全由DMA控制器完成。用中断方式传128字节可能要中断128次,用DMA方式整个数据块只需要CPU参与一次预处理和一次后处理。这就是DMA方式在需要高速大批量数据传送的场景(磁盘、网卡、显卡、串口高速通信)中成为标配的根本原因。
2. DMA的三种传送方式:搞懂“周期挪用”就赢了一半
2.1 三种方式的定义和特点
在408教材里,DMA的数据传送方式通常有三种:CPU停止法(成组传送)、周期挪用(周期窃取)、DMA与CPU交替访存。三种方式的区别,核心在于“DMA控制器占用总线的时机和长度”不同。
CPU停止法最简单粗暴:DMA控制器获得总线控制权后,CPU就完全让出总线的使用权,DMA独占总线,连续传送完整个数据块,然后才把总线还给CPU。这种方式的好处是控制逻辑简单,传送速度快;缺点是CPU在数据块传送期间基本被“架空”,虽然理论上有DMA控制器的系统里CPU还能干点不访存的内部操作,但对于没有Cache的早期系统,CPU几乎等于停机等待。所以这种方式更适合高速外设且数据块很大的场景——反正CPU也插不上手,干脆让DMA一次干完。
周期挪用法,也叫周期窃取,是教学和考试中的重点。它的思路是:DMA控制器不独占总线,而是每传送一个数据字,就向CPU“借用”一个总线周期。用完立即归还,然后等下一个数据准备好,再次申请总线周期。这种方式把大块数据传送切成了一个个小的总线周期,对CPU的影响明显减小,CPU只是偶尔被“插空”暂停一个总线周期,大部分时间照常执行程序。
DMA与CPU交替访存,也叫透明方式。这种方式要求主存可以被两个端口同时访问,CPU和DMA控制器各自使用独立的地址线和数据线,按时间片轮流访存。每个存储周期内,一半时间给CPU用,一半时间给DMA用。这种方式CPU几乎感觉不到DMA的存在,但硬件代价很大,需要双端口存储器,所以应用场景相对受限,考试中以了解为主。
2.2 周期挪用到底是“怎么偷”总线的
周期挪用的过程值得细抠,因为这是考试喜欢出选择题和简答题的地方。我把它拆成几个时间点来看。
外设准备好一个数据后,向DMA控制器发出DMA请求(DREQ)。DMA控制器收到这个请求后,向CPU发出总线请求(HRQ,Hold Request),表示“我想用一下总线”。CPU在执行完当前正在进行的总线周期后,检测到HRQ信号,会让出自己的总线控制权,回一个总线应答信号(HLDA,Hold Acknowledge)。DMA控制器收到HLDA后,获得总线使用权,用一个总线周期把外设准备好的这个数据写入主存(或从主存读出到外设),然后立刻释放总线,把控制权还给CPU。
这里有几个容易搞错的地方。第一次学的时候,很多人以为周期挪用是“DMA把整个数据块一次搬完,搬完才释放总线”,其实不对。周期挪用是每次只挪用一两个总线周期,用来传一个数据字,传完就释放。整个数据块是由一次次短促的挪用拼凑完成的。也就是说,外设准备好一个字,DMA来挪用一个周期;准备下个字,再来挪用一次。所以周期挪用特别适合外设速度不是特别快、数据是一个一个准备好的情况。
还有一个必须注意的点:CPU让出总线的时机,是当前总线周期结束之后,而不是当前指令执行完之后。450毫微秒的一个总线周期之后,CPU就会把总线交给DMA。但CPU内部的指令执行可能还在继续,如果指令流水线需要访问主存,就会因为总线被占用而插入等待状态。所以周期挪用虽然影响小,但确实会延长CPU执行程序的时间,只是延长的量很小罢了。
2.3 三种方式的对比与适用场景
把三种方式放在一起看,它们之间的取舍关系非常清楚。CPU停止法适合数据块大、传送速率要求极高的场景,代价是CPU长时间离线;周期挪用适合大多数中高速外设,对CPU影响小、硬件实现适中,是实际中最常用的方案;交替访存对CPU影响最小,但需要有双端口存储器,成本最高。
考试里如果问“某外设每秒钟需要传送多少数据,采用周期挪用对CPU的影响有多严重”,本质上是在算“挪用比例”。比如外设每80个总线周期产生一个数据,那么DMA大约每80个周期挪用1个周期,CPU的访存效率大约损失1/80。如果外设速度很快,每两个周期就产生一个数据,DMA挪用比例就是50%,CPU的存取速度几乎被吞掉一半,这时就得考虑换用其他方案,或者提升总线带宽。
3. DMA控制器内部拆解:每个寄存器都是干什么的
3.1 核心寄存器组
DMA控制器不是简单的一个“数据搬运开关”,它内部包含一组寄存器,各自承担明确的任务。我把它们列出来,你在复习的时候最好能做到闭着眼默写。
主存地址寄存器(AR,Address Register),用来存放当前要访问的主存单元的地址。每传送一个数据,AR会自动加1(或减1),指向下一个主存单元。这个自动增减的过程完全由硬件完成,不需要CPU干预,这是DMA效率高的直接原因之一。
数据缓冲寄存器(BR,Data Register),用来暂存从外设读入或准备写往外设的数据。外设和主存之间往往速度不匹配,数据缓冲寄存器起到临时中转的作用。注意,DMA的数据缓冲寄存器每次暂存的通常是一个字,DMA控制器核心的数据通路宽度也就是一个字。
字计数器(WC,Word Counter),用来记录需要传送的数据总字数。每传送一个字,WC减1,当WC减到0时,说明数据块传送完毕,DMA控制器会向CPU发出中断请求,报告传送结束。
设备地址寄存器(DAR,Device Address Register),用来存放外设的设备号或外设的地址信息,用于选择对应的外设。在DMA启动时由CPU写入。
状态和控制寄存器,用来存放控制信息,比如传送方向(读/写)、传送方式(单字/成组)、中断允许位等。CPU通过向这些控制位写入数据来配置DMA的工作模式。
除了寄存器,DMA控制器内部还有DMA控制逻辑和中断机构。控制逻辑负责产生主存地址、修改字计数、发出读写命令、判断传送是否完成等操作;中断机构负责在数据块传送结束时向CPU发出中断请求,让CPU进行后处理。
3.2 工作流程:从预处理到后处理
DMA的完整工作过程可以分成三个阶段:预处理、数据传送、后处理。三个阶段里,只有预处理和后处理需要CPU参与,数据传送阶段CPU完全不干预。
预处理阶段,CPU执行几条指令,向DMA控制器写入必要的信息。具体包括:向设备地址寄存器写入外设地址,向主存地址寄存器写入主存起始地址,向字计数器写入传送的数据块长度,然后启动外设。这几步做完,CPU就回去继续执行自己的程序了。
数据传送阶段,完全是DMA控制器和外设之间的“双人舞”。外设准备好数据后发出DMA请求,DMA控制器协调总线使用权,把数据从外设写入主存,或者从主存读出送到外设,同时修改地址寄存器和字计数器。这个过程循环往复,直到字计数器归零。
后处理阶段,DMA控制器向CPU发出中断请求,CPU响应后进入中断服务程序,做一些收尾检查工作,比如确认数据是否传送正确、判断是正常结束还是出错结束,然后撤销外设的DMA请求信号,让外设停止工作。
这里有一个非常经典的考点:DMA方式下,数据传送阶段CPU不参与,但预处理和后处理阶段CPU是要参与的。很多人刚学的时候误以为“DMA完全不需要CPU”,然后把预处理和后处理也当成DMA控制器的活儿,这就大错特错了。DMA节省的是“逐字传送过程中的CPU开销”,而不是完全把CPU排除在I/O流程之外。
3.3 一个容易想错的点:DMA和CPU谁在控制总线
DMA控制器获得总线控制权之后,它在物理上替代CPU成为了总线主设备。这个过程中,CPU并没有“死机”,它只是暂时失去了对地址线、数据线和控制线的使用权。对于现代CPU来说,失去了总线使用权,意味着无法访问主存,也无法访问I/O端口,但它内部的寄存器操作、Cache命中访问这些不需要总线的操作还能继续执行。
所以准确说法是:DMA占用总线期间,CPU可能仍然在执行指令,但如果这些指令需要访存,就会被“挂起”。有些教材把这描述为“CPU停止工作”,那是简化说法。在早期的单总线结构中,因为没有Cache,CPU一旦失去总线,就真的什么都干不了了,所以那时的成组传送对CPU的影响接近100%。但在带Cache的系统中,CPU大部分时间访问Cache,对外部总线的依赖度降低,DMA对CPU执行的拖累会减轻不少。这个区别在衡量“DMA对CPU性能影响”时非常关键。
4. DMA、程序查询、中断:一张表看清三者的区别
4.1 数据通路与CPU介入程度对比
三种I/O方式的本质区别,可以从两个维度看:一是数据从哪里到哪里,二是CPU在每个环节里扮演什么角色。
程序查询方式下,数据从外设到CPU,再从CPU到主存。也就是说,CPU内部寄存器是数据传送的必经之路,主机和外设之间没有直接的数据通路。中断方式也是一样,每个数据都要经过CPU的寄存器中转。而DMA方式下,主存与外设之间由DMA控制器直接建立数据通路,数据不经过CPU内部寄存器。
正是因为数据通路不同,CPU的介入程度也完全不同。程序查询需要CPU逐字检查状态、逐字传送;中断方式虽然不需要CPU死等,但每一次传送都要CPU响应中断并执行传送程序;DMA只需要CPU预处理和后处理。这个介入程度的差异,直接决定了三种方式在高速大块数据传送场景下的性能表现。
我觉得可以这样理解:程序查询是CPU一把屎一把尿把每个字节伺候到位;中断是CPU每个字节被叫醒一次去接货;DMA是CPU一次性把收货单填好,让一个专门的搬运工(DMA控制器)把一整批货搬完,最后签个字确认收货。
4.2 响应时机、开销、速度的全方位对比
三种方式在多个维度上都有鲜明区别,我整理了一张对比表,复习的时候可以直接拿来用。
| 对比项 | 程序查询 | 中断方式 | DMA方式 |
|---|---|---|---|
| 数据传送单位 | 字(字节) | 字(字节) | 数据块 |
| 数据通路 | CPU寄存器中转 | CPU寄存器中转 | DMA控制器直达主存 |
| CPU介入程度 | 全程参与 | 每次传送都要响应中断 | 仅预处理和后处理 |
| 响应时机 | CPU主动查询 | 指令执行周期结束后响应中断 | 总线周期结束后让出总线 |
| 硬件代价 | 最低 | 较低 | 较高 |
| 适用场景 | 低速简单外设 | 中低速外设、实时性要求高的场景 | 高速大块数据传送场景 |
这张表里值得细看的是“响应时机”。程序查询不存在“响应”问题,因为CPU什么都可以查;中断请求是在一条指令执行完后被采样,因为要保证当前指令能够完整执行完,否则指令的原子性就被破坏了;而DMA总线的请求是在当前总线周期结束后被响应,因为总线周期比指令周期更短。很多填空题和选择题就喜欢考“DMA请求在什么时刻被响应”——答案是总线周期结束时,不是指令周期结束时。
在开销方面,中断方式的固定开销是“现场保存+现场恢复”,DMA的固定开销是“预处理指令+中断后处理”。当传送的数据量很小的时候,DMA的预处理开销可能比分次中断的累计开销还要大,所以DMA并不是“什么场合都好”,它适合数据块较大的场景。这个“块大小阈值”的概念,在工程上非常实用。
4.3 408考研高频考点:这些细节最容易被出题人盯上
结合历年真题的出题风格,我总结几个高频考点,你在复习的时候可以重点标注。
第一个是“谁在什么阶段做什么事”的判断题。比如:预处理阶段,主存起始地址、传送字数由CPU写入;传送阶段,主存地址的修改、字计数的减一是由DMA控制器硬件完成的;后处理阶段,对传送结果进行检查的是CPU。这种题干喜欢打乱主体让你判断对错,本质考的就是三个阶段的分工。
第二个是“周期挪用与CPU执行的关系”。比如给出一个机器周期和存取周期的数值,问DMA每传送一个数据字需要占用几个总线周期,或者一个数据块传送完,CPU被拖慢了多长时间。这类是计算题的基础。
第三个是“DMA与中断的区别”。DMA方式结束后要向CPU发中断请求,这是很多人的迷惑点——既然DMA已经不需要CPU了,为什么还要发中断?答案是:数据块传完了,CPU需要知道结果,以便进行错误检查、启动下一次传送等善后工作。这个中断是“整块数据传完才发一次”,不是“每个字发一次”,和中断方式的中断频率完全不同。
第四个是“I/O方式的选择”。给一个应用场景,问你选哪种方式最合适——键盘适合用中断或查询,硬盘和网卡适合用DMA。这类题目只要抓住“数据规模”和“速度要求”两个核心指标,基本不会错。
5. 做题与复习时最容易踩的坑
5.1 “一个数据块传送一次总线请求”到底对不对
很多辅导书和习题里有一个经典说法:DMA方式是“以数据块为单位传送”。这个说法本身没问题,但容易被误解成“DMA一次总线请求就传送整个数据块”。真相要看具体是哪种传送方式。
在周期挪用方式下,DMA每传送一个字就要发起一次总线请求,整个数据块由成百上千次短促的请求拼接完成。只有在成组传送(CPU停止法)方式下,才是一次总线请求、独占总线、连续传送整个数据块。所以,如果题目没有特别说明是成组传送,默认说“DMA每传送一个数据字都需要占用一次总线”,这在周期挪用场景下是成立的。
这个考点在选择题里极具迷惑性。出题人经常把“数据块传送”“总线请求次数”“周期挪用占比”混在一起,让你判断哪些说法正确。我的建议是:做题时先判断题目默认的DMA方式是哪一种,如果题目给了“外设准备好一个数据就发一次请求”的条件,那就是周期挪用;如果题目说“DMA启动后连续传送直到结束”,那就是成组传送。
5.2 周期挪用对CPU的影响怎么算
周期挪用的影响算起来有个关键前提取:DMA控制器挪用的是主存总线周期,而CPU执行指令需要多个总线周期。假设一个总线周期是200纳秒,CPU执行一条指令平均需要5个总线周期(即1微秒),那么DMA每挪用1个总线周期,CPU执行这条指令的时间就会从1微秒延长到1.2微秒。
更完整的计算方式是这样的:先算出外设产生数据的速率,比如外设每秒要传送10000个字,每个字需要挪用1个总线周期。如果总线周期是200纳秒,那么DMA每秒挪用10000个总线周期,也就是挪用2毫秒的总线时间。再看CPU的总执行时间,如果CPU每秒执行5000微秒的指令,被DMA挪用2毫秒,CPU的有效执行时间减少了约0.04%。这里我给的数字只是一个示例,核心方法是:DMA每秒占用的总线周期数除以系统每秒可提供的总线周期总数,这个比值就是总线被占用的比例,也约等于CPU访存效率下降的比例。
但这里有个坑:不是每次DMA请求都能立刻被响应。如果外设的数据是突发到达的,多个DMA请求挤在一起,可能发生等待。所以实际影响还要考虑请求的分布。考试题目通常会理想化为均匀分布,做题时按比例计算即可。
5.3 几个高频易混概念:中断、DMA、通道、IOP
复习到I/O这一章,最让人头疼的是DMA和通道、IOP(输入输出处理器)之间的关系。很多人问:DMA已经有了,为什么还要通道?
我的理解是:DMA控制器本质上只能完成数据传送这一件事。它不知道数据块的意义,不会对数据进行加工,也不能管理多个外设的复杂调度。当系统里有大量高速外设,且需要不同设备进行优先级调度、数据格式转换、校验等复杂操作时,单靠多个DMA控制器会导致硬件逻辑极其复杂。于是出现了通道——一种比DMA更高级的I/O控制部件。通道可以执行通道程序,能控制多台外设,能在传送数据的同时做简单处理。IOP则更进一步,带有更强的运算能力,本质上是I/O子系统里的专用处理机。
考试里如果拿DMA和通道对比,记住几个关键差异:DMA通过硬件逻辑控制传送,通道通过执行通道程序控制传送;DMA适合单一或少数外设的大块数据传送,通道适合多台外设的复杂I/O管理;DMA的预处理功能简单,通道的功能更完备。做题时不会混淆,因为DMA的考题集中在寄存器、周期挪用和流程,通道的考题集中在通道程序、分类和与DMA的比较。
还有一种容易混淆的是“DMA与中断方式的结合”。DMA传送结束后向CPU发出中断请求,用的是中断机制;但整个数据传送本身不依赖中断。所以可以说“DMA用到了中断机制,但不是中断方式的数据传送模式”。这个概念理清楚,后面做题就不容易乱。
6. 从课本到工程:DMA在真实设备里是怎么用的
6.1 磁盘、网卡、显卡这些设备为什么非用DMA不可
课本上讲的DMA原理,到了真实系统里几乎是所有高速外设的标配。最典型的例子是磁盘。机械硬盘内部扇区读出来之后,如果靠CPU逐字去搬,以磁盘每秒几百兆字节的传输速度,CPU根本扛不住。磁盘控制器把读出的数据块直接通过DMA写入内存指定缓冲区,只在一个数据块读完后向CPU发一个中断,CPU从缓冲区取数据就行。这里的中断频率大概是磁盘转速决定的,比如每秒几百次,跟按字节传输的密集型中断完全不是一个量级。
网卡的收包过程也一样。网卡收到一帧数据后,通过DMA把数据直接写入内存中的接收环形缓冲区,写完后通知驱动去处理。如果每收一个字节就中断一次CPU,那千兆网卡一秒钟能产生上亿次中断,没有任何操作系统能承受这种负担。DMA的意义就在于把“频繁、大量、简单重复的数据搬运”从CPU身上彻底剥离。
还有图形设备。显卡的显存与主存之间的数据交换,无论是纹理上传还是帧缓冲更新,都走DMA通道。用户在屏幕上拖动窗口时,那部分图像的拷贝就是由DMA完成的。可以说,没有DMA,现代PC的多媒体体验会直接崩溃。
6.2 STM32里配置一次DMA,核心参数只有这几个
很多学计算机组成原理的同学同时也在接触嵌入式开发,说到DMA,STM32毫无疑问是入门首选。其实你在STM32上配置DMA时用的概念,跟课本完全对得上。
打开STM32的DMA配置,最重要几个字段是:外设地址、存储器地址、数据传输方向、传输数据量、外设地址增量、存储器地址增量和循环模式。外设地址对应课本里的设备地址寄存器,存储器地址对应主存地址寄存器,传输数据量对应字计数器。使能地址增量模式后,DMA每传一个数据,地址自动加1,对应课本说的AR自动修改;传输数据量写到0,就是字计数器归零,传送结束。
我举个串口接收的例子:用UART接收一帧不定长数据,传统做法是每个字节触发一次接收中断,CPU进中断把数据搬进数组。用DMA的做法是配置好UART的RX DMA通道,把目标地址设为数组首地址,数据量设为最大可能长度,开启UART的空闲中断,然后CPU完全不管接收过程。当一帧数据到达后,数据自动被DMA搬进数组,空闲中断触发后,CPU通过计算DMA剩余计数来推算实际收到的字节数。
这样配置下来,小数据块用DMA可能看不出什么优势,但大数据量(比如几十字节以上的不定长串口帧、ADC连续采样数据)时,CPU占用率的差别非常明显。课本里说的“数据块大才值得用DMA”,在真实MCU开发里是完全成立的。
6.3 实际工程中用DMA的三个经典坑
工程上踩过的坑,比书本上的考点更具体。第一个坑是DMA中断标志的清除顺序。很多MCU要求在读取剩余数据计数器之前或之后,按特定顺序清除中断标志,顺序颠倒可能导致漏掉最后一笔数据或者误触发一次中断。我调试UART DMA接收时,曾经因为标志清除顺序不对,每收完一帧就多进一次空闲中断,排查了很久才发现问题。
第二个坑是Cache一致性。到了带Cache的高性能CPU上,CPU通过DMA往内存写数据,但CPU的Cache里还留着旧数据;或者CPU改了内存数据但还没写回,DMA去读到的却是旧数据。这就是所谓的Cache与DMA数据不一致问题。Linux内核里有专门的DMA API(比如dma_alloc_coherent、dma_map_single)来处理缓冲区的Cache一致性,裸机或RTOS开发里也要留意,必要时做Cache无效化或flush操作。
第三个坑是外设速度与DMA频率的匹配。DMA虽然能把CPU解放出来,但如果外设数据到达速率超过DMA控制器的处理能力,DMA会产生溢出错误。比如ADC用DMA连续采样时,如果ADC的触发频率太高,DMA每采样一个数据都抢不到总线,就会丢数据。解决办法是提高DMA优先级,或者降低采样率,或者用双缓冲交替传送。课本里的周期挪用概念,在这种场景下可以从“CPU被拖慢”的角度反向理解——就算CPU让得再快,总线带宽始终是天花板。
DMA这块内容,我在考研那会儿也反复糊涂过,后来是通过两件事彻底想通的。第一件事是动手画了一张流程图,把预处理、传送、后处理三个阶段的分工用不同颜色标出来,每天睡前看一遍,连续看了一周就再也忘不掉了。第二件事是在STM32上真的跑了一次串口DMA接收,亲眼看到数据在CPU完全不管的情况下自动进了内存,之前所有抽象的概念一下就落地了。
如果你现在正被DMA的各种细节绕得头疼,我建议你也试试这两个方法:先画图,再动手。408的考点虽然偏理论,但背后对应的全是真实硬件的工作方式。顺着“外设准备好数据—DMA请求总线—CPU让出总线—数据直接进内存—传送完成发中断”这条主线去理解,比死记硬背寄存器名字管用得多。等你把DMA真正吃透了,再回头看程序查询和中断方式,你会对计算机I/O子系统形成一套完整的、体系化的认识,这比多刷几道题有价值得多。