2. CMA应用场景:多媒体编解码、GPU显存分配、Camera ISP、显示控制器等需要大块连续物理内存的驱动场景
连续物理内存(Contiguous Memory)在嵌入式Linux系统中是稀缺资源。多媒体编解码、GPU渲染、Camera ISP处理以及显示控制器等硬件模块,通常通过DMA(直接内存访问)操作数据,要求缓冲区在物理地址上连续。CMA(Contiguous Memory Allocator)正是为解决这一矛盾而设计——它允许系统在不需要大块连续内存时将其作为可移动页面使用,仅在驱动申请时动态迁移并锁定连续区域。
2.1 典型驱动场景与内存需求对比
不同硬件模块对连续内存的需求量、分配时机和生命周期差异显著。下表归纳了四个主要场景的关键参数:
| 场景 | 典型连续内存需求 | 分配时机 | 生命周期 | 对齐要求 |
|---|---|---|---|---|
| 多媒体编解码(VPU/CODEC) | 4~32 MB 每路流 | 解码/编码会话启动时 | 会话持续期间 | 4KB~2MB 对齐 |
| GPU显存分配 | 16~256 MB 每帧缓冲 | 渲染任务提交时 | 帧生命周期(短时) | 64KB~2MB 对齐 |
| Camera ISP | 8~64 MB 每帧 | 视频流开启时 | 流持续期间(循环缓冲) | 32KB~1MB 对齐 |
| 显示控制器(Display Controller) | 4~16 MB 每图层 | 显示模式设置时 | 常驻(直到模式切换) | 1MB 对齐(典型) |
2.2 多媒体编解码场景:VPU的连续缓冲需求
视频编解码器(VPU)通常包含硬件加速的DMA引擎,直接从DDR读取压缩码流并写入解码后的YUV/RAW帧。如果物理地址不连续,硬件无法通过一次DMA传输完成整帧数据搬运,导致性能骤降或功能异常。
/* 多媒体驱动中典型的CMA分配流程(伪代码) */
struct vpu_session {
struct device *dev;
struct cma *cma;
dma_addr_t phys_addr;
void *vaddr;
size_t size;
};
int vpu_alloc_frame_buffer(struct vpu_session *sess, size_t size)
{
/* 从设备关联的CMA区域分配连续内存 */
sess->vaddr = dma_alloc_from_contiguous(sess->dev, size, 0);
if (!sess->vaddr)
return -ENOMEM;
/* 获取物理地址并映射到IOMMU(如果存在) */
sess->phys_addr = dma_map_single(sess->dev, sess->vaddr, size,
DMA_FROM_DEVICE);
/* 配置硬件寄存器 */
writel(sess->phys_addr, sess->regs + VPU_FRAME_BASE);
return 0;
}
alignment属性指定对齐粒度。
2.3 GPU显存分配场景:短生命周期与高频率分配
GPU驱动(如DRM/KMS、Vivante、Mali)在渲染每一帧时可能需要分配多个连续缓冲用于顶点数据、纹理和帧缓冲。与多媒体场景不同,GPU缓冲的生命周期极短(通常仅持续几帧),且分配频率极高。若CMA分配/释放路径存在瓶颈,直接导致帧率下降。
- 挑战1:分配延迟敏感 —— 每帧分配必须在16.6ms(60fps)内完成,否则掉帧。
- 挑战2:碎片化加剧 —— 频繁分配释放导致CMA区域产生外部碎片,大块分配失败率上升。
- 挑战3:与CPU页面回收竞争 —— CMA迁移页面时可能触发大量LRU回收,增加延迟。
2.4 Camera ISP场景:多帧循环缓冲与带宽约束
Camera ISP(图像信号处理器)通常需要多个连续缓冲构成循环队列:一个用于当前帧写入,一个用于上一帧处理,一个用于输出。每个缓冲必须物理连续,因为ISP的DMA引擎不支持SG(Scatter-Gather)模式。
/* Camera ISP循环缓冲配置示例 */
struct isp_buffer_ring {
struct device *dev;
struct cma *cma;
dma_addr_t phys[ISP_RING_DEPTH]; /* 3~4个缓冲 */
void *virt[ISP_RING_DEPTH];
size_t buf_size; /* 单帧大小 */
int head, tail;
};
int isp_alloc_ring(struct isp_buffer_ring *ring, size_t frame_size)
{
int i;
for (i = 0; i < ISP_RING_DEPTH; i++) {
/* 从CMA分配连续内存 */
ring->virt[i] = dma_alloc_from_contiguous(ring->dev,
frame_size, 0);
if (!ring->virt[i])
goto err_free;
ring->phys[i] = virt_to_phys(ring->virt[i]);
}
return 0;
err_free:
while (--i >= 0)
dma_release_from_contiguous(ring->dev, ring->virt[i],
frame_size);
return -ENOMEM;
}
dma-ranges属性将CMA区域限定在高速DDR域。
2.5 显示控制器场景:长期锁定与模式切换
显示控制器(Display Controller)驱动在系统启动或显示模式切换时分配连续缓冲,用于存储帧缓冲(Framebuffer)或图层数据。这些缓冲通常长期锁定,直到显示模式改变或驱动卸载。因此,显示控制器对CMA区域的稳定性要求最高——一旦分配成功,不允许被迁移或回收。
| 显示模式 | 分辨率 | 色深 | 单帧大小(近似) | 图层数 |
|---|---|---|---|---|
| 1080p60 | 1920x1080 | 32bpp | ~8 MB | 1~3 |
| 4K60 | 3840x2160 | 32bpp | ~32 MB | 1~4 |
| 8K30 | 7680x4320 | 32bpp | ~128 MB | 1~2 |
shared-dma-pool且no-map),避免与其他驱动竞争。同时,在驱动初始化时一次性分配所有图层缓冲,后续模式切换仅更新指针,不重新分配内存。
2.6 场景驱动的CMA配置策略
根据上述场景特征,驱动开发者在配置CMA区域时应遵循以下策略:
- 专用区域 vs 共享区域: 显示控制器和Camera ISP建议使用专用CMA区域(
shared-dma-pool+no-map),避免被其他驱动干扰;GPU和多媒体编解码可以共享一个大型CMA区域,利用统计复用降低总内存占用。 - 对齐粒度: 根据硬件DMA引擎的约束设置对齐。例如,某些VPU要求2MB对齐,GPU要求64KB对齐。在设备树中通过
alignment属性指定。 - 预留大小: 根据最大并发场景计算。例如,支持4路1080p解码时,每路8MB,加上metadata,预留40MB较为安全。
- 分配策略: 高频分配场景(GPU)使用缓冲池;低频分配场景(显示控制器)使用一次性分配。