DMA 技术文档
本页系统整理直接内存访问相关的核心概念、硬件接口、环境配置与安全须知。代码块均可一键复制。
DMA 基础原理
直接内存访问(Direct Memory Access,DMA)是一种让外设子系统在不依赖 CPU 逐条执行数据搬运指令的前提下,直接与主存储器交换数据的技术。它通过独立的 DMA 控制器获得系统总线的主控权,在后台完成批量数据传输,从而释放 CPU 去执行计算或控制任务。
典型工作流程包括:外设触发 DMA 请求 → DMA 控制器向 CPU 申请总线 → 总线仲裁授权 → DMA 执行一次或多次传输 → 传输完成触发中断并释放总线。理解这一流程是排查 DMA 不工作、数据错位或中断丢失等问题的基础。
核心术语
- 通道(Channel):DMA 控制器中的独立传输单元,通常与特定外设绑定。
- 源地址 / 目标地址:数据从哪来、到哪去,可以是外设寄存器或内存地址。
- 传输长度:一次 DMA 事务需要搬运的数据单元数量。
- 突发传输(Burst):DMA 在获得总线后连续传输多个数据单元,减少总线仲裁开销。
伪代码示例
// DMA 基础配置伪代码
void DMA_Config(uint32_t src, uint32_t dst, uint16_t len) {
DMA_Channel_TypeDef* ch = DMA1_Channel1;
DMA_DeInit(ch);
DMA_InitTypeDef cfg;
cfg.PeriphAddr = src;
cfg.MemAddr = dst;
cfg.Direction = DMA_DIR_PeriphSRC; // 外设到内存
cfg.BufferSize = len;
cfg.PeriphInc = DMA_PeriphInc_Disable;
cfg.MemInc = DMA_MemInc_Enable;
cfg.PeriphDataSize = DMA_PeriphDataSize_Word;
cfg.MemDataSize = DMA_MemDataSize_Word;
cfg.Mode = DMA_Mode_Normal;
cfg.Priority = DMA_Priority_High;
cfg.M2M = DMA_M2M_Disable;
DMA_Init(ch, &cfg);
DMA_ITConfig(ch, DMA_IT_TC, ENABLE); // 传输完成中断
DMA_Cmd(ch, ENABLE);
}
硬件通信方式
DMA 与硬件的交互方式取决于总线架构和外设接口。常见场景包括:外设通过专用 DMA 请求线向控制器发信号;PCIe 设备通过 MSI/MSI-X 中断或门铃机制触发主机 DMA;嵌入式 SoC 则通过 AHB / AXI 总线矩阵进行主从切换。
在 x86 平台,传统 DMA 依赖 8237 兼容控制器或现代 IOMMU 管理下的设备内存访问;在 ARM / RISC-V 平台,通常由 PL330、XDMA 或各厂商自定义 DMA IP 完成。无论哪种架构,都需关注地址对齐、缓存一致性与总线宽度匹配。
地址对齐与宽度
// 地址对齐检查示例
#define DMA_ALIGN_MASK 0x03U
bool is_dma_aligned(uint32_t addr, uint8_t width_bytes) {
if (width_bytes == 0) return false;
return (addr & (width_bytes - 1)) == 0;
}
// 使用示例
uint32_t buffer = 0x2000'1000;
if (!is_dma_aligned(buffer, 4)) {
log_error("Buffer not aligned for 32-bit DMA");
}
环境搭建
学习 DMA 通常需要准备目标硬件、调试工具与交叉编译链。以 STM32 为例,推荐安装 STM32CubeIDE 或 VS Code + PlatformIO;对于 Linux 内核驱动开发,则需要配置内核源码、交叉编译器与 QEMU / 真实开发板。
推荐工具链
- 编译器:arm-none-eabi-gcc、x86_64-linux-gnu-gcc
- 调试器:OpenOCD、ST-Link、J-Link
- 逻辑分析仪:Saleae、DSLogic(用于观察 DMA 时序)
- 宿主机:Ubuntu 22.04 LTS 或 Windows + WSL2
Makefile 片段
# 最小 Makefile 示例(ARM Cortex-M)
TARGET = dma_demo
CC = arm-none-eabi-gcc
CFLAGS = -mcpu=cortex-m4 -mthumb -O2 -Wall -g
LDFLAGS = -TSTM32F407VGTx_FLASH.ld -nostartfiles
SRCS = main.c dma.c startup_stm32f407xx.s
OBJS = $(SRCS:.c=.o)
$(TARGET).elf: $(OBJS)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(OBJS) $(TARGET).elf $(TARGET).bin
驱动相关
在操作系统层面使用 DMA,通常需要内核驱动配合。驱动负责分配物理连续的 DMA 缓冲区、配置设备寄存器、处理完成中断,并通过 mmap 或 read/write 向用户态暴露数据。Linux 中常用 dma_alloc_coherent、dma_map_sg 等 API。
用户态程序若直接访问 DMA 硬件,需要 root / 管理员权限,并可能绕过操作系统的内存保护机制。强烈建议在虚拟机或专用开发板上进行实验,避免损坏生产环境。
Linux DMA 缓冲区分配
// Linux 内核模块:分配一致性 DMA 缓冲区
#include
void* dma_buf;
dma_addr_t dma_handle;
struct device* dev = &pdev->dev;
size_t size = 4096;
dma_buf = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);
if (!dma_buf) {
dev_err(dev, "Failed to allocate DMA buffer\n");
return -ENOMEM;
}
// 使用 dma_buf(CPU 虚拟地址)与 dma_handle(设备总线地址)
// ...
dma_free_coherent(dev, size, dma_buf, dma_handle);
风险提示
重要安全提醒
DMA 能够绕过 CPU 直接读写物理内存,错误配置或恶意使用可能导致系统崩溃、数据丢失、隐私泄露或硬件损坏。本站所有资料仅供计算机底层技术学习与学术研究,禁止用于非法入侵、未经授权访问外部设备或任何违反法律法规的行为。
- 在受控、隔离的实验环境中测试 DMA 代码,切勿在生产机或他人设备上运行。
- 确保理解目标平台的内存布局与 IOMMU 配置,防止越界访问。
- 使用 DMA 前关闭或配置好缓存一致性,避免陈旧数据或脏数据问题。
- 遵守《网络安全法》等法律法规,所有操作后果由使用者自行承担。