1. 为什么设备驱动需要连续大块内存?
在嵌入式系统和服务器环境中,像GPU、视频编解码器、摄像头这类设备对内存有着特殊需求。它们通常需要操作数十MB甚至上百MB的连续物理内存缓冲区,比如处理4K视频帧时,单个未压缩帧就可能占用超过24MB内存(3840x2160像素 x 3字节/像素)。
传统的内存分配方式如kmalloc()存在明显局限:
- 最大只能分配几MB内存(取决于内核配置)
- 长期运行后内存碎片化会导致分配失败
- 无法保证物理地址连续性
我曾在一个智能摄像头项目上踩过坑:当系统运行一段时间后,视频流突然出现卡顿。通过内核日志发现是DMA缓冲区分配失败,根本原因就是内存碎片化导致无法获取连续内存。这时候CMA就派上用场了——它在系统启动时就预留好连续内存区域,专供这类设备使用。
2. CMA与DMA的协同工作机制
2.1 CMA的核心设计思想
CMA(Contiguous Memory Allocator)的聪明之处在于它的"双重身份"设计:
- 设备未使用时:CMA区域由普通内存管理系统管理,可以分配可移动类型页面(比如用户进程的内存页)
- 设备需要时:迁移已分配的页面,重新整合出连续物理内存
这种设计通过/proc/meminfo就能观察到:
$ grep Cma /proc/meminfo CmaTotal: 262144 kB # 预留给CMA的总内存 CmaFree: 131072 kB # 当前可用的CMA内存2.2 DMA子系统如何利用CMA
当设备驱动调用DMA分配API时(如dma_alloc_coherent()),内核会按以下顺序尝试获取内存:
- 连贯池(Coherent Pool):小容量原子分配
- CMA区域:主要的大块连续内存来源
- 伙伴系统:常规内存分配
- SWIOTLB:最后的保底方案
具体流程可以通过内核代码片段说明:
// 简化的DMA分配路径(drivers/base/dma-coherent.c) struct page *dma_alloc_from_contiguous(struct device *dev, size_t count, unsigned int align, gfp_t gfp_mask) { // 首先尝试从CMA分配 if (dev_get_cma_area(dev)) page = cma_alloc(dev_get_cma_area(dev), count, align); // 失败后尝试其他方式 if (!page) page = fallback_alloc(dev, count, align); return page; }3. CMA的实战配置与优化
3.1 配置CMA区域的三种方式
3.1.1 通过设备树(推荐方式)
reserved-memory { #address-cells = <2>; #size-cells = <2>; ranges; vpu_reserved: vpu@f8000000 { compatible = "shared-dma-pool"; reg = <0x0 0xf8000000 0x0 0x8000000>; // 128MB reusable; }; };3.1.2 内核启动参数
// 在bootargs中添加: cma=128M@0x10000000 // 在256MB地址处分配128MB3.1.3 内核配置选项
CONFIG_CMA_SIZE_MBYTES=256 CONFIG_CMA_AREAS=7 // 允许创建多个独立CMA区域3.2 性能优化技巧
案例:在某款智能NVR设备上,我们发现4K视频录制时有帧丢失现象。通过ftrace分析发现是CMA页面迁移导致延迟波动:
- 减少迁移开销:
// 在驱动中设置GFP标志 dma_alloc_coherent(dev, size, dma_handle, GFP_KERNEL | __GFP_NORETRY);- 预留专用CMA区域:
isp_reserved: isp@e0000000 { compatible = "shared-dma-pool"; no-map; // 禁止系统其他部分使用 reg = <0x0 0xe0000000 0x0 0x2000000>; };4. 常见问题与解决方案
4.1 CMA分配失败排查
症状:dmesg中出现cma: cma_alloc: alloc failed警告
排查步骤:
- 检查CMA剩余容量:
$ cat /proc/meminfo | grep Cma - 分析内存碎片:
$ cat /proc/buddyinfo - 检查页面迁移统计:
$ grep -E 'migrate|compact' /proc/vmstat
4.2 与ION分配器的配合
在Android系统中,CMA常与ION分配器配合使用。典型配置如下:
// 内核配置 CONFIG_ION=y CONFIG_ION_SYSTEM_HEAP=y CONFIG_ION_CMA_HEAP=y // 设备树 ion_reserved: ion@f0000000 { compatible = "ion-pool"; reg = <0x0 0xf0000000 0x0 0x4000000>; };5. 进阶话题:CMA替代方案
当CMA无法满足需求时,可以考虑:
连贯池(Coherent Pool):
// 启动参数增加 coherent_pool=64M动态大页(HugeTLB):
// 驱动中申请大页 page = alloc_pages(GFP_TRANSHUGE, HUGETLB_PAGE_ORDER);定制化分配器:
// 实现自己的dma_ops static const struct dma_map_ops my_dma_ops = { .alloc = my_custom_alloc, .free = my_custom_free, };
在实际项目中,我们曾为高吞吐量视频分析设备实现过混合方案:80%内存通过CMA分配,20%通过预分配的HugeTLB备用,这样即使在高碎片场景下也能保证QoS。