Linux内存管理 —— DMA和一致性缓存

本文探讨了Linux内存管理中的DMA和一致性缓存问题。出现内存不一致的原因在于CPU的write back策略和DMA操作不访问CPU cache。为解决一致性问题,Linux提供了如dma_alloc_coherent()和dma_free_coherent()等API,以及流式DMA映射方法。通过这些接口,内核可以确保DMA操作时内存的一致性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 出现内存不一致的原因

CPU写内存的时候有两种方式:
1. write through: CPU直接写内存,不经过cache。
2. write back: CPU只写到cache中。cache的硬件使用LRU算法将cache里面的内容替换到内存。通常是这种方式。

DMA可以完成从内存到外设直接进行数据搬移。但DMA不能访问CPU的cache,CPU在读内存的时候,如果cache命中则只是在cache去读,而不是从内存读,写内存的时候,也可能实际上没有写到内存,而只是直接写到了cache。
这里写图片描述

这样一来,如果DMA从将数据从外设写到内存,CPU中cache中的数据(如果有的话)就是旧数据了,这时CPU在读内存的时候命中cache了,就是读到了旧数据;CPU写数据到内存时,如果只是先写到了cache,则内存里的数据就是旧数据了。这两种情况(两个方向)都存在cache一致性问题。例如,网卡发包的时候,CPU将数据写到cache,而网卡的DMA从内存里去读数据,就发送了错误的数据。
这里写图片描述

2. 如何解决一致性问题

主要靠两类APIs:

### LinuxDMA 的实现与使用 #### 1. DMA 设置掩码 在设备驱动程序初始化过程中,通常会调用 `dma_set_mask_and_coherent` 函数来设置设备支持的 DMA 地址范围。如果设备能够处理 64 位地址,则优先配置为 64 位;否则尝试 32 位地址空间[^1]。此函数的作用是告知内核该设备可以访问的最大物理地址范围。 当无法满足任何一种情况时,系统会发出警告并跳过对该设备的支持逻辑[^1]: ```c if (!dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64))) { using_dac = 1; consistent_using_dac = 1; } else if (!dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32))) { using_dac = 0; consistent_using_dac = 0; } else { dev_warn(dev, "mydev: No suitable DMA available\n"); goto ignore_this_device; } ``` --- #### 2. 连续内存分配器 (CMA) 为了更好地支持硬件设备对大块连续内存的需求,Linux 提供了一种名为 **Contiguous Memory Allocator (CMA)** 的机制[^2]。它允许动态预留一块区域专门用于分配给需要连续内存的设备。这种方式特别适合于图形处理器或其他高性能外设。 相比传统的伙伴算法(Buddy System),CMA 能够更高效地解决碎片化问题,并且不会阻塞其他线程的操作[^2]。然而需要注意的是,在某些情况下可能仍然依赖 SWIOTLB 或者低内存映射技术作为补充方案。 --- #### 3. SWIOTLB 缓冲区管理 对于那些不支持高端内存寻址或者存在特殊需求的情况,Linux 使用软件 I/O TLB (**Software IO Translation Lookaside Buffer**, 简称 SWIOTLB)[^2] 来提供间接解决方案。通过预先创建一个固定大小的缓冲池,所有超出设备能力的数据传输都会被重定向到这个区域内完成。 具体来说,SWIOTLB 初始化阶段会在启动早期将部分 RAM 映射成可供使用的虚拟地址区间。之后每次涉及跨边界操作时便自动触发相应的转换过程。 以下是典型场景下的工作流程概述: - 如果目标位置位于有效范围内,则直接执行; - 否则先复制至 SWIOTLB 缓冲区内再继续后续步骤。 这种设计虽然增加了额外开销,但却极大简化了兼容性移植性方面的挑战。 --- #### 4. 构建 zImage 文件中的 DMA 支持 构建 Linux 内核镜像文件的过程中,ARM 平台上的压缩版本 vmlinux 经过特定工具链编译后最终形成可引导形式——即所谓的 zImage[^3]。在此期间包含了针对不同架构特性定制化的选项设定,其中包括但不限于 DMA 功能模块加载路径优化等内容。 实际开发工作中可以根据项目具体情况调整 Makefile 配置参数以适应各种复杂环境要求。 --- ### 示例代码片段展示如何申请释放 DMA 缓存区 下面给出一段简单的例子说明怎样正确获取以及清理关联资源: ```c #include <linux/dma-mapping.h> struct device *dev; // 分配一致性DMA 缓冲区 void *buffer = dma_alloc_coherent(dev, size_t size, dma_addr_t *dma_handle, gfp_t flag); if (!buffer) { pr_err("Failed to allocate DMA buffer.\n"); } // ... 使用 buffer ... // 当不再需要时记得及时归还 dma_free_coherent(dev, size_t size, void *cpu_addr, dma_addr_t dma_handle); ``` 以上方法适用于大多数常规用途场合下快速建立连接通道而无需关心底层细节差异之处太多。 ---
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值