2、ION/DMA-BUF基础:ION内存分配器原理,DMA-BUF文件描述符机制,Buffer共享与同步
好,咱们进入第二章。这一章讲的是Camera内存管理的基石——ION和DMA-BUF。说实话,我当年刚接触Android Camera HAL时,被这两个东西绕得晕头转向。后来踩了不少坑,才慢慢摸清楚它们的脾气。
你想想看,Camera HAL要处理的事情很特殊:ISP要写数据,CPU要读数据,GPU可能要贴纹理,Display要直接显示。这么多硬件模块都要访问同一块内存,怎么搞?这就是ION和DMA-BUF要解决的核心问题。
2.1 ION内存分配器:到底是个啥?
ION是Android搞出来的通用内存分配器。说白了,它就是个「内存管家」。不同的硬件对内存有不同要求:有的要连续物理内存,有的只要虚拟地址连续就行,有的甚至不需要CPU访问。
ION把这些需求抽象成不同的Heap(堆)。每个Heap管理一种特定类型的内存池。我习惯把ION理解成一个「内存超市」,不同的Heap就是不同的货架。
| Heap类型 | 用途 | 特点 |
|---|---|---|
| ION_HEAP_TYPE_SYSTEM | 通用CPU内存 | 非连续,速度一般 |
| ION_HEAP_TYPE_SYSTEM_CONTIG | 需要物理连续的场景 | 通过CMA分配,较慢 |
| ION_HEAP_TYPE_CARVEOUT | 预留内存区域 | 固定大小,专用 |
| ION_HEAP_TYPE_DMA | DMA设备专用 | 通过DMA API分配 |
我在项目中遇到过一个问题:某个平台Camera预览花屏,查了两天才发现是用了SYSTEM Heap而不是SYSTEM_CONTIG。ISP需要物理连续内存,你给它非连续的,不出问题才怪。嗯,这里要注意:Camera的ISP通常要求物理连续内存,除非硬件支持IOMMU/SMMU。
2.2 ION的工作流程:分配、共享、释放
ION的使用流程其实很清晰。我给大家画个简化的流程:
// 1. 打开ION设备
int ion_fd = open("/dev/ion", O_RDONLY);
// 2. 分配内存
struct ion_allocation_data alloc;
alloc.len = buffer_size;
alloc.heap_id_mask = 1 << ION_HEAP_TYPE_SYSTEM_CONTIG;
alloc.flags = ION_FLAG_CACHED;
ioctl(ion_fd, ION_IOC_ALLOC, &alloc);
// 3. 获取文件描述符
struct ion_fd_data fd_data;
fd_data.handle = alloc.handle;
ioctl(ion_fd, ION_IOC_SHARE, &fd_data);
// 现在fd_data.fd就是DMA-BUF的fd
// 4. 使用完后释放
close(fd_data.fd);
close(ion_fd);
这里有个关键点:ION_IOC_SHARE会创建一个DMA-BUF文件描述符。这个fd才是真正在进程间传递的东西。我刚开始做的时候,以为直接传handle就行,结果跨进程时各种崩溃。后来才明白:handle只在ION驱动内部有效,跨进程必须用fd。
2.3 DMA-BUF文件描述符机制:核心中的核心
DMA-BUF,全称是DMA Buffer Sharing Framework。它本质上是一个基于文件描述符的共享内存机制。
为什么用文件描述符?因为fd是Linux进程间最成熟的通信方式之一。你可以通过socket传递fd,可以通过Binder传递fd,甚至可以通过Unix域套接字传递。这意味着:
- 跨进程共享:Camera HAL进程分配的buffer,可以传给App进程、SurfaceFlinger进程
- 跨硬件共享:同一个buffer,ISP可以写,GPU可以读,Display可以显示
- 生命周期管理:fd的引用计数机制自动管理buffer生命周期
我曾经调试过一个诡异的问题:某个场景下Camera预览偶尔黑屏。查了半天,发现是某个模块提前close了fd,导致buffer被释放,但ISP还在写数据。嗯,这就是典型的生命周期管理不当。
2.4 Buffer共享与同步:别让硬件打架
多个硬件共享同一块buffer,最大的问题是什么?同步!你想想看,ISP正在往buffer里写数据,GPU突然开始读,读到的就是半帧数据,画面就撕裂了。
DMA-BUF的同步机制是通过dma_buf_begin_cpu_access和dma_buf_end_cpu_access来实现的。但说实话,在Camera HAL里,我们更多用的是硬件层面的同步。
核心原则:谁写谁负责同步,谁读谁等待同步。
在Camera HAL中,常见的同步方式有:
- Fence机制:每个buffer关联一个fence,写操作完成后signal fence,读操作等待fence
- 硬件同步点:某些平台支持硬件级别的同步点,比如Qualcomm的sync point
- CPU轮询:简单粗暴,但效率低,不推荐
我建议在实现Camera HAL时,优先使用Fence机制。Android的BufferQueue本身就支持fence传递,用起来很方便。
2.5 实战中的内存分配策略
讲了这么多理论,咱们来点实际的。在Camera HAL中,我一般这样管理内存:
| 场景 | 推荐Heap | 缓存策略 | 同步方式 |
|---|---|---|---|
| ISP输出buffer | SYSTEM_CONTIG | 非缓存(uncached) | 硬件fence |
| CPU后处理buffer | SYSTEM | 缓存(cached) | CPU cache操作 |
| 显示buffer | DMA或CARVEOUT | 非缓存 | 硬件fence |
| 编码器输入buffer | DMA | 非缓存 | 硬件fence |
这里有个经验:能不缓存就别缓存。缓存虽然能提高CPU访问速度,但引入了cache一致性维护的麻烦。对于Camera这种高吞吐场景,cache miss的代价往往比直接访问非缓存内存还大。
最后说一句,ION/DMA-BUF这块的知识,光看书是不够的。我建议你找个实际平台,写个简单的测试程序,分配一块buffer,在CPU和硬件之间来回传递,感受一下同步和cache操作带来的影响。只有亲手踩过坑,才能真正理解这些机制。