3. Buffer管理基础:连续物理内存、虚拟内存映射、Cache一致性、DMA缓冲区对齐要求
在Raw格式输出与DMA Buffer共享优化中,Buffer管理是决定系统吞吐量和稳定性的核心环节。本章将深入剖析连续物理内存分配、虚拟内存映射机制、Cache一致性维护以及DMA缓冲区对齐要求,为后续的共享优化打下坚实基础。
3.1 连续物理内存:DMA的硬性需求
大多数DMA控制器(尤其是传统总线设备)要求传输缓冲区在物理地址上连续。这是因为DMA引擎通过物理地址递增访问内存,不支持分散-收集(Scatter-Gather)时,非连续物理页会导致传输错误。
关键概念: 物理连续 ≠ 虚拟连续。用户空间malloc返回的虚拟地址连续,但底层物理页可能分散在不同区域,无法直接用于DMA。
3.1.1 连续物理内存分配方法
| 分配方式 | API / 机制 | 特点 | 适用场景 |
|---|---|---|---|
| kmalloc | kmalloc(size, GFP_KERNEL) |
分配物理连续内存,最大约4MB(取决于系统配置) | 小尺寸DMA缓冲区(< 1MB) |
| dma_alloc_coherent | dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL) |
分配物理连续且Cache一致的内存,返回虚拟地址和DMA总线地址 | 标准DMA传输,无需手动Cache维护 |
| __get_free_pages | __get_free_pages(GFP_KERNEL, order) |
分配2^order个连续物理页,最大约4MB | 内核内部DMA,需要手动管理 |
| CMA (Contiguous Memory Allocator) | dma_alloc_from_contiguous(dev, count, align) |
预留大块连续物理内存,支持动态分配和释放 | 大尺寸DMA缓冲区(> 4MB),如摄像头、显示 |
| IOMMU / SMMU | 通过IOMMU映射分散物理页为连续I/O虚拟地址 | 无需物理连续,但需要硬件支持 | 现代SoC,高性能场景 |
最佳实践: 优先使用
dma_alloc_coherent 或 CMA 分配DMA缓冲区。kmalloc仅适用于小尺寸且生命周期短的缓冲区。
3.1.2 连续物理内存分配示例
#include <linux/dma-mapping.h>
#define BUF_SIZE (1024 * 1024) // 1MB
struct device *dev;
dma_addr_t dma_handle;
void *cpu_addr;
// 分配物理连续且Cache一致的DMA缓冲区
cpu_addr = dma_alloc_coherent(dev, BUF_SIZE, &dma_handle, GFP_KERNEL);
if (!cpu_addr) {
dev_err(dev, "Failed to allocate DMA coherent memory\n");
return -ENOMEM;
}
// 使用缓冲区...
// 硬件使用 dma_handle 作为总线地址
// 软件使用 cpu_addr 作为内核虚拟地址
// 释放缓冲区
dma_free_coherent(dev, BUF_SIZE, cpu_addr, dma_handle);
3.2 虚拟内存映射:连接CPU与DMA的桥梁
CPU通过虚拟地址访问内存,而DMA设备通过物理地址或总线地址访问。虚拟内存映射机制负责将用户空间、内核空间与DMA缓冲区关联起来。
常见陷阱: 用户空间指针不能直接传递给DMA硬件。必须通过
virt_to_phys() 或 dma_map_single() 获取物理/总线地址。
3.2.1 映射关系图
3.2.2 映射操作API
- dma_map_single():将内核虚拟地址映射为DMA总线地址,同时处理Cache一致性。
- dma_unmap_single():解除映射,释放DMA总线地址。
- dma_map_sg():将分散的物理页映射为DMA scatter-gather列表。
- get_user_pages():将用户空间虚拟地址锁定为物理页,用于DMA传输。
3.3 Cache一致性:数据同步的核心挑战
CPU和DMA设备各自拥有独立的Cache或缓冲。当DMA直接读写物理内存时,CPU Cache中的数据可能与物理内存不一致,导致数据损坏。
核心问题: CPU写内存时,数据可能只停留在Cache中,尚未写回物理内存。DMA读取物理内存时,读到的是旧数据。反之,DMA写入物理内存后,CPU Cache中的旧副本未被失效,CPU读取到的是脏数据。
3.3.1 Cache一致性维护策略
| 策略 | 实现方式 | 优点 | 缺点 |
|---|---|---|---|
| 一致性内存 (Coherent) | 硬件自动维护(如ARM的ACE/CHI总线)或使用 dma_alloc_coherent |
无需软件干预,编程简单 | 性能开销较大,每次访问都需总线嗅探 |
| 非一致性内存 + 手动维护 | 使用 dma_map_single 时自动执行Cache刷新/失效 |
性能高,仅在传输边界处理 | 需要正确调用API,容易遗漏 |
| 非Cacheable映射 | 将DMA缓冲区页表属性设置为非Cacheable | 完全避免一致性问题 | CPU访问性能极差 |
3.3.2 Cache操作流程
3.3.3 手动Cache维护示例
// 使用 dma_map_single 自动处理Cache一致性
dma_addr_t dma_handle;
void *buffer = kmalloc(BUF_SIZE, GFP_KERNEL);
// 映射前:自动执行Cache刷新(写回物理内存)
dma_handle = dma_map_single(dev, buffer, BUF_SIZE, DMA_TO_DEVICE);
// 启动DMA传输...
// 等待DMA完成...
// 映射后:自动执行Cache失效(从物理内存重新加载)
dma_unmap_single(dev, dma_handle, BUF_SIZE, DMA_TO_DEVICE);
// 现在CPU可以安全读取buffer中的数据
方向参数:
DMA_TO_DEVICE(CPU写,DMA读)只做Cache刷新;DMA_FROM_DEVICE(DMA写,CPU读)只做Cache失效;DMA_BIDIRECTIONAL 两者都做。
3.4 DMA缓冲区对齐要求
DMA控制器通常对缓冲区的起始地址、大小和偏移有严格的对齐要求。违反对齐可能导致传输错误、性能下降甚至硬件崩溃。
3.4.1 常见对齐要求
| 对齐类型 | 典型值 | 原因 |
|---|---|---|
| 地址对齐 | 4字节、8字节、16字节、32字节、64字节、页对齐(4KB) | DMA控制器内部FIFO宽度、总线事务大小 |
| 大小对齐 | 传输单元大小的整数倍(如512字节、1024字节) | 避免部分字传输,提高效率 |
| 偏移对齐 | 与地址对齐相同 | 确保每次传输起始地址满足对齐 |
| Cache Line对齐 | 32字节、64字节(取决于CPU架构) | 避免Cache一致性问题中的伪共享 |
3.4.2 对齐检查与分配
#include <linux/dma-mapping.h>
// 获取设备DMA对齐要求
unsigned int align_mask = dma_get_required_mask(dev);
// 或直接使用 dma_get_cache_alignment()
// 分配对齐内存
void *buffer;
dma_addr_t dma_handle;
// 方法1:使用 dma_alloc_coherent(自动对齐)
buffer = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);
// 方法2:使用 kmalloc 配合对齐宏
buffer = kmalloc(size + align_mask, GFP_KERNEL);
if (buffer) {
// 手动对齐
void *aligned = PTR_ALIGN(buffer, align_mask + 1);
// 注意:释放时仍需使用原始指针
}
// 方法3:使用 CMA 分配大块对齐内存
struct page *page = dma_alloc_from_contiguous(dev, count, align_order);
if (page) {
buffer = page_to_virt(page);
dma_handle = page_to_phys(page);
}
对齐检查: 使用
IS_ALIGNED(addr, align) 宏检查地址是否对齐。例如:if (!IS_ALIGNED((unsigned long)buffer, 64)) { /* 未对齐 */ }
3.4.3 对齐与性能关系
3.5 综合实践:Buffer管理检查清单
Buffer管理黄金法则: 物理连续 + 正确映射 + Cache同步 + 严格对齐 = 稳定高效的DMA传输
- 分配阶段: 使用
dma_alloc_coherent或 CMA 分配物理连续内存,确保地址和大小对齐。 - 映射阶段: 使用
dma_map_single或dma_map_sg获取DMA总线地址,指定正确的传输方向。 - 传输阶段: 确保DMA描述符中的地址和大小满足设备对齐要求。
- 完成阶段: 使用
dma_unmap_single解除映射,自动处理Cache失效。 - 释放阶段: 使用
dma_free_coherent释放缓冲区,避免内存泄漏。