Linux内核中的DMA管理:直接内存访问机制详解

张开发
2026/6/5 0:48:35 15 分钟阅读
Linux内核中的DMA管理:直接内存访问机制详解
Linux内核中的DMA管理直接内存访问机制详解作为一名深耕操作系统和嵌入式开发的工程师我对Linux内核中的DMADirect Memory Access管理机制有着深入的理解。DMA是一种重要的硬件特性它允许外设直接访问系统内存减少CPU的负担。DMA的基本概念DMA的核心思想是直接访问外设直接访问系统内存不需要CPU介入减少中断减少CPU中断次数提高系统性能批量传输支持大吞吐量的数据传输DMA的类型1. 系统DMA系统DMA由芯片组提供通常用于传统外设PCI DMAPCI设备的DMA传输ISA DMA传统ISA设备的DMA传输2. 设备自带DMA现代设备通常集成自己的DMA控制器网络控制器支持DMA的网络接口卡存储控制器支持DMA的磁盘控制器音频控制器支持DMA的音频设备DMA的核心API1. 分配DMA缓冲区// 分配一致性DMA缓冲区 void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t gfp) { // 分配物理连续的内存 // 确保CPU和DMA设备都能访问 return __dma_alloc_coherent(dev, size, dma_handle, gfp); } // 分配流式DMA缓冲区 void *dma_alloc_noncoherent(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t gfp) { // 分配内存可能不连续 // 需要显式同步 return __dma_alloc_noncoherent(dev, size, dma_handle, gfp); } // 释放DMA缓冲区 dma_free_coherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t dma_handle) dma_free_noncoherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t dma_handle)2. DMA映射// 映射内存到DMA地址写操作 dma_addr_t dma_map_single(struct device *dev, void *addr, size_t size, enum dma_data_direction direction) // 映射内存到DMA地址批量操作 int dma_map_sg(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction) // 取消映射 dma_unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size, enum dma_data_direction direction) dma_unmap_sg(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction)3. 同步操作// 同步CPU写操作到DMA设备 dma_sync_single_for_device(struct device *dev, dma_addr_t dma_handle, size_t size, enum dma_data_direction direction) // 同步DMA设备写操作到CPU dma_sync_single_for_cpu(struct device *dev, dma_addr_t dma_handle, size_t size, enum dma_data_direction direction) // 批量同步 dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction) dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction)DMA的实现原理1. DMA地址映射// DMA地址映射结构 struct dma_mapping_ops { void *(*alloc_coherent)(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t gfp); void (*free_coherent)(struct device *dev, size_t size, void *vaddr, dma_addr_t dma_handle); dma_addr_t (*map_single)(struct device *dev, void *ptr, size_t size, enum dma_data_direction direction); void (*unmap_single)(struct device *dev, dma_addr_t dma_addr, size_t size, enum dma_data_direction direction); int (*map_sg)(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction); void (*unmap_sg)(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction); void (*sync_single_for_cpu)(struct device *dev, dma_addr_t dma_handle, size_t size, enum dma_data_direction direction); void (*sync_single_for_device)(struct device *dev, dma_addr_t dma_handle, size_t size, enum dma_data_direction direction); void (*sync_sg_for_cpu)(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction); void (*sync_sg_for_device)(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction); };2. DMA引擎// DMA引擎结构 struct dma_chan { struct dma_device *device; struct list_head device_node; dma_cookie_t cookie; spinlock_t cookie_lock; struct list_head client_list; struct tasklet_struct tasklet; enum dma_status status; void *private; }; // DMA设备 struct dma_device { struct list_head channels; struct device *dev; char name[32]; int chancnt; struct dma_chan *chan; struct module *owner; struct dma_device_ops *ops; };使用场景1. 网络设备// 网络设备的DMA使用 struct sk_buff *skb netdev_alloc_skb(dev, size); if (!skb) return -ENOMEM; // 分配DMA缓冲区 void *buf dma_alloc_coherent(pdev-dev, size, dma_addr, GFP_ATOMIC); if (!buf) goto free_skb; // 映射到DMA地址 skb-dma_addr dma_addr; // 启动DMA传输 dev-dma_engine-start_tx(dma_addr, size);2. 存储设备// 磁盘设备的DMA使用 struct request *req blk_fetch_request(q); if (!req) return; // 准备散射/聚集列表 struct scatterlist sg[SG_MAX]; int nents blk_rq_map_sg(q, req, sg); if (!nents) { blk_end_request(req, -EIO, blk_rq_bytes(req)); return; } // 映射SG列表 dma_map_sg(pdev-dev, sg, nents, DMA_FROM_DEVICE); // 启动DMA传输 dma_async_memcpy_issue_pending(chan);3. 音频设备// 音频设备的DMA使用 struct snd_pcm_substream *substream ...; // 分配DMA缓冲区 void *buf dma_alloc_coherent(pdev-dev, substream-runtime-dma_bytes, substream-runtime-dma_addr, GFP_KERNEL); if (!buf) { return -ENOMEM; } // 设置音频缓冲区 substream-runtime-dma_area buf; substream-runtime-dma_addr dma_handle; // 启动DMA传输 dmaengine_submit(tx_desc); dma_async_issue_pending(chan);性能优化建议1. 选择合适的DMA缓冲区类型// 对于持续使用的缓冲区使用一致性DMA void *buf dma_alloc_coherent(dev, size, dma_handle, GFP_KERNEL); // 对于临时缓冲区使用流式DMA void *buf kmalloc(size, GFP_KERNEL); dma_addr_t dma_handle dma_map_single(dev, buf, size, DMA_TO_DEVICE);2. 批量传输// 使用散射/聚集列表进行批量传输 struct scatterlist sg[4]; // 填充SG列表 sg_init_table(sg, 4); sg_set_buf(sg[0], buf1, size1); sg_set_buf(sg[1], buf2, size2); sg_set_buf(sg[2], buf3, size3); sg_set_buf(sg[3], buf4, size4); // 映射SG列表 int nents dma_map_sg(dev, sg, 4, DMA_TO_DEVICE);3. 避免频繁映射/取消映射// 错误频繁映射/取消映射 for (int i 0; i 1000; i) { dma_addr_t dma_addr dma_map_single(dev, buf, size, DMA_TO_DEVICE); // 传输 dma_unmap_single(dev, dma_addr, size, DMA_TO_DEVICE); } // 正确一次性映射 DMA_addr_t dma_addr dma_map_single(dev, buf, size * 1000, DMA_TO_DEVICE); for (int i 0; i 1000; i) { // 传输 } dma_unmap_single(dev, dma_addr, size * 1000, DMA_TO_DEVICE);4. 合理设置DMA缓冲区大小// 选择合适的缓冲区大小 size_t buffer_size PAGE_SIZE * 4; // 16KB void *buf dma_alloc_coherent(dev, buffer_size, dma_handle, GFP_KERNEL);常见陷阱1. 忘记同步// 错误使用非一致性DMA但没有同步 void *buf dma_alloc_noncoherent(dev, size, dma_handle, GFP_KERNEL); // 写入数据到缓冲区 memcpy(buf, data, size); // 错误没有同步就启动DMA dev-start_dma(dma_handle, size); // 正确同步后再启动 memcpy(buf, data, size); dma_sync_single_for_device(dev, dma_handle, size, DMA_TO_DEVICE); dev-start_dma(dma_handle, size);2. 缓冲区对齐// 确保缓冲区对齐 #define DMA_ALIGNMENT 16 void *buf kzalloc(size DMA_ALIGNMENT - 1, GFP_KERNEL); void *aligned_buf (void *)(((unsigned long)buf DMA_ALIGNMENT - 1) ~(DMA_ALIGNMENT - 1));3. 内存屏障// 在DMA操作前后使用内存屏障 dma_sync_single_for_device(dev, dma_handle, size, DMA_TO_DEVICE); // 内存屏障确保所有写操作完成 mb(); // 启动DMA传输 dev-start_dma(dma_handle, size);调试技巧1. 查看DMA统计# 查看DMA通道信息 cat /proc/dma # 查看PCI设备的DMA信息 lspci -v | grep -A 10 DMA2. DMA错误处理// 检查DMA映射失败 int ret dma_map_sg(dev, sg, nents, DMA_TO_DEVICE); if (!ret) { dev_err(dev, DMA mapping failed\n); return -ENOMEM; }总结DMA是Linux内核中实现高性能数据传输的关键机制它通过减少CPU干预提高了系统的整体性能。作为嵌入式开发者理解DMA的工作原理和使用方法对于开发高性能的设备驱动至关重要。

更多文章