1. 从一道408真题说起DMA到底在考什么如果你正在啃计算机组成原理尤其是唐朔飞或者白中英那套教材翻到I/O那一章的时候大概率会被一堆控制方式绕晕程序查询、程序中断、DMA、通道……名字听着都懂但一到做题就分不清谁是谁。2024年408那道第45题考的就是DMA很多人栽在“周期挪用”和“停止CPU访存”这两个概念的细节上。DMA全称Direct Memory Access直接存储器访问。说白了它就是一个“搬砖队长”——数据在主存和I/O设备之间搬运这件事以前是CPU亲自一箱一箱扛现在CPU说“我太忙了找个专职搬运工吧”这个搬运工就是DMA控制器。CPU只需要告诉它从哪搬、搬到哪、搬多少、搬完了喊我一声。剩下的具体搬运过程DMA控制器自己搞定CPU该干嘛干嘛去。这篇文章我打算把DMA方式从“为什么需要它”到“具体怎么工作”再到“考试怎么考、实际怎么用”完整捋一遍。不管你是正在准备408考研的学生还是学STM32时被串口DMA、ADC多通道DMA搞得头大的嵌入式新手又或者只是想把计算机组成原理里这块知识补扎实的开发者这篇内容都能让你把DMA这件事彻底搞明白。我会尽量用生活化的类比把原理讲透同时把408考试里那些容易踩的坑、常考的细节都标出来。2. 为什么会有DMA三种I/O控制方式的进化逻辑2.1 从“死等”到“打断”再到“专职搬运”要理解DMA得先知道它前面两代方案差在哪。程序查询方式是最原始的。CPU给I/O设备发个命令然后就在那儿循环检查状态寄存器——“好了没好了没好了没”设备没准备好CPU就一直在那空转。这就好比你点了外卖然后站在门口一直盯着猫眼等啥也干不了。效率极低CPU和I/O设备完全串行工作。程序中断方式进了一步。CPU发完命令就去干别的事了设备准备好之后主动发一个中断信号给CPUCPU收到中断后暂停手头的活转去处理数据传送。这就像外卖到了骑手给你打电话你放下手头的事去拿。比死等强多了但每次传送一个字节或一个字都要打断CPU一次。如果传1MB数据按每次传4字节算那就是26万次中断。CPU光忙着响应中断、保存现场、恢复现场了真正干活的时间被切得稀碎。DMA方式的思路完全不同。它不是“一个字节一个字节地打断CPU”而是“整块整块地搬搬完了再说”。CPU只需要在开始的时候做一次初始化配置然后DMA控制器接管总线直接在内存和I/O设备之间开一条数据通道。数据搬完了DMA控制器再发一个中断告诉CPU“活干完了”。整个过程中CPU只在开头和结尾参与中间完全不用管。这三种方式的进化逻辑其实就一条主线让CPU越来越少地参与数据搬运的具体过程。程序查询是CPU全程参与程序中断是CPU频繁参与DMA是CPU几乎不参与。再往后还有通道方式那是更高级的形态连DMA控制器的初始化都能批量处理这里不展开。2.2 DMA的核心价值解放CPUDMA最核心的价值就四个字解放CPU。在需要大量数据高速传送的场景下比如磁盘读写、网络数据包收发、图像采集、音频播放数据量动辄几KB到几MB如果还用中断方式一个字节一个字节地传CPU基本上就被绑死了。我拿STM32上的实际场景举个例子。你用ADC采集8个通道的模拟量采样率设到100kHz每个通道每次采12位数据。如果用中断方式每采一个点就进一次中断100kHz就是每秒10万次中断。STM32的主频就算跑到168MHz光处理中断上下文切换就够呛根本没时间跑你的主循环逻辑。但换成DMA方式ADC采完的数据自动通过DMA搬到内存数组里你只需要在DMA传输完成中断里处理一批数据就行了。CPU占用率从接近100%直接降到个位数。这就是DMA存在的意义把CPU从繁重的数据搬运工作中解放出来让它专注于计算和控制。2.3 DMA与中断的本质区别很多人学到这里会有一个疑问DMA和中断到底有什么本质区别不都是CPU不用一直等着吗区别在于谁来搬数据。中断方式下数据传送本身还是CPU在执行中断服务程序时完成的——CPU执行指令把数据从I/O接口读到寄存器再写到内存。DMA方式下数据传送是由DMA控制器这个硬件直接完成的CPU根本不碰数据。CPU只是“授权”DMA控制器使用总线然后就不管了。打个比方。中断方式就像你请了个助理但每次搬东西还是你自己动手助理只负责提醒你“该搬了”。DMA方式就像你请了个搬运工你告诉他“把这堆货从A搬到B”然后你就可以去开会了搬运工自己推着车一趟一趟搬搬完了跟你说一声。这个区别直接决定了中断方式下每传一个数据单位都要执行若干条指令速度受限于CPU执行指令的速度DMA方式下数据传送由硬件直接控制速度可以接近总线的极限带宽。3. DMA控制器的内部结构与工作原理3.1 DMA控制器的核心组成DMA控制器不是随便就能当搬运工的它内部有一套完整的逻辑。一个典型的DMA控制器包含以下关键部件地址寄存器包括内存地址寄存器和设备地址寄存器。内存地址寄存器存放当前要访问的内存地址每传一个数据就自动加一或减一。设备地址寄存器存放I/O设备的接口地址。字计数器记录还剩多少数据没传。初始化时填入要传送的总字数每传一个字自动减一减到零就表示传完了发出中断信号。控制逻辑负责解析CPU发来的控制命令管理DMA请求和总线应答的时序决定什么时候占用总线、什么时候释放总线。状态寄存器记录DMA控制器当前的状态比如是否正在传输、是否出错、是否完成等。数据缓冲寄存器在内存和I/O设备之间做临时缓冲协调两边速度差异。这些部件协同工作让DMA控制器能够独立完成数据搬运任务。CPU在初始化阶段把源地址、目的地址、传送长度、传送方向等信息写入DMA控制器的寄存器然后启动DMA。之后DMA控制器就按照这些参数自动执行。3.2 DMA的工作流程拆解DMA的完整工作流程可以分成三个阶段第一阶段初始化。CPU执行一段程序把DMA控制器需要的参数写进去。具体包括内存起始地址、I/O设备地址、传送字数、传送方向读还是写、是否开启中断等。这一步CPU是主动参与的但只做一次。第二阶段数据传送。DMA控制器向CPU发出总线请求HOLD信号CPU在当前总线周期结束后响应发出总线允许信号HLDA把总线控制权交给DMA控制器。DMA控制器获得总线控制权后直接在内存和I/O设备之间传送数据。每传一个字地址寄存器加一字计数器减一。这个过程可能持续很多个总线周期。第三阶段结束处理。当字计数器减到零DMA控制器释放总线向CPU发出中断请求。CPU响应中断执行中断服务程序检查传送是否成功然后进行后续处理。这里有一个关键细节DMA控制器占用总线的方式。它不是一直霸占着总线不放而是每次传送一个数据单位就释放一次总线或者传完一组数据后释放。具体采用哪种方式取决于DMA控制器的类型和工作模式。3.3 三种DMA传送方式停止CPU访存、周期挪用、交替访问这是408考试的高频考点也是很多人容易搞混的地方。停止CPU访存方式DMA控制器要传数据的时候直接向CPU发一个信号要求CPU暂停使用总线。CPU收到信号后在当前总线周期结束后交出总线控制权DMA控制器开始连续传送一整块数据传完之后再把总线还给CPU。这种方式下DMA传送期间CPU完全不能访存只能做那些不需要访问内存的操作比如执行寄存器运算。优点是控制简单适合数据量很大的情况缺点是CPU被“冻住”了一段时间影响CPU效率。周期挪用方式也叫周期窃取。DMA控制器每次只“偷”一个总线周期来传一个数据传完立刻还给CPU。如果CPU正在使用总线DMA控制器就等一个周期如果CPU不用总线比如CPU正在执行不需要访存的指令DMA控制器就趁机传一个数据。这种方式对CPU的影响最小因为CPU只是在偶尔需要访存的时候被“插队”了一下。缺点是DMA控制器的控制逻辑更复杂而且每个数据传送都要经历一次总线请求和应答的过程效率相对低一些。交替访问方式把总线时间分成固定的时间片一部分给CPU用一部分给DMA用两者交替使用总线互不干扰。这种方式不需要总线请求和应答的过程效率高但需要更复杂的时序控制实际中用得不多。传送方式CPU影响控制复杂度适用场景停止CPU访存大CPU完全停止访存简单数据量大、高速传送周期挪用小CPU偶尔等待较复杂一般场景最常用交替访问无CPU和DMA分时使用最复杂对时序要求严格的场景考试里经常考的是周期挪用方式下DMA控制器和CPU争用总线的优先级问题。一般来说DMA请求的优先级高于CPU访存因为I/O设备的数据传送有实时性要求如果数据没及时取走可能会丢失。但CPU的指令执行不能被打断太久所以DMA也不能无限期占用总线。4. DMA与CPU的协作细节总线仲裁与优先级4.1 总线请求与应答的完整时序DMA控制器要使用总线必须经过一套“申请-批准”的流程。这套流程的时序在408考试里经常以选择题或大题的形式出现。当DMA控制器准备好传送数据时它会向CPU发出总线请求信号通常叫HOLD或BR。CPU收到请求后不会立刻响应而是等当前总线周期结束。为什么要等因为CPU可能正在执行一条需要访存的指令如果中途被打断指令执行就不完整了。所以CPU会在当前总线周期完成后发出总线允许信号HLDA或BG同时把地址总线、数据总线和控制总线置为高阻态让出总线控制权。DMA控制器收到HLDA信号后获得总线控制权开始传送数据。传送完成后DMA控制器释放总线撤销HOLD信号。CPU检测到HOLD信号撤销后重新获得总线控制权继续执行被暂停的操作。这个过程中有一个容易忽略的细节CPU在发出HLDA信号后并不是完全停止工作。CPU内部的一些操作比如寄存器之间的运算、指令译码可以继续进行只是不能访问内存。这就是为什么停止CPU访存方式下CPU并不是“死机”只是“不能访存”。4.2 DMA请求的优先级为什么高于CPU访存这个问题在考试里经常以“为什么DMA请求优先级高于CPU访存请求”的形式出现。答案的核心在于数据丢失的风险。I/O设备的数据传送往往有实时性要求。比如磁盘读出的数据如果不及时取走下一批数据来了就会覆盖掉比如ADC采样的数据如果不及时搬走下一次采样就会覆盖上一次的结果。而CPU访存请求晚一点处理顶多是CPU执行慢一点不会导致数据丢失。所以DMA请求的优先级必须高于CPU访存请求。但这里有一个平衡DMA也不能无限期占用总线。如果DMA一直霸占总线CPU就无法取指令、无法读写数据整个系统就卡住了。所以实际设计中DMA控制器通常会在传送完一个数据块后主动释放总线给CPU一个喘息的机会。4.3 DMA与中断的配合使用DMA和中断不是互斥的而是配合使用的。DMA负责数据传送中断负责传送完成后的通知。具体来说DMA控制器在传送完指定数量的数据后会向CPU发出一个中断请求。CPU响应这个中断执行相应的中断服务程序。这个中断服务程序通常做这些事情检查DMA状态寄存器确认传送是否成功、处理传送过程中可能出现的错误、准备下一次DMA传送的参数、通知上层软件数据已经就绪。这种配合方式的好处是CPU不需要在数据传送过程中频繁介入只需要在传送完成后处理一次。对于大批量数据传送效率提升非常明显。在STM32的实际开发中DMA传输完成中断是最常用的中断之一。比如你用串口DMA发送一帧数据配置好DMA之后调用发送函数然后就可以去处理其他任务了。当DMA把整帧数据都发送完毕后触发传输完成中断你在中断里可以置一个标志位或者启动下一次发送。整个过程CPU只在开头配置和结尾处理时参与中间完全不占用。5. 408考试中DMA的高频考点与解题套路5.1 周期挪用方式下的总线占用计算这是408大题里经常出现的计算题类型。题目通常会给出CPU主频、总线周期、DMA传送一个数据需要的时间、数据传送量等参数让你计算DMA传送期间CPU实际可用于访存的时间比例。解题的关键是搞清楚周期挪用方式下DMA每次只占用一个总线周期。假设CPU的主频为f一个总线周期为TDMA传送一个数据需要一个总线周期。如果DMA要传送N个数据那么DMA总共需要N个总线周期。在这N个总线周期里CPU无法访存。但CPU并不是完全不能工作它可以在DMA不占用总线的那些周期里正常访存。具体计算时要注意DMA请求和CPU访存请求可能同时发生这时候DMA优先。所以实际CPU可用的总线周期数 总周期数 - DMA占用的周期数。如果题目还给出了CPU访存概率比如CPU有50%的指令需要访存那计算会更复杂一些。我拿一道典型题目举例CPU主频500MHz总线周期为2个时钟周期DMA传送一个数据需要一个总线周期。现在要用DMA传送1000个数据求DMA传送期间CPU可用于访存的时间比例。解题思路总线周期 2 / 500MHz 4ns。DMA传送1000个数据需要1000个总线周期 4000ns。在这4000ns内CPU的总线周期数也是1000个因为总线周期相同。DMA占用了1000个总线周期中的1000个不对这里要仔细。DMA每传送一个数据占用一个总线周期传送1000个数据占用1000个总线周期。但CPU在这段时间内也有1000个总线周期可用。如果DMA和CPU争用总线DMA优先那么CPU实际可用的总线周期数 1000 - 1000 0这显然不对。问题出在DMA传送1000个数据并不是在1000个连续的总线周期内完成的。DMA控制器发出请求后CPU响应需要时间DMA获得总线后传送一个数据然后释放总线。下一个数据又要重新请求。所以实际的时间线是DMA请求 - CPU响应 - DMA传送 - DMA释放 - CPU使用总线 - DMA再次请求……这样交替进行。在周期挪用方式下DMA传送一个数据后释放总线CPU至少可以获得一个总线周期来访存如果CPU有访存需求的话。所以CPU可用于访存的时间比例取决于DMA请求的频率和CPU访存的需求。如果DMA连续不断地请求CPU可能只能获得很少的总线周期。这类题目的标准解法是先算出DMA传送一个数据的平均时间间隔再算出在这个间隔内CPU可以使用的总线周期数最后求比例。具体计算要看题目给出的条件关键是理解“周期挪用”的含义——DMA每次只借一个周期借完就还。5.2 DMA传送过程的中断次数计算另一个高频考点是用DMA方式传送一批数据总共会产生多少次中断答案通常是一次。因为DMA控制器在传送完所有数据后才发一次中断。但要注意题目中的细节如果题目说“每传送一个数据块就中断一次”那就要看数据块的大小。比如总共传1MB数据DMA每次最多传4KB那就是256次中断。还有一种情况题目问的是“DMA传送过程中CPU被中断的次数”。如果DMA采用停止CPU访存方式CPU在DMA传送期间完全不参与传送完成后才被中断一次。如果采用周期挪用方式CPU在DMA传送期间可能被“插队”多次但这些不是中断只是总线周期的让出。中断仍然只有传送完成后的那一次。考试里经常把“总线请求次数”和“中断次数”混在一起考。总线请求次数可能很多每次DMA传送一个数据都要请求一次总线但中断次数只有一次传送完成后。这两个概念要分清楚。5.3 DMA与中断方式的对比分析题这类题目通常以简答题或综合分析题的形式出现要求比较DMA和中断方式在数据传送上的区别。答题时要抓住几个核心维度数据传送的发起者中断方式下数据传送由CPU执行中断服务程序完成DMA方式下数据传送由DMA控制器硬件完成。CPU的参与程度中断方式下每传一个数据CPU都要介入DMA方式下CPU只在初始化和结束时介入。传送速度中断方式受限于CPU执行指令的速度DMA方式受限于总线带宽通常更快。适用场景中断方式适合数据量小、速度要求不高的场景DMA方式适合数据量大、速度要求高的场景。对CPU的影响中断方式下CPU频繁被打断效率低DMA方式下CPU几乎不受影响效率高。答题时最好能结合具体的例子来说明比如“磁盘读写用DMA键盘输入用中断”这样更有说服力。6. 从408到实战DMA在嵌入式开发中的典型应用6.1 STM32串口DMA收发从配置到调试学408的时候觉得DMA是个抽象概念但到了STM32上DMA是实打实要配置的。我拿串口DMA收发举个例子这是嵌入式开发中最常见的DMA应用场景。用STM32CubeMX配置串口DMA的步骤大致是这样的先使能串口然后在DMA Settings里添加DMA请求。串口发送对应DMA通道的Memory-to-Peripheral模式串口接收对应Peripheral-to-Memory模式。优先级一般设中等或高数据宽度根据串口数据格式设8位或16位。模式选择Normal单次传输还是Circular循环传输取决于你的应用场景。配置完成后生成代码在main函数里调用HAL_UART_Transmit_DMA()就可以启动DMA发送。这个函数会把数据地址、长度等信息写入DMA控制器然后立刻返回不阻塞CPU。你可以接着执行其他任务。当DMA发送完成后会触发DMA传输完成中断在中断回调函数里可以置标志位或者启动下一次发送。这里有几个实操中容易踩的坑。第一个坑是DMA传输完成中断和串口发送完成中断的区别。DMA传输完成中断表示DMA已经把数据从内存搬到了串口数据寄存器但串口可能还在发送最后一个字节。如果要确保数据完全发送完毕应该用串口的TCTransmission Complete中断而不是DMA的传输完成中断。我当初就因为这个坑在DMA传输完成中断里立刻切换了RS485的收发方向结果最后一个字节没发完就被截断了。第二个坑是DMA缓冲区的生命周期。DMA传输是异步的你传给DMA的缓冲区指针在传输完成之前必须保持有效。如果你在栈上定义了一个局部数组然后调用DMA发送函数函数返回后栈被回收DMA还在往那个地址搬数据就会出问题。所以DMA缓冲区一定要用全局数组或者静态数组或者用动态内存分配并确保在传输完成前不被释放。第三个坑是DMA和CPU同时访问同一块内存的冲突。如果DMA正在往某个数组里写数据CPU同时去读这个数组可能会读到半新半旧的数据。解决办法是用双缓冲或者乒乓缓冲DMA写一块的时候CPU读另一块通过中断切换。6.2 ADC多通道采集DMA解放CPU的经典案例ADC多通道采集是DMA的另一个经典应用。STM32的ADC可以配置成扫描模式依次采集多个通道。如果不用DMA每采完一个通道就要进一次中断读取数据CPU占用率很高。用DMA之后ADC每采完一个通道DMA自动把数据搬到内存数组里采完所有通道后触发一次DMA传输完成中断。配置要点ADC配置成扫描模式、连续转换模式或者由定时器触发DMA配置成Circular模式这样DMA会循环搬运不需要每次重新启动。数据宽度设为半字16位因为ADC结果是12位的用16位存储方便对齐。这里有一个细节ADC的DMA请求是在每次转换完成后发出的。如果ADC配置成连续转换模式DMA会连续不断地搬运数据。如果配置成单次转换模式每次转换完成后DMA搬一次然后需要软件再次启动ADC。实际项目中通常用定时器触发ADC转换这样采样率精确可控DMA在后台默默搬运CPU只需要定期处理数据数组就行了。我做过一个项目用STM32F4的ADC采集三相电流电压6个通道采样率20kHz。用DMA搬运数据CPU占用率不到5%。如果换成中断方式20kHz的采样率每采一个通道进一次中断6个通道就是120kHz的中断频率CPU基本上什么都干不了。6.3 DMA传输中的常见问题与排查思路DMA用起来爽但出问题的时候也让人头疼。我整理了几个常见问题和排查思路。问题一DMA传输不启动。排查步骤检查DMA时钟是否使能很多人忘了开DMA控制器的时钟、检查DMA通道是否配置正确、检查外设的DMA请求是否使能、检查DMA传输方向是否正确。问题二DMA传输的数据不对。排查步骤检查源地址和目的地址是否正确、检查数据宽度是否匹配比如外设是8位数据DMA配成了16位就会出错、检查地址是否递增有些外设的地址是固定的不能递增、检查DMA缓冲区的对齐方式。问题三DMA传输完成中断不触发。排查步骤检查中断是否使能、检查NVIC中的中断优先级配置、检查DMA传输完成标志是否被清除、检查DMA模式是Normal还是CircularCircular模式下传输完成中断的行为不同。问题四DMA和CPU争用总线导致系统卡顿。排查步骤检查DMA的优先级配置、考虑使用周期挪用方式而不是停止CPU访存方式、优化DMA传输的数据块大小避免一次传输太长时间。问题现象可能原因排查方法DMA不启动时钟未使能、通道配置错误检查RCC配置、检查DMA通道映射数据错误地址错误、数据宽度不匹配检查源/目的地址、检查数据对齐中断不触发中断未使能、标志未清除检查NVIC配置、检查中断标志位系统卡顿DMA优先级过高、传输块太大调整优先级、减小传输块大小7. DMA方式的知识边界与常见误区7.1 DMA能替代中断吗不能。DMA和中断解决的是不同的问题。DMA解决的是“数据怎么搬”的问题中断解决的是“事情发生了怎么通知”的问题。DMA传送完成后还是需要通过中断来通知CPU。而且不是所有I/O设备都适合用DMA比如键盘、鼠标这种低速设备数据量小用中断就够了上DMA反而是杀鸡用牛刀。7.2 DMA传送一定比中断快吗不一定。DMA的优势在于大批量数据传送时CPU占用率低但单次传送的延迟不一定比中断低。因为DMA需要总线请求和应答的过程如果数据量很小这个开销可能比直接中断处理还大。所以选择哪种方式要看数据量和实时性要求。7.3 DMA控制器能同时处理多个设备吗一个DMA控制器通常有多个通道可以同时处理多个设备的DMA请求。但同一时刻只有一个通道能获得总线控制权。多个通道之间需要仲裁优先级高的通道先获得服务。在STM32中DMA1和DMA2各有多个通道每个通道可以独立配置但同一时刻只有一个通道在传输。7.4 学了408的DMA在实际开发中有什么用这个问题我被问过很多次。408里讲的DMA原理是通用的虽然具体的寄存器配置和硬件细节跟STM32不完全一样但核心概念是一样的地址寄存器、字计数器、总线请求、周期挪用、传输完成中断。理解了这些概念再看STM32的DMA配置手册就会发现只是换了个名字而已。而且408考试里那些计算题训练的是你对总线时序和系统性能的分析能力这种能力在实际做嵌入式性能优化时非常有用。8. 我踩过的DMA坑与实操心得最后分享几个我在实际项目中使用DMA时踩过的坑都是教材上不会写的。第一个坑DMA传输完成中断里不能做耗时操作。DMA传输完成中断通常频率不低如果中断服务程序里做了太多事情会影响其他中断的响应。我一般只在中断里置一个标志位具体处理放到主循环里做。第二个坑DMA缓冲区的Cache一致性问题。如果你的MCU有Cache比如STM32H7系列DMA直接访问内存时可能绕过Cache导致CPU读到的是Cache里的旧数据。解决办法是在DMA传输前后做Cache清理或无效化操作。这个问题在F4系列上不存在F4没有Cache但到了H7上就必须考虑。第三个坑DMA和中断的优先级配置。DMA传输完成中断的优先级不能设得太低否则可能被其他中断阻塞导致数据处理不及时。但也不能设得太高否则会影响其他关键中断。我一般把DMA中断设成中等优先级具体要看系统里有哪些中断源。第四个坑DMA传输长度寄存器的位宽限制。STM32的DMA传输长度寄存器通常是16位的最大传输长度是65535。如果要传的数据超过这个数需要分多次传输。我当初传一张240x320的RGB565图片数据量是153600字节超过了65535结果DMA只传了一部分就停了。后来改成每次传一行或者用双缓冲才解决。第五个坑DMA请求的使能时机。有些外设的DMA请求需要在使能外设之前配置好有些则相反。比如STM32的ADC需要先配置DMA再使能ADC的DMA请求最后启动ADC转换。顺序搞错了DMA就不会工作。这个在参考手册里写得很清楚但初学者容易忽略。DMA这个东西理论上学一遍实际用一遍再回头看看408的题目会发现那些概念都变得很具体。唐朔飞教材里讲的“周期挪用”“停止CPU访存”在STM32的参考手册里就是DMA模式配置和优先级设置。白中英教材里讲的“DMA控制器组成”在STM32里就是DMA_HandleTypeDef结构体里的那些成员。把理论和实践对上号这块知识就真正扎实了。