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;
}
内存布局示意:
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 |
调整容量并重新分配 | 原数据自动迁移 |
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 增加
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),保证多线程安全。
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
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);
}
4.4 内存管理最佳实践总结
| 场景 | 推荐策略 | 避免做法 |
|---|---|---|
| HAL 返回 result metadata | 使用 clone_camera_metadata 传递所有权 |
直接传递栈上指针或局部变量地址 |
| 多线程共享 metadata | 引用计数 + 原子操作 | 手动管理释放时机,容易 race condition |
| 大数据量 metadata(> 1MB) | 共享内存 + fd 传递 | Binder 直接拷贝导致内存峰值 |
| 小数据量 metadata(< 64KB) | Binder 序列化传递 | 共享内存创建/映射开销反而更大 |
| 进程异常退出 | 注册 death recipient 清理共享内存 | 依赖对方进程主动释放 |