NVS 原理与恢复行为

本页依据 文档基线与后续更新 中记录的 Zephyr main 快照更新。

NVS 通过顺序追加、元数据提交和循环回收减少 Flash 擦除次数。 本页描述当前存储算法的关键约束;私有字段和控制流程仍需对应具体提交阅读。 公共使用方法见 NVS 非易失键值存储。

sector 中的两条增长方向

数据从 sector 前部向后追加,allocation table entry(ATE)从末尾向前追加。 一条记录包含载荷及元数据,元数据描述 ID、数据偏移、长度等信息,并带元数据 CRC。 基础元数据逻辑大小为 8 字节,物理占用还需按设备写块对齐。

NVS 数据与元数据追加的概念示意

图是早期教程中的概念图。当前字段和实际布局见本页列出的固定提交源码。 ID 的高值有内部用途,不应把 16 位 ID 范围全部当成可用的应用键。

提交、读取与删除

写入先追加数据,再写入有效元数据。只有数据、缺少完整有效 ATE 的区域在挂载恢复时 不会被当成成功提交的新值。元数据 CRC 用于识别未完成写入, 可选数据 CRC 则用于载荷校验,两个作用不能互换。

读取沿记录寻找相同 ID 的最新有效版本。写入前会比较已有值,相同 ID 的内容未改变时 避免再次写 Flash。删除使用零长度记录表达;旧载荷并不会因删除立即物理擦除, 因此删除 API 本身不构成安全擦除承诺。

多个 ID 的连续更新是多次独立写入,不构成跨 ID 的事务。 需要原子更新一组参数时,可把它们放进一条带版本号的数据记录,或在应用中设计提交协议。

sector 切换与垃圾回收

当前 sector 容量不足时,NVS 进入循环中的下一位置,并处理仍有效的记录, 使即将被擦除区域中的最新数据能够保留下来。 至少保留一个空闲 sector,才能有空间复制和继续更新。

NVS 循环回收与有效记录搬移的概念示意

有效数据集过大或可用空间过少,会造成回收压力。实现会避免无休止的擦除循环并返回空间 不足错误,应用不能只靠重试解决容量问题。 nvs_sector_use_next() 可强制切换,但频繁使用会增加擦除, 不适合作为每次写入后例行调用的“提交”接口。

挂载与中断恢复

nvs_mount() 检查设备写块、sector 布局与数量,再扫描已有记录和 sector 标记, 恢复追加位置与有效数据。记录完整性依赖介质与驱动遵守写入、擦除语义。

应用仍需验证自己的记录版本、长度、数值范围和跨版本兼容性。 “能够恢复最后完整的 NVS 记录”不意味着任意断电位置下,所有应用状态都自动满足业务一致性。 升级改变数据 CRC、分区大小、sector 组织或写块大小时,应先确认旧数据的可读性。

磨损估算

可用如下粗略关系估算擦除寿命:

寿命(分钟)≈ sector 数量 × sector 大小 × 单页允许擦除次数
              / [每分钟写入次数 × (记录数据长度 + 元数据开销)]

实际还包含对齐、CRC、sector 标记、垃圾回收搬移、有效数据占用和不均匀布局等开销, 这个关系不能替代器件规格与负载测量。减少不必要更新、聚合变化、增加可用空间, 通常比频繁强制擦除更适合延长寿命。

源码阅读顺序

从公共 nvs_mount/read/write/delete 接口开始,再查看 subsys/kvss/nvs/nvs.c 中的挂载、追加与 GC 路径,以及 nvs_priv.h 的 ATE 与地址布局。 旧教程中的 subsys/fs/nvs 路径已经迁移。

上游依据