4. 缓存管理基础
在摄像头驱动中,DMA 负责把图像数据从传感器接口搬运到系统内存,而 CPU 往往通过 Cache 访问同一块缓冲区。只要 Cache 与内存之间出现“副本不一致”,驱动就会遇到花屏、旧帧、数据错位甚至随机崩溃。理解 Cache 一致性、写回/写直达策略、内存屏障以及 DMA 与 Cache 的交互,是设计稳定摄像头数据通路的前提。
一、核心原理:为什么会出现 Cache 一致性问题
现代 SoC 中,CPU 访问内存通常经过多级 Cache,而 DMA 控制器一般直接访问物理内存(或经过 IOMMU/SMMU 映射后的内存),并不感知 CPU Cache 的内容。于是同一块图像缓冲区可能同时存在三个“视图”:
- CPU Cache 中的副本:CPU 读写时看到的最新数据。
- 物理内存中的主副本:DMA 实际读写的目标。
- 设备侧 FIFO/寄存器:传感器或 CSI 控制器内部的临时数据。
当 CPU 写入了新配置或新帧数据但尚未写回内存时,DMA 读到的仍是旧内存;当 DMA 把新图像写入内存后,CPU Cache 中可能仍保留旧行,CPU 读到的就是过期数据。这就是 DMA 与 Cache 交互问题的根源。
二、写回与写直达策略
Cache 的写策略决定 CPU 写数据时如何同步到内存:
- 写直达(Write-Through):CPU 每次写 Cache 的同时也写入内存。实现简单、一致性强,但写内存带宽压力大,摄像头高帧率场景下容易成为瓶颈。
- 写回(Write-Back):CPU 只写 Cache,并标记为脏行,直到该行被替换或显式清理时才写回内存。带宽利用率高,但 DMA 读取前必须确保脏行已写回。
多数应用处理器对普通内存采用写回策略,因此驱动在启动 DMA 前通常需要执行 clean(写回脏行)或 flush(写回并无效化)。
| 策略 | 写内存时机 | 一致性风险 | 典型适用 |
|---|---|---|---|
| 写直达 | 每次写 Cache 同步写内存 | 较低,但带宽开销大 | 小缓冲、控制寄存器 |
| 写回 | 脏行替换或显式清理时 | DMA 前需 clean/flush | 大帧缓冲、图像数据 |
三、内存屏障:顺序比速度更重要
即使 Cache 操作正确,CPU 和编译器仍可能重排访存指令。例如驱动先写 DMA 描述符,再写寄存器启动 DMA;若启动寄存器的写被提前,DMA 可能读到未完成的描述符。内存屏障用于约束访存顺序:
- 编译屏障:
barrier(),阻止编译器重排。 - 写屏障:
wmb(),保证之前的写先于之后的写完成。 - 读屏障:
rmb(),保证之前的读先于之后的读完成。 - 全屏障:
mb(),读写均有序。 - DMA 专用屏障:
dma_wmb()、dma_rmb(),用于设备与 CPU 之间的共享内存。
/* 典型顺序:先填描述符,再启动 DMA */
desc->addr = dma_addr;
desc->len = frame_size;
dma_wmb(); /* 确保描述符先于寄存器写可见 */
writel(desc_phys, DMA_DESC_REG);
writel(START_BIT, DMA_CTRL_REG);
四、DMA 与 Cache 的交互:四类典型问题
摄像头驱动中,DMA 与 Cache 的交互可归纳为四种方向,处理方式各不相同:
- CPU 写、DMA 读:如配置描述符、填充元数据。DMA 启动前需
dma_sync_single_for_device()或 clean 操作,把脏行写回内存。 - DMA 写、CPU 读:如采集完成的图像帧。CPU 读取前需
dma_sync_single_for_cpu()或 invalidate,丢弃旧 Cache 行。 - 双向共享:如环形缓冲区的读写指针。需在每次交接点执行同步,并配合屏障。
- 非一致性架构:部分 SoC 的 DMA 不参与 Cache 一致性,必须由驱动显式维护;一致性架构(如带 CCI 的 ARM)可减少部分操作,但仍需正确使用 DMA API。
五、案例场景:一帧图像从采集到显示的缓存路径
以 MIPI CSI 摄像头采集一帧 1080p 图像为例,典型流程如下:
1. 驱动分配 DMA 一致性缓冲区或普通缓冲区
2. CPU 填充 DMA 描述符(地址、长度、控制位)
3. dma_wmb() 保证描述符先于启动寄存器可见
4. 启动 DMA,CSI 数据写入物理内存
5. DMA 完成中断触发
6. dma_sync_single_for_cpu() 使 CPU 侧 Cache 无效化
7. CPU 读取图像数据,进行 ISP 处理或送显
8. 若 CPU 修改了缓冲区,再次 dma_sync_single_for_device()
若第 6 步缺失,CPU 可能读到 Cache 中的旧帧;若第 3 步缺失,DMA 可能读到未完成的描述符,导致地址错乱。两者都是摄像头驱动调试中常见的问题。
dma_alloc_coherent() 分配的一致性内存可避免大部分 Cache 维护操作,但会牺牲部分性能;对高带宽图像帧,通常采用流式 DMA 映射加显式同步。
六、误区与复盘
- 误区一:认为 DMA 会自动与 Cache 保持一致。只有硬件一致性架构且正确配置时才成立,普通 DMA 必须由驱动维护。
- 误区二:只做 invalidate 不做 clean。CPU 写过的脏数据未写回,DMA 读到旧值,配置类数据尤其明显。
- 误区三:忽略屏障。描述符与启动寄存器之间没有顺序保证,偶发失败难以复现。
- 误区四:同步范围过大。对整个大缓冲反复 clean/invalidate 会显著增加 CPU 开销,应按帧或按描述符粒度同步。
- 误区五:在中断上下文中执行耗时同步。大范围 Cache 操作可能引起延迟抖动,影响高帧率采集。
总结来看,摄像头驱动的缓存管理可以归纳为一条主线:明确数据流向,判断 CPU 与 DMA 谁写谁读,再选择 clean、invalidate 或两者兼有,并用内存屏障锁定顺序。写回与写直达策略决定了同步成本,而 DMA API 与屏障则是把“可能出错”变成“确定正确”的关键工具。