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可能在读的过程中被别的线程改了,怎么办?

我建议的规范是:

  1. 读前检查有效性:确认Metadata没有被标记为“待删除”或“过期”。
  2. 使用版本号机制:每次更新Metadata时,版本号+1。读取时带上版本号,如果版本变了,说明数据被改了,需要重新读。
  3. 读操作要加锁:至少是读锁,防止写操作干扰。

小技巧:对于高频读取的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里最容易出问题的。删早了,别的模块还在用;删晚了,内存泄漏。

我建议的删除流程:

  1. 标记删除:先标记为“待删除”,但不立即释放内存。
  2. 等待引用归零:检查所有引用计数,等没人用了再删。
  3. 清理关联资源:如果Metadata关联了其他资源(比如DMA buffer、文件句柄),记得一起清理。
  4. 通知管理器:告诉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存到文件里,或者通过网络传出去。到时候见。