尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

DMA直接存储器存取:从408高频考点到STM32与Linux工程实践

发布时间:2026/9/27 1:56:22

资讯中心
01
ARTICLE

DMA直接存储器存取:从408高频考点到STM32与Linux工程实践

DMA直接存储器存取:从408高频考点到STM32与Linux工程实践
计算机组成原理里I/O那一章的知识点密度在408里不算最高但DMA这块绝对是绕不开的重点。说白了DMA解决的是一个很朴素的问题数据从外设进内存、从内存出外设凭什么非要CPU一趟一趟地搬程序查询、程序中断都绕不开CPU这个搬运工可搬到DMA这里CPU就退居二线了——它只管两头交接中间那段独自完成数据搬运的是DMA控制器。这篇文章把DMA的硬件组成、三种传送方式、完整工作流程、考试区别点以及STM32和Linux里的实际用法都过一遍无论你是正在准备408初试还是想把组原知识落到工程里都能理出一条清晰脉络。1. 为什么I/O控制方式走到了DMA这一步1.1 程序查询和程序中断各自的“累”在哪最早的程序查询方式干的事特别实在CPU不停去读设备的状态寄存器看看设备准备好了没有。准备好了就读一个字节没准备好就继续轮询。这种方式的缺点太明显了——CPU绝大多数时间都浪费在“等”上面就好比你一直在窗口前盯着菜好了没其他啥也干不了。程序查询方式在简单场景下没有硬件开销但CPU利用率低得吓人。程序中断方式就聪明了一点设备准备好了主动喊CPUCPU响应中断后执行中断服务程序把数据搬走。好处是CPU不用一直轮询等待可以做自己的事。但坑在于每传送一个字节或者一个字都要触发一次中断中断服务程序里要保护现场、恢复现场、识别中断源杂七杂八的开销非常大。如果设备一秒钟产生几十万个字节CPU就什么也别干了光处理中断就忙不过来。我复习的时候老师举过一个例子说一个高速设备按字节频繁中断CPU的有效计算能力会被吃掉一大半这个印象特别深。1.2 DMA为什么不搬数据只做调度DMA的全称是Direct Memory Access直接存储器存取。它的核心思想就是数据搬运这件事由专门的硬件——DMA控制器来做CPU不再参与逐字搬运而是把任务交代清楚之后就可以去忙别的了。整个过程看起来像是这样的CPU先对DMA控制器说好数据从哪里来、到哪里去、一共搬多少然后启动设备。接下来DMA控制器自己申请总线的使用权把数据一个接一个地从外设搬到主存或者从主存取出来写给外设。等一整块数据都搬完了DMA控制器再向CPU发出一个中断请求告诉CPU“活干完了你来验收一下”。CPU在这里只出场两次一次是开头布置任务一次是结尾检查结果中间那段最耗时的搬运动作CPU完全解放了出来。这带来的变化是本质性的数据传送单位从程序中断方式下的“字”变成了“数据块”中断频率从每字一次变成每块一次CPU的开销大幅下降。408大纲里另外还有一个通道方式你可以把通道理解成DMA的加强版它甚至能自己执行通道程序来管理多台设备但考试重点还是DMA考生必须把DMA吃透。2. DMA控制器的硬件结构与三种传送方式2.1 DMA控制器的六个关键部件DMA能独立干活靠的是一堆专用寄存器。把这几个部件搞清楚后面看流程图才不会晕。部件作用由谁初始化主存地址寄存器MAR存放当前要访问的主存地址CPU在预处理阶段写入首地址每传一个字自动1传送长度计数器WC记录还剩多少个字没传每传一个字减1减到0代表传完CPU写入总字数数据缓冲寄存器DBR暂存从外设读入或将要写出的一个字DMA控制器自己使用DMA请求触发器向CPU发出总线请求信号的开关预处理时由CPU启动控制/状态寄存器记录传送方向读/写、启动/停止、错误状态CPU写入控制字段中断机构整块传送结束时向CPU发中断请求DMA内部逻辑触发MAR和WC这两兄弟是最重要的。一个负责“地址走到哪了”一个负责“还剩多少”每搬一个字MAR自增、WC自减两者配合就把一整块数据按顺序放到主存里了。预处理阶段CPU把首地址和字数填进这两个寄存器之后后面就不用管了这正是DMA能够独立传送的硬件基础。另外还要知道DBR是DMA的数据通路整个传送过程数据不进CPU的通用寄存器。2.2 三种传送方式停止、挪用、交替DMA控制器获得总线之后以什么节奏来占用主存有三种经典做法。第一种是停止CPU访问主存。这种方式下DMA一旦获得总线使用权就会连续传送完整一个数据块期间CPU完全不能访问主存只能等着。优点是控制简单适合高速外设大批量传送缺点是CPU被晾在一边哪怕想读个指令都做不了实时性差的场景会很吃亏。第二种是周期挪用也叫周期窃取。这是408考得最多的一种。DMA每传送一个字就临时占用一个主存存储周期搬完一个字马上把总线还给CPU。这里要分情况如果CPU当时正在访存DMA就得等当前这个存储周期结束如果CPU刚好在执行不需要访存的指令DMA就趁机插进来“偷”一个周期。周期挪用对CPU的影响比第一种小得多代价是DMA的传送会被打断外设的数据缓冲寄存器需要有足够的容量来暂存数据防止数据覆盖。第三种是DMA与CPU交替访问。这种方式把主存时间切成等分的时间片一半给CPU一半给DMA两者轮流使用主存看起来就像互不干扰一样。优点是CPU基本感知不到DMA的存在缺点是对硬件要求高主存速度必须足够快才能支撑这种交替切换实际系统里很少用这招教材里更多是为了讲清概念。三种方式的对比一张表就能说清楚方式对CPU的影响适用场景硬件复杂度停止CPU访存数据块传送期间CPU整个等待高速外设、大批量连续数据低周期挪用每个存储周期被插一下外设速率不恒定、数据量中等中交替访问几乎透明主存足够快、共享紧密高3. DMA工作流程全解预处理、数据传送、后处理3.1 三阶段谁在干活、谁在看戏DMA的一次完整传送过程可以拆成三个阶段这三个阶段必须分清是谁在干活因为考试特别喜欢在这里设问。预处理阶段CPU执行I/O指令往DMA控制器里写参数。具体来说要做四件事一是设置传送方向告诉DMA是从外设读到主存还是从主存取数据写给外设二是把主存缓冲区首地址写入MAR三是把要传送的字数写入WC四是启动该设备。预处理阶段看起来简单但它是CPU唯一必须深度参与的阶段这部分开销不可忽略。数据传送阶段真正的主角是DMA控制器。DMA通过DMA请求触发器向CPU发出总线请求CPU在合适的时机让出总线。拿到总线后的动作重复循环把外设数据读入DBR再把DBR里的数据写到主存指定地址或者反向进行然后MAR加1、WC减1直到WC变成0。整个过程中数据完全没经过CPUCPU只是在周期挪用的时候被短暂“打扰”一下。后处理阶段当WC减到0DMA控制器通过中断机构向CPU发出中断请求。CPU响应后执行一段中断服务程序做的事情包括检查传送是否正确、有没有校验错误、要不要做后续处理。这里有个关键的考试坑DMA传送结束后CPU要响应一次中断但这不代表DMA方式靠中断来搬数据——中断只是用来做“验收工作”的真正的搬运动作在第二阶段就完成了。3.2 磁盘读数据实例与CPU占比计算用一个经典的磁盘读取例子走一遍流程顺便算一道408风味很浓的计算题。假设某磁盘的平均数据传输率是4MB/s每次和主存之间按4KB大小的块传送数据。DMA的预处理阶段需要1000个时钟周期后处理阶段需要500个时钟周期CPU时钟频率为50MHz。问DMA方式下CPU用于该磁盘I/O的时间占比大概是多少先算每传送一块数据需要的时间4KB除以4MB/s得到1ms。CPU在每块数据上的总开销是预处理加后处理也就是1500个时钟周期。CPU时钟频率50MHz意味着一个时钟周期是20ns1500个周期就是30us。那么CPU的开销占比就是30us除以1ms等于3%。这个3%非常能说明问题。如果改用程序中断方式假设每传送一个字节中断处理需要200个时钟周期4KB也就是4096字节仅中断处理的时间就花掉4096乘以200等于819200个时钟周期按50MHz算大约16.4ms比数据本身传送的1ms还高出16倍CPU几乎什么正事都干不了。这就是DMA方式的价值所在。做题的时候单位换算要特别小心MB/s和MHz这种“M”不是同一个东西一个是字节一个是赫兹很多人栽在这上面。4. 408高频陷阱DMA与程序中断方式的核心区别4.1 一张对照表看清CPU参与度408选择题里DMA和程序中断方式的概念辨析属于每年都可能出现的高频考点而且挖坑手法很固定就是让你判断“某个动作到底是谁做的”。把下面这张表记牢基本能压住大半考题。对比维度程序中断方式DMA方式数据传送执行者CPU执行中断服务程序搬数据DMA控制器硬件直接搬数据传送单位字或字节数据块响应时机一般在指令执行周期结束后响应可在存储周期结束时响应粒度更细CPU介入程度每个字/字节都要CPU深度参与只在预处理和后处理阶段介入现场保护每次中断都要保护与恢复现场数据传送期间不需要现场保护中断频率每传一个字/字节中断一次每传完一个数据块中断一次应用场景低速外设、随机事件高速外设、成块数据这里有个特别容易错的概念DMA和中断到底是什么关系。DMA的前后两个阶段确实都用了中断但这个中断只是“通知”性质的CPU在中断服务程序里做的不是搬运数据而是检查结果。而程序中断方式里的中断中断服务程序本身就是搬运数据的主体。一个是“请CPU来动手”一个是“请CPU来验收”性质完全不同。4.2 周期窃取、响应时机与Cache一致性周期窃取是DMA方式中理解难度最高的点考试小题和计算题都爱从这里出。记住一条主线DMA请求的是总线使用权而CPU对I/O设备的中断请求是等当前指令执行结束才响应的。两者响应时机完全不一样一个是存储周期级别一个是指令周期级别。存储周期比指令周期短得多所以DMA对主存访问的“插入”可以非常频繁这也是它能支撑高速外设的原因。结合Cache的考点也要提前防着。DMA直接在主存和外设之间搬数据如果CPU用到的数据段在Cache里而且Cache采用的是写回法write-back那么Cache里被修改过的数据还没写回主存DMA却直接读了主存读到的就是过期数据这就是典型的一致性问题。写直达法write-through能解决这个问题因为每次写Cache都会同步写主存但代价是访存次数增加。工程上的做法更灵活比如让DMA缓冲区不使用Cache或者在DMA传输之前做缓存失效、传输之后做缓存清理后面讲Linux DMA API时会再碰见这套思想。5. 面对24年45题这类大题拆题思路与时间线画法5.1 考查风格和常见挖坑点24年统考的大题里I/O部分就瞄着DMA来考网上考研圈讨论得挺热闹。这道题我没有原卷但从大家回忆和讨论的风格来看它沿用了组原大题一贯的做法把DMA、存储周期、Cache这些点拧在一起让你在同一个时间系统里算开销、判断CPU行为。复习DMA大题先要知道命题老师喜欢在哪里挖洞。第一个坑是把响应时机记混DMA在存储周期结束时就可以请求总线不是非要等到指令执行结束。第二个坑是忽略预处理和后处理的时间有人算完数据传送占用的时间就觉得完事了忘了CPU两头还有开销。第三个坑是不考虑访存冲突DMA每窃取一个存储周期如果CPU本来也要访存CPU就被拖慢这个延迟在很多题目里要单独算。第四个坑是混淆Cache与主存数据能命中Cache的时候CPU对主存的访问次数会下降DMA窃取周期造成的影响也会跟着变化。5.2 三步拆解DMA计算/分析题我复习后期做这种题形成了一套固定的拆解顺序分享出来大家可以参考。第一步读题先标方向。看清题目说的是外设往主存传还是主存往外设传。方向定了才知道DMA里数据怎么流转也才知道CPU访存次数会不会变多。第二步画时间线。一条线上放CPU的动作另一条线上放DMA的动作。预处理、数据传送、后处理三个阶段分别标出来。数据传送阶段里再按每个存储周期或者每传送一个字切细分段把整个块传送的耗时拆明白。时间线画对了这道题基本上就解了八成。第三步统一单位算总量。所有时间要么统一成周期数要么统一成微秒或者纳秒。MB/s、MHz、K、M这些前缀非常容易把人绕晕我吃过亏之后习惯先把所有数据翻译成“周期的数量级”再代入计算。还有一个经验题目如果给了Cache命中率一定要把Cache命中情况单独考虑。CPU访问主存的次数并不等于CPU产生访存请求的次数Cache挡住了一部分主存访问DMA窃取主存周期时和CPU冲突的概率就会变低这种联动关系是近年计算题的进阶考法。6. 从考研考场到工程现场STM32与Linux里的DMA6.1 STM32串口DMA接收与ADC多通道采集课本上DMA的原理放到STM32里是特别好的验证场景。我最早用标准库调串口DMA的时候对着参考手册配寄存器配了半天后来发现用DMA_InitTypeDef结构体配置其实特别直观。以串口接收为例配置的核心逻辑是外设基地址填USART1-DR内存地址填一个接收缓冲区数组传输方向设成外设到内存数据宽度设为字节缓冲区大小就是一次要接收的数据量模式设成循环接收这样DMA就能一直在后台把串口收到的字节搬进缓冲区。DMA_InitTypeDef DMA_InitStructure; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)USART1-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)rx_buf; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize RX_BUF_SIZE; DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode DMA_Mode_Circular; DMA_InitStructure.DMA_Priority DMA_Priority_High; DMA_InitStructure.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_Channel5, DMA_InitStructure); DMA_Cmd(DMA1_Channel5, ENABLE);串口接收不定长数据时最常用的组合是DMA加IDLE空闲中断。DMA负责把数据搬进缓冲区IDLE中断负责判断一帧数据发完了没有。判断已经收到多少数据不是自己维护计数而是读取DMA当前剩余计数寄存器用缓冲区总长度减去当前剩余值得到的就是本次实际收到的字节数。这个技巧在标准库和LL库里都有对应的API非常重要。配合ADC多通道采集的时候DMA的价值更加直接。多个通道转换结果在数据寄存器里不断刷新CPU如果逐个通道去读不仅浪费时间还容易漏数据。配置DMA把ADC的数据寄存器映射到一个数组上转换完一个通道DMA就自动把结果搬到数组对应位置完全不用CPU动手。实测下来这样做之后CPU占用率几乎可以降到零CPU可以专心跑显示刷新或者通信协议栈。6.2 Linux DMA API一致性映射与流式映射到了Linux驱动里DMA不再是裸寄存器操作而是通过一套标准API来管理。刚接触内核驱动的人常常困惑为什么不能直接把物理地址给设备原因在于现代SoC里往往有IOMMU或者SMMUDMA控制器看到的地址空间和CPU看到的物理地址空间不是一回事驱动必须通过DMA API来完成地址映射这既是兼容性的需要也是安全和隔离的需要。Linux里最常用的两个概念是一致性映射和流式映射。一致性映射用dma_alloc_coherent分配它保证CPU和设备看到的内存视图是一致的不需要手动维护Cache同步适合那些需要长期存在的DMA缓冲区。流式映射用dma_map_single和dma_unmap_single每次传输前建立映射传输结束后解除映射如果Cache不是一致性的话还需要调用dma_sync_single_for_device或者dma_sync_single_for_cpu来刷新Cache。dma_addr_t dma_handle; void *buf dma_alloc_coherent(dev, size, dma_handle, GFP_KERNEL); // buf 给CPU侧使用dma_handle 给DMA控制器使用这一段刚好呼应了前面讲的Cache一致性问题。课本上讨论写回法下的Cache和DMA冲突工程里就是用DMA API在软件层面解决的。理解了这层关系再回头看组原课本你会觉得DMA不是一个抽象的知识点而是一套真实的、每天都在运转的硬件机制。我在实际调串口DMA接收的时候还踩过一个特别典型的坑DMA缓冲区长度和实际数据长度不一致导致接收位置算错。后来养成了一个习惯就是牢记“剩余计数寄存器当前值要随时去读”不要自己在中断里额外维护一个偏移量否则一旦遇到DMA循环换区位置就乱了。这让我对课本上“传送长度计数器自动递减”这件事印象深刻原来它不只在考试里有用写固件的时候也是核心寄存器。408考的是原理工程考的是细节但底层那套逻辑从来没变过CPU布置任务DMA控制器埋头搬运传完再通知CPU。把这条主线抓住无论面对真题还是实际项目里的DMA配置都不会慌。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。