4、Metadata生命周期管理:创建、读取、更新、删除(CRUD)操作规范
好,咱们今天聊聊Metadata的CRUD。说白了,就是怎么管好这些“数据的数据”。
我在做Camera HAL的时候,遇到过最头疼的问题之一,就是Metadata的生命周期混乱。有的节点创建了没销毁,有的读到了过期的值,还有的更新了一半系统就崩了。嗯,这些坑我都踩过。所以今天我把这些经验整理成规范,希望能帮你少走弯路。
4.1 创建(Create):从无到有,规矩要立好
创建Metadata,不是new一个对象那么简单。你得想清楚:这个数据归谁管?什么时候创建?创建后谁来销毁?
我个人习惯,把创建操作分成两类:
- 静态创建:在系统初始化时,一次性创建好所有固定Metadata。比如Sensor的物理尺寸、像素数这些不会变的数据。
- 动态创建:在运行时按需创建。比如每一帧的曝光参数、白平衡设置,这些每帧都可能变。
核心原则:谁创建,谁负责。 创建者必须记录Metadata的“出生证明”——包括创建时间、创建者ID、预期生命周期。
来看一段伪代码,这是我常用的创建模式:
// 创建Metadata实例
metadata_t* meta = metadata_create(METADATA_TYPE_FRAME);
// 设置基础属性
meta->owner_id = get_current_thread_id();
meta->created_at = get_timestamp();
meta->expected_lifetime = LIFETIME_FRAME_BASED; // 基于帧的生命周期
// 初始化默认值
metadata_set_default(meta, "exposure_time", 10000); // 10ms
metadata_set_default(meta, "iso", 100);
// 注册到管理器
metadata_manager_register(meta);
这里有个细节:expected_lifetime 字段。我曾经在项目中漏掉这个字段,结果一个Metadata被多个模块引用,谁都不敢删,最后内存泄漏了。所以,创建时就定好“寿命”,后面就好管理了。
4.2 读取(Read):别读到脏数据
读取操作看起来简单,但坑最多。你想想看,一个Metadata可能在读的过程中被别的线程改了,怎么办?
我建议的规范是:
- 读前检查有效性:确认Metadata没有被标记为“待删除”或“过期”。
- 使用版本号机制:每次更新Metadata时,版本号+1。读取时带上版本号,如果版本变了,说明数据被改了,需要重新读。
- 读操作要加锁:至少是读锁,防止写操作干扰。
小技巧:对于高频读取的Metadata(比如每帧都要读的曝光参数),可以用RCU(Read-Copy-Update)机制。读操作完全无锁,写操作通过拷贝替换来实现。我在高通平台上用过这个方案,性能提升很明显。
代码示例:
// 安全读取Metadata
metadata_t* meta = metadata_manager_find("frame_123_exposure");
if (meta == NULL) {
// 处理找不到的情况
return ERROR_NOT_FOUND;
}
// 加读锁
metadata_read_lock(meta);
// 检查版本号
uint32_t version_before = meta->version;
int exposure = meta->value.exposure_time;
// 读完后检查版本是否变化
if (meta->version != version_before) {
// 数据被改了,需要重新读
exposure = meta->value.exposure_time;
}
// 释放读锁
metadata_read_unlock(meta);
嗯,这里要注意:版本号检查不能保证100%安全,但能覆盖99%的场景。对于要求极高的场景,可以用双缓冲机制。
4.3 更新(Update):原子性是关键
更新操作,说白了就是改数据。但改数据的时候,最怕改到一半系统崩了,或者别的线程读到一半的数据。
我总结的更新规范:
- 原子更新:要么全部改完,要么一点不改。用事务机制或者写锁来实现。
- 更新前备份:对于关键Metadata,更新前先备份旧值。万一新值有问题,可以回滚。
- 通知订阅者:更新完成后,通知所有订阅了这个Metadata的模块。别让人家还用旧数据。
我曾经踩过的坑:有一次更新Sensor的帧率参数,只更新了驱动层的值,忘了通知算法层。结果算法层还在用旧帧率做计算,导致画面闪烁。从那以后,我强制要求所有更新操作必须带通知机制。
更新代码示例:
// 原子更新Metadata
metadata_update_begin(meta); // 开启事务
// 备份旧值
metadata_backup(meta);
// 设置新值
meta->value.exposure_time = 20000; // 20ms
meta->value.iso = 200;
meta->version++; // 版本号递增
// 提交事务
metadata_update_commit(meta);
// 通知订阅者
metadata_notify_subscribers(meta, "exposure_params_changed");
4.4 删除(Delete):善后工作要做足
删除操作,是CRUD里最容易出问题的。删早了,别的模块还在用;删晚了,内存泄漏。
我建议的删除流程:
- 标记删除:先标记为“待删除”,但不立即释放内存。
- 等待引用归零:检查所有引用计数,等没人用了再删。
- 清理关联资源:如果Metadata关联了其他资源(比如DMA buffer、文件句柄),记得一起清理。
- 通知管理器:告诉Metadata管理器,这个节点已经释放了。
核心原则:延迟删除,安全第一。 宁可多等几毫秒,也别删错数据。
删除代码示例:
// 安全删除Metadata
metadata_mark_deleted(meta); // 第一步:标记
// 等待引用归零
while (meta->ref_count > 0) {
// 可以设置超时,防止死等
wait_for_ref_count_zero(meta, 100); // 最多等100ms
}
// 清理关联资源
metadata_cleanup_resources(meta);
// 从管理器中移除
metadata_manager_unregister(meta);
// 释放内存
metadata_destroy(meta);
4.5 CRUD操作规范总结
好了,我把这些规范整理成一张表,方便你查阅:
| 操作 | 核心要求 | 常见陷阱 | 我的建议 |
|---|---|---|---|
| 创建 | 明确生命周期、设置默认值 | 忘记注册到管理器 | 创建时立即注册 |
| 读取 | 检查有效性、版本号、加锁 | 读到过期数据 | 用RCU优化高频读取 |
| 更新 | 原子操作、备份、通知 | 更新后忘记通知 | 强制带通知机制 |
| 删除 | 标记删除、等待引用归零 | 内存泄漏或野指针 | 延迟删除策略 |
最后说一句:CRUD规范不是死规矩,而是经验总结。你在实际项目中,可以根据具体场景灵活调整。但核心思想不变——数据的安全性和一致性,永远是第一位的。
下一章,咱们聊聊Metadata的序列化与反序列化。说白了,就是怎么把Metadata存到文件里,或者通过网络传出去。到时候见。