4. Camera内存分配策略:连续内存分配、分散内存分配、ION内存管理器

做Camera驱动开发,内存管理这块是绕不开的坎。我刚开始接触时也踩过不少坑,尤其是不同平台、不同分辨率下,选错分配策略,轻则掉帧,重则直接黑屏。今天咱们就把这块掰开揉碎了聊。

4.1 连续内存分配:简单粗暴,但有限制

连续内存分配,说白了就是给Camera sensor要一块物理地址连续的大内存。为什么需要连续?因为很多老旧的ISP硬件或者DMA控制器,它只认连续的物理地址,你给它分散的,它直接罢工。

优点很明显

  • 硬件兼容性好,几乎所有DMA引擎都支持
  • 地址转换开销小,CPU访问也快
  • 实现简单,内核里dma_alloc_coherent一把梭

缺点也致命

  • 系统运行久了,内存碎片化严重,大块连续内存很难申请到
  • 浪费严重,比如你只需要3MB,但系统可能给你对齐到4MB
  • 在嵌入式设备上,连续内存总量有限,多路Camera同时跑就捉襟见肘

核心代码示例:

// 分配连续内存
dma_addr_t dma_phys;
void *cpu_addr = dma_alloc_coherent(dev, size, &dma_phys, GFP_KERNEL);
if (!cpu_addr) {
    printk(KERN_ERR "Failed to allocate coherent DMA memory\n");
    return -ENOMEM;
}

// 使用完后释放
dma_free_coherent(dev, size, cpu_addr, dma_phys);

我的经验: 在项目初期,如果Camera分辨率不超过1080p,用连续分配完全够用。但一旦上到4K甚至8K,连续分配基本就是噩梦。我曾经在一个项目里,系统跑了12小时后,连一块2MB的连续内存都申请不到,最后只能重启。嗯,从那以后我学乖了。

4.2 分散内存分配:灵活但管理复杂

分散内存分配,也叫SG(Scatter-Gather)模式。它允许DMA访问不连续的物理内存块,每个块通过一个SG表来描述。说白了,就是把一堆零散的小内存拼起来用。

为什么需要它?

  • 系统内存碎片化严重时,分散分配几乎总能成功
  • 内存利用率高,按需分配,不浪费
  • 适合大分辨率、多路Camera的场景

代价是什么?

  • 硬件必须支持SG模式,老旧的DMA控制器不行
  • SG表的构建和维护有额外开销
  • 驱动代码复杂度上升,你得自己管理SG列表

核心代码示例:

struct scatterlist *sg;
int nents;

// 分配分散内存
sg = kmalloc(sizeof(struct scatterlist) * num_pages, GFP_KERNEL);
sg_init_table(sg, num_pages);
for (i = 0; i < num_pages; i++) {
    struct page *page = alloc_page(GFP_KERNEL | __GFP_ZERO);
    sg_set_page(&sg[i], page, PAGE_SIZE, 0);
}

// 映射给DMA使用
nents = dma_map_sg(dev, sg, num_pages, DMA_FROM_DEVICE);

// 使用完后解除映射并释放
dma_unmap_sg(dev, sg, nents, DMA_FROM_DEVICE);
for (i = 0; i < num_pages; i++) {
    __free_page(sg_page(&sg[i]));
}
kfree(sg);

注意: 分散分配虽然灵活,但每次DMA传输时,硬件需要遍历SG表。如果SG表项太多(比如几百个),传输效率会下降。我建议单个SG表项不要超过256个,否则性能损耗明显。

4.3 ION内存管理器:Android生态的标配

说到Camera内存管理,ION是绕不开的。它是Android系统里专门为多媒体场景设计的内存分配器。你想想看,Camera、GPU、Display、Video编解码,这些模块之间需要共享内存,如果各管各的,那不乱套了?

ION的核心概念:

  • Heap(堆):不同类型的物理内存池,比如连续堆、系统堆、Carveout堆
  • Buffer(缓冲区):分配出来的内存块,可以跨进程共享
  • Handle(句柄):用户空间操作Buffer的凭证
  • FD(文件描述符):通过fd实现跨进程传递

常见的ION Heap类型:

Heap类型 特点 适用场景
ION_HEAP_TYPE_SYSTEM 从系统内存分配,非连续 小分辨率、低帧率
ION_HEAP_TYPE_SYSTEM_CONTIG 从系统内存分配,但保证连续 需要连续内存的ISP
ION_HEAP_TYPE_CARVEOUT 从预留的固定区域分配,连续 高分辨率、高帧率
ION_HEAP_TYPE_CHUNK 从大块连续内存中切分 多路Camera同时工作

ION分配示例(用户空间):

#include <linux/ion.h>
#include <linux/ion_4.12.h>

int ion_fd = open("/dev/ion", O_RDWR);
struct ion_allocation_data alloc_data;

alloc_data.len = buffer_size;
alloc_data.heap_id_mask = 1 << ION_HEAP_TYPE_SYSTEM_CONTIG;
alloc_data.flags = 0;

// 分配ION buffer
ioctl(ion_fd, ION_IOC_ALLOC, &alloc_data);

// 获取fd用于跨进程共享
struct ion_fd_data fd_data;
fd_data.handle = alloc_data.handle;
ioctl(ion_fd, ION_IOC_SHARE, &fd_data);

// 现在可以通过fd_data.fd来操作这块内存了
// 使用完后关闭
close(fd_data.fd);
close(ion_fd);

我的建议: 在Android平台上做Camera驱动,优先使用ION。它帮你解决了跨进程共享、内存复用、缓存同步等一堆麻烦事。我曾经在一个项目里自己手写内存池,结果发现ION早就把这些问题都处理好了。别重复造轮子。

4.4 如何选择?实战中的决策依据

说了这么多,到底该用哪种?我一般按这个思路来决策:

  1. 看硬件能力:DMA控制器是否支持SG模式?不支持的话,只能连续分配。
  2. 看分辨率:1080p以下,连续分配够用;4K以上,必须用分散或ION。
  3. 看平台:Android平台无脑选ION;纯Linux平台,看内核版本,4.19以上推荐DMA-BUF。
  4. 看内存压力:系统内存紧张时,分散分配比连续分配更可靠。

避坑指南: 我曾经在一个项目里,为了省事全部用连续分配。结果产品在用户手里跑了几天后,Camera突然打不开。查了半天,是内存碎片导致连续内存申请失败。从那以后,我只要分辨率超过1080p,一律用ION的SYSTEM_CONTIG或者直接上分散分配。

嗯,内存分配策略这块,说白了就是权衡。没有银弹,只有最适合你当前场景的方案。下一节咱们聊聊DMA传输的优化技巧,到时候会用到今天讲的内存分配知识。