NvM 升级后读出历史残留的数据?可能是你对"抗变"的理解错了
软件升级之后,有个 NvM Block 读出来的数据不是改版前存好的旧值:有时是默认值,有时竟是一串早就以为没有的旧数据。你八成也想过:我都勾了抗变,不该读出改版前存好的旧值吗?
讲清楚这件事之前,先说三个词:抗变,就是NvM Layout配置变了,NvM 还照旧去读 NV 里的数据,对应NvM的参数 NvMResistantToChangedSw;NV, 指 EEP存储的数据,掉电不丢;默认值,是编译进程序里预先写好的那份数据(ROM 里的值,不能修改也不丢失)。
再借个比喻,帮你把这些名词对上号:NvM Block 是相框,数据是照片,长度是相框尺寸。RAM 是桌上的相框(掉电就丢失),EEP 是档案柜里存的照片(只要不擦除EEP,就一直存着数据),默认值是相框自带的底图(天生自带,不会丢失,也不能被修改)。NvM 是管理员,配置 ID (Compiled Configuration ID)是整个档案柜共用的一张版本贴纸。你换了新尺寸的相框,没人替你把旧照片裁好塞进去。读出来的,要么是底图,要么是档案柜里一张尺寸对得上的旧照(这里事先假设永远不会存在能读到半截照片的情况,要么读出来一张尺寸刚好的旧照,要么读出来底图,没有第三种情况)。

图1:相框、档案柜、底图和管理员的对应关系
比喻和真实术语的对照,先记在这儿,后面忘记了回到这里看:
-
相框 = NvM Block;相框尺寸 = 块长度
-
桌上的相框 = RAM 块;档案柜里的照片 = EEP(NV)里存的数据
-
底图 = 默认值(编译进程序、放在 ROM 里的那份)
-
管理员 = NvM 模块,上电读块的动作是 NvM_ReadAll
-
版本贴纸 = 配置 ID(Compiled Configuration ID)
-
勾了抗变 = 块的 NvMResistantToChangedSw 为 True
-
三道门 = 三个判断:配置 ID、NvMDynamicConfiguration、NvMResistantToChangedSw
后文为了不绕,关键位置我会把比喻和真实术语放在一起写。
一、升级后读 NV 之前,NvM 要过三道门
配置工具里那几个开关,谁管谁,很容易混。本文只追一件事:勾了抗变之后,读出来的旧值到底是哪一版的。
先看 NvM_ReadAll(管理员)的判断顺序,见图2。

图2:NvM_ReadAll 升级后读 NV 的三道门(判定流程)
-
第一道门:配置 ID(柜子的版本贴纸)对得上吗?对上,照常把 EEP 里的值读进 RAM;对不上,进第二道门。
-
第二道门:NvMDynamicConfiguration(动态配置)开着吗?False,不读 NV,用默认值(底图);True,进第三道门。
-
第三道门:这个块的 NvMResistantToChangedSw(抗变)勾了吗?没勾(False),忽略 NV,用默认值;勾了(True),把 NV 里的数据装进 RAM。
另外,配置 ID 不自增,NvM 就认为配置没变,不会走后两道门。
二、真相:长度变了,抗变取不回改版前的值
勾了抗变,NvM 就无视“配置变了”这件事,像没变过一样照常去 EEP(档案柜里)翻。
翻出来的,不是改版前写进去的旧值。实验是这样的:块 A 长度由小变大,配置 ID 变了,勾了抗变,EEP 从没擦过。EEP 里要是有一份“同样长度、写过值”的历史数据(柜里的旧照),读到的就是它;没有,就读到默认值(底图)。大变小同理。这就是“脏数据”的来路。
NvM 为什么偏偏认长度对得上的那份?我的推断是存储按长度匹配,这只是推断,没验证过机制(当然,如果NvM底层用的Fee来自不同的供应商,那么实现机制可能会有差异,建议仿造我的方法测试)。
图3 把这个现象画成时间线,数据用 X1、X2、X3代替:

图3:三个版本的时间线,升级后读到第 1 版的值
具体是这样:第 1 版长度 2,写入 X1X1;第 2 版长度 1,写入 X2;当前第 3 版长度又回到 2,默认值是 X3X3。升级后读到的是 X1X1,既不是 X2,也不是默认值 X3X3。
读到 X1X1的那一刻,你大概会怀疑是不是自己改错了配置。未必,多半是 EEP 里真有这份旧数据。
前提都是块 A 长度已变、EEP 没擦,三种情形各读到什么:
| 情形 | 读到的值 |
|---|---|
| 小→大,勾抗变,配置 I D 变 | EEP 里有同长度的旧数据→读到那份历史值(如 X₁X₁);没有→默认值(如 X₃X₃) |
| 大→小,勾抗变,配置 I D 变 | 同上 |
| 不勾抗变 | 默认值(如 X₃X₃) |
抗变不是备份
,它只管“照常去 EEP 里读”,不管读出来的是哪一版的数据。
三、相邻块没事,RAM 和 NV 对不上才出事
相邻块。三个块按 BlockId 排:块 B(2)、块 A(3)、块 C(4)。块 A 不管变长还是变短,B 和 C 照样读到 EEP 里原来的值(设为 Xb、Xc)。我理解这里说的 Layout 指块在存储里的排布,它会受影响,但不影响读到旧值。就像相邻的相框各挂各的,你把中间那个换大了,别的还挂在原处。

图4:块 A 变长,块 B、块 C 仍读到原来的值
边界先说清:本实验配置 ID自增(因为块A的长度变了,NvM Layout自然就变),块B和块C的抗变属性为True,块A的抗变属性为False(当然,本实验中实验对象是块B和块C,块A的抗变属性不重要)。
RAM 对不上 NV。这个坑更阴。RAM 块(桌上的相框)的长度 N,和 EEP 里那份(档案柜里的照片)的长度 M 对不上,在升级过程中一直没擦 EEP 的情况下,会被判定为异常。
要求是两边一样长,并打开 Block Size Check(块长度检查)选项。之所以建议打开Block Size Check,是因为打开后会在代码编译阶段报错,提示你当前NvM Block的配置出错了,而不是等软件装车了再给你暴雷。

图5:RAM 长度 N、NV 长度 M,两边对不上
四、正解:升级前 5 条自查
长度变了的 NvM Block,别想当然勾抗变。不勾抗变→读默认值;勾抗变→取决于 EEP 里有没有同长度的旧数据。想让结果可控,要么不勾抗变,要么升级前擦 EEP。
NvM Layout 升级前,下面 5 条全查一遍。第 3、4 条直接决定会不会读到脏数据,优先。
-
配置 ID。
在配置工具里搜 Compiled Configuration ID。长度变了而 ID 没自增,容易栽。配置变更就自增。按流程图推演,自增后不勾抗变的块都会回到默认值,连长度没变的也一样。
-
NvMDynamicConfiguration。
看取值,再去你用的 NvM 源码里看 NvM_ReadAll 怎么处理它。取 False 且配置 ID 已变时,流程图所示是读默认值,方向以你的源码为准。看不懂分支,就做一次升级前后对比。
-
抗变(优先)。
逐个看块的 NvMResistantToChangedSw。长度变了还勾着,NvM 可能从 EEP 里读出同长度的旧数据。你可以不勾,或者先确认 EEP 里没有同长度的历史数据。
-
EEP 擦没擦(优先)。
以产线/刷写流程为准,别只靠读 EEP 判断。流程不擦、历史上又有过同长度版本,就是隐患。
要擦,先想清代价:擦等于清空整个 EEP,所有 NvM Block 回到默认值,可能包括 VIN、里程等,具体看你的配置。时机上,应赶在第一次 NvM_ReadAll 之前。要保留旧数据,常见思路是应用层加版本号做迁移,本文不展开。
-
RAM 与 NV 长度。
对比两个长度数字,不一样就改一致,有长度检查选项就打开。
这 5 项过一遍,下次升级能少赌一些运气。最后,如果你的项目分支、基线、车型特别复杂、特别多,为了避免因为跨基线升级导致NvM数据丢失,给你一个保命建议:无论多少基线、分支软件版本,全局只设计一张NvM Layout。(这样如论怎么升级,NvM Layout始终是兼容的,不会丢数据)
勾了抗变,不等于旧值受保护。
升级前先问一句:EEP 里有没有同长度的旧数据(柜里有没有同尺寸的旧照)?
你在项目里遇到过哪些跟NvM升级丢数据相关的坑?评论区聊聊。
本文由 [公众号:自学汽车软件] 原创首发,欢迎分享与转发。媒体或公众号如需转载,请在文末或醒目位置注明文章出处及作者。
浙公网安备 33010602011771号