初识DMA:为什么它这么重要?

各位同学,今天我们来聊聊嵌入式开发中最基础也最关键的一环——DMA。说白了,DMA就是让你系统里的CPU能从繁重的数据搬运任务中解放出来。我个人习惯把DMA看作CPU的“快递小哥”:CPU只负责下订单,真正把货从一个地方搬到另一个地方,都是快递小哥干的。

为什么非要搞个DMA?你想想看,CPU本来是要处理业务逻辑的,结果被内存拷贝这种苦力活拖累了。我在项目中遇到过这样的场景:一个4核处理器,跑网络协议栈时,三个核都闲着,就一个核被中断和内存拷贝压得喘不过气。后来加了DMA,情况完全变了。这就是我们这节课要讲的核心——DMA到底是什么,它凭什么这么牛,以及它到底能在哪些地方派上用场。

1.1 DMA的定义

DMA是Direct Memory Access的缩写,中文叫直接存储器访问。嗯,这个名字其实挺直白的——它就是一种允许硬件设备(比如磁盘控制器、网络接口卡、声卡等)直接读写系统内存的技术,完全不用经过CPU的干预。

我之前带过一个刚入行的工程师,他总以为DMA是个独立的芯片。其实不对,DMA通常是集成在SoC内部的模块,或者直接挂在系统总线上。你可以在数据手册里找到它,比如STM32系列里的DMA控制器,或者x86架构里的IOMMU。

核心概念: DMA控制器本身就是一个总线主设备,它能接管总线控制权,在内存和外设之间直接传输数据。

1.2 DMA相比PIO有什么优势?

要理解DMA的价值,最简单的方法就是拿它和另一种方式对比——PIO(Programmed I/O,程序控制输入输出)。

维度 DMA PIO
定义 硬件自动完成数据传输 CPU通过指令逐字传输
CPU占用 只有启动时介入,之后完全解放 整个传输过程CPU全占
速度优势 可达总线带宽上限 受限于CPU指令执行速度
典型应用 大块数据拷贝、高速外设 小数据量、配置寄存器

我个人习惯用一句话总结这俩的差别:PIO就像你自己一趟趟搬砖,DMA就是你雇了辆卡车。

我曾经踩过的坑: 在某个项目里,我刚开始用PIO去读取一个USB摄像头的高分辨率帧数据,结果CPU占用率直接飙到95%,系统几乎动不了。我当时一拍脑袋——嗯,这里不对,必须上DMA。换了之后,CPU占用降到5%左右,系统流畅得像换了台新机器。所以记住:大块数据,不要用PIO。

1.3 DMA的典型应用场景

DMA能干的事其实比你想象的多。我整理了三个最常见的场景,你在实际项目中大概率会遇到。

场景一:内存到内存的拷贝

这是最直接的应用。比如你要把一块数据从内存的A区搬到B区。如果用CPU循环拷贝,那就太浪费计算资源了。DMA可以一次性搞定。特别是在多核系统里,DMA拷贝甚至能跨内存域,不用核间中断去打扰其他核。

// 伪代码示例:使用DMA做内存拷贝
// 假设我们有一个DMA描述符链
struct dma_desc {
    uint32_t src_addr;
    uint32_t dst_addr;
    uint32_t length;
    uint32_t next_desc;
};

// 配置一个DMA通道
dma_channel_config ch = {
    .direction = DMA_MEM_TO_MEM,
    .transfer_size = sizeof(uint32_t),
    .src_addr = (uint32_t)src_buf,
    .dst_addr = (uint32_t)dst_buf,
    .length = BUFFER_SIZE
};

// 启动DMA
dma_start(&ch);

// CPU可以去做其他事了
// 等DMA传输完成后,会触发一个完成中断

场景二:高速外设数据传输

以太网、USB3.0、PCIe这些高速外设,天生就是DMA的客户。你不信可以想想看:一个千兆网卡,每秒要处理125MB的数据,如果每个数据包都走CPU,那CPU就得疯掉。实际上,现在的网络控制器全部自带DMA引擎,甚至还有多队列DMA来分摊负载。

一个小技巧: 在配置外设的DMA传输时,我建议你注意一下“对齐地址”。很多DMA控制器要求源地址和目标地址按字或按缓存行对齐。如果不满足,可能会触发异常或降低效率。我一般在分配DMA缓冲区时,都用cache-line对齐的分配函数。

场景三:低功耗场景下的数据采集

这可能是你平时不太注意,但实际很重要的场景。比如一个传感器节点,需要每隔1毫秒采集一次数据。如果每次采集都唤醒CPU,那功耗根本降不下来。但用DMA的话,CPU可以进入深度睡眠,DMA自己把采集到的数据搬到一个环形缓冲区里,等攒够了一包数据,再一次性唤醒CPU处理。这种做法我在一个智能手表项目里用过,成功把电池续航从3天拉到了7天。

1.4 DMA与中断绑定的前置关系

前面讲的都是基础知识。但我们的课程重点是多核平台的DMA中断绑定。在进入这个话题之前,你需要先理解一个关键点:DMA传输完成后,必须通知CPU。这个通知机制就是中断。

在多核系统里,事情就没那么简单了。哪个CPU应该响应该DMA完成中断?如果中断发给了不合适的核,轻则导致负载不均,重则造成缓存抖动和性能下降。所以,我们需要把DMA完成中断“绑定”到最合适的CPU核心上。

这就是我们后面几节课要讲的核心内容。现在,我给你画个全景图,让你对DMA的整体位置有个清晰的认识。

DMA技术全景图 DMA控制器 CPU核心 系统内存 高速外设 启动命令 数据读写 数据传输 完成中断 关键流程: CPU启动DMA → DMA直接读写内存和外设 → 完成后通过中断通知CPU

这张图很清楚。CPU只需要发一个启动命令给DMA控制器,然后DMA自己去跑数据搬运,最后发个中断告诉你“活干完了”。简单吧?但要把这个“中断”在多核环境里安排得明明白白,那才是真功夫。

好了,第一课就到这里。你对DMA已经有了一个全景式的认识:它到底是什么、相比PIO的优势在哪、以及实际项目里怎么用。我们后面会一步步深入,讲清楚怎么在多个CPU核心上把DMA中断绑定得最优。你先把这些基础消化好,后面的内容才稳得住。