4. Metadata 内存管理:allocate/free 机制、引用计数、跨进程传递

Camera Metadata 在 Android 系统中贯穿 HAL、Framework 与 App 三层,其内存管理直接决定了系统的稳定性与性能。本章深入剖析 Metadata 的分配与释放机制、引用计数原理以及跨进程传递的两种核心路径:Binder 与共享内存。

4.1 Metadata 内存分配与释放机制

Android Camera 系统使用 camera_metadata_t 结构体管理 Metadata,其内存布局分为头部(header)与数据区(data)两部分。分配与释放遵循严格的规则,以避免内存泄漏与野指针。

4.1.1 分配接口与内存布局

核心分配函数为 allocate_camera_metadata,它一次性分配连续内存,包含固定大小的头部和可变大小的数据区。

// 分配一个指定 entry 容量和数据容量的 camera_metadata
camera_metadata_t* metadata = allocate_camera_metadata(
    entry_capacity,   // 最大支持的 entry 数量
    data_capacity     // 数据区总字节数
);
if (!metadata) {
    // 分配失败处理
    return -ENOMEM;
}

内存布局示意:

camera_metadata_t 内存布局 Header(固定大小) sizeof(camera_metadata_t) 包含:版本、entry 计数、容量等 Data Area(可变大小) entry 数组 + 原始数据缓冲区 连续存储,按偏移量访问 一次性分配连续内存,避免碎片化,提升缓存命中率

4.1.2 释放接口与安全策略

释放函数 free_camera_metadata 必须与分配函数配对使用。释放后应将指针置空,防止悬挂指针。

// 释放 metadata 内存
free_camera_metadata(metadata);
metadata = NULL;  // 安全置空,避免野指针
函数 功能 注意事项
allocate_camera_metadata 分配新 metadata 对象 返回 NULL 表示内存不足
free_camera_metadata 释放 metadata 内存 调用后指针需置 NULL
clone_camera_metadata 深拷贝 metadata 新对象需独立释放
resize_camera_metadata 调整容量并重新分配 原数据自动迁移
常见陷阱:HAL 层在 process_capture_result 中返回的 metadata 指针,Framework 层会接管所有权。HAL 不应再释放该指针,否则导致 double-free 崩溃。

4.2 引用计数机制

为避免频繁的深拷贝,Android 为 camera_metadata 引入了引用计数机制。当多个组件共享同一份 metadata 时,仅增加引用计数,直到最后一个引用释放时才真正回收内存。

4.2.1 引用计数结构

引用计数存储在 metadata 头部的 refcount 字段中,类型为 int32_t。初始值为 1,每次增加引用调用 clone_camera_metadata 或显式 camera_metadata_refcount_inc。

// 引用计数相关操作
int32_t refcount = camera_metadata_get_refcount(metadata);
// 增加引用计数(浅拷贝语义)
camera_metadata_t* shared = clone_camera_metadata(metadata);
// shared 与 metadata 共享数据区,refcount 增加
引用计数共享模型 主 Metadata refcount = 3 共享者 A 共享者 B 共享者 C 所有共享者指向同一块 内存数据区 最后一个释放时 才真正 free()

4.2.2 引用计数规则

  • 初始值:每次 allocate_camera_metadata 创建新对象时,refcount = 1。
  • 增加:调用 clone_camera_metadata 或 camera_metadata_refcount_inc 时,refcount++。
  • 减少:调用 free_camera_metadata 时,refcount--;当 refcount 降为 0 时,真正释放内存。
  • 线程安全:引用计数操作使用原子变量(__atomic_add_fetch),保证多线程安全。
最佳实践:在 HAL 的 process_capture_result 中,如果需要对 metadata 进行延迟处理,应调用 clone_camera_metadata 增加引用计数,而不是直接保存指针。这样可以避免在 Framework 释放后出现 use-after-free。

4.3 跨进程传递:Binder 与共享内存

Camera Metadata 需要从 HAL 进程传递到 Framework 进程,再传递到 App 进程。Android 提供了两种跨进程传递机制:Binder 和共享内存(Shared Memory)。

4.3.1 Binder 传递

Binder 是 Android 默认的 IPC 机制。对于小尺寸 Metadata(通常 < 1MB),Binder 将数据序列化后直接拷贝到目标进程。

// Binder 传递 Metadata 的典型流程(Framework 侧)
status_t CameraClient::setRequestMetadata(
    const camera_metadata_t* metadata) {
    Parcel data;
    // 将 metadata 序列化到 Parcel
    data.writeCameraMetadata(metadata);
    // 通过 Binder 发送
    remote()->transact(CMD_SET_REQUEST, data, &reply);
    return NO_ERROR;
}
特性 Binder 传递 共享内存传递
数据拷贝次数 2 次(用户态→内核态→用户态) 0 次(直接映射)
适用数据量 < 1MB(推荐) > 1MB(大块数据)
生命周期管理 自动(Parcel 生命周期) 手动(fd 管理 + 引用计数)
实时性 中等(受 Binder 线程池影响) 高(无序列化开销)

4.3.2 共享内存传递

对于大尺寸 Metadata(如 3A 统计信息、HDR 融合参数),Android 使用 SharedMemory(ashmem)机制,通过文件描述符(fd)实现零拷贝传递。

// 创建共享内存并写入 Metadata
#include <android/sharedmem.h>

int fd = ashmem_create_region("camera_metadata", data_size);
if (fd < 0) {
    // 创建失败处理
    return -1;
}

// 映射到本进程地址空间
void* addr = mmap(NULL, data_size, PROT_READ | PROT_WRITE,
                  MAP_SHARED, fd, 0);
memcpy(addr, metadata_data, data_size);
munmap(addr, data_size);

// 通过 Binder 传递 fd
Parcel data;
data.writeFileDescriptor(fd);
remote()->transact(CMD_SHARE_METADATA, data, &reply);
close(fd);  // 发送端关闭,接收端通过 dup 获得新 fd
共享内存跨进程传递流程 HAL 进程 1. 创建 ashmem 2. mmap + memcpy 内核共享内存 物理内存页 Framework 进程 3. 接收 fd 4. mmap 直接读取 Binder 传递 fd 零拷贝:数据始终位于同一物理内存页,仅传递文件描述符,避免大数据量序列化开销

4.3.3 跨进程引用计数管理

当 Metadata 通过共享内存跨进程传递时,引用计数需要跨进程维护。Android 使用 camera_metadata 头部的 refcount 字段,该字段位于共享内存中,所有映射了该内存的进程都能访问。

// 跨进程引用计数操作(需原子操作)
int32_t* refcount_ptr = (int32_t*)((char*)metadata + 
    offsetof(camera_metadata_t, refcount));

// 原子增加引用计数
__atomic_add_fetch(refcount_ptr, 1, __ATOMIC_SEQ_CST);

// 原子减少引用计数,并检查是否为 0
int32_t old = __atomic_fetch_sub(refcount_ptr, 1, __ATOMIC_SEQ_CST);
if (old == 1) {
    // 最后一个引用,释放共享内存
    munmap(metadata, total_size);
    close(shared_fd);
}
关键风险:跨进程引用计数必须使用原子操作,且所有进程必须遵守相同的释放协议。如果某个进程异常退出(crash),共享内存可能泄漏。生产环境中应结合 binder death recipient 机制进行清理。

4.4 内存管理最佳实践总结

场景 推荐策略 避免做法
HAL 返回 result metadata 使用 clone_camera_metadata 传递所有权 直接传递栈上指针或局部变量地址
多线程共享 metadata 引用计数 + 原子操作 手动管理释放时机,容易 race condition
大数据量 metadata(> 1MB) 共享内存 + fd 传递 Binder 直接拷贝导致内存峰值
小数据量 metadata(< 64KB) Binder 序列化传递 共享内存创建/映射开销反而更大
进程异常退出 注册 death recipient 清理共享内存 依赖对方进程主动释放
性能指标参考:在 Android 12+ 设备上,Binder 传递 64KB metadata 耗时约 200-500μs;共享内存传递 4MB metadata 耗时约 50-100μs(不含创建时间)。选择哪种方式取决于数据量大小和实时性要求。