如果芯片在上一次操作Flash时遭遇了意外掉电 ,会发生什么呢?
1. 写入过程掉电

一个Record的写入按照如下顺序执行:
- TL Part
- ID Part
- Content
- IC Part
如果在前三步意外掉电,IC Part由于没有开始写入,所以File ID仍然是原始值0xFFFF。
File ID = 0xFFFF的数据也会被视为脏数据,在读取、更新、删除操作的时候,脏数据会被忽略。GC 过程会删除脏数据,收回空间。
如果在第四步掉电,IC Part有两种可能:
- 写入失败:File ID仍然是0xFFFF,情况同上
- 写入错误:CRC 校验将无法通过,应用代码可以得知
所以一旦我们开启了CRC校验,即可解决第四步出现的意外掉电情况。
(经过我有限的测试,在第四步掉电时,只遇到“写入失败”,完全没有遇到“写入错误”,一个猜测是芯片电源管脚上的电容余电保护Flash将单个Word写入完毕。)
2. 删除、更新过程掉电
FDS 的删除操作实际是将Record Key写成0x0000,使该数据成为脏数据。所以删除操作本质上仍然是写操作。
如果写Record Key失败,它并未变成0x0000,那么下次运行时FDS 会把它识别成有效数据。应用层程序应该有意识的去处理这种“删除失败”的情况。
更新操作实际是先删除原数据,再写入新数据。它的掉电情况与删除和写入一致。
3. 读取过程掉电
无需分析,没有任何影响。
4. GC过程掉电

GC 过程共有四个步骤,在第一步时掉电,GC 尚未开始,没有任何影响。
在其他三个步骤时掉电,都会影响到FDS 数据页和交换页的存在情况。这里使用一个标志位flag来标记各个页的类型,枚举出全部可能的情况:
- 如果是空白页,则flag = PAGE_ERASED
- 如果不是空白页 ,页头有效,是数据页,则flag = PAGE_DATA
- 如果不是空白页,页头有效,是交换页,页内空白,则flag = PAGE_SWAP_CLEAN
- 如果不是空白页,页头有效,是交换页,页内有内容,则flag = PAGE_SWAP_DIRTY
(其实这里忽略了一种情况,如果不是空白页,但页头无效,则flag = NO_PAGES,FDS 对这种情况是直接忽略该页,就像Windows面对硬盘坏道一样不做处理。为了方便介绍,这里故意跳过它。)
对于FDS_VIRTUAL_PAGE = 3的情况,我们分析它们的flag。每个页有4种可能的情况,3个页总的可能情况是4 x 4 x 4 = 64。
事实上,我们无需枚举这么多情况,因为它只是掉电,FDS的基本结构仍然存在,即:
- FDS 最多只有一个交换页
- FDS 可以有多个数据页
如果交换页存在,它只可能存在一个,从代码实现上,不可能出现多个交换页的情况。所以我们更关注数据页和交换页的存在与否,而非具体数目。
使用逻辑“或”操作来枚举各种情况:
- 所有页均是擦除页:PAGE_ERASED
- 所有页均是数据页:PAGE_DATA
- 存在擦除页,存在数据页: (PAGE_ERASED | PAGE_DATA)
- 存在擦除页,存在交换页:(PAGE_ERASED | PAGE_SWAP_CLEAN) or ( PAGE_ERASED | PAGE_SWAP_DIRTY)
- 存在数据页,存在交换页: (PAGE_DATA | PAGE_SWAP_CLEAN) or ( PAGE_DATA | PAGE_SWAP_DIRTY)
- 存在擦除页,存在数据页,存在交换页: ( PAGE_ERASED | PAGE_DATA | PAGE_SWAP_CLEAN) or ( PAGE_ERASED | PAGE_DATA | PAGE_SWAP_DIRTY)
以上覆盖了所有可能出现的GC过程掉电的情况,重启芯片,FDS 在初始化时候对它们进行相应处理:
场景 | 方案 |
PAGE_ERASED | 新建数据页和交换页 |
PAGE_DATA | 报错 |
PAGE_ERASED | PAGE_DATA | 将一个擦除页设为交换页,其他擦除页设为数据页。(这就是GC第四步掉电情况。) |
PAGE_ERASED | PAGE_SWAP_CLEAN | 将所有擦除页设为数据页 |
PAGE_ERASED | PAGE_SWAP_DIRTY | 将该交换页改成数据页,第一个擦除页设为交换页,其他擦除页为数据页。(如果FDS页数是2的话,它就是GC第三步掉电情况。) |
PAGE_DATA | PAGE_SWAP_CLEAN | 这是正常运行的情况 |
PAGE_ERASED | PAGE_SWAP_DIRTY | 把交换页擦掉并重新设置交换页。(这就是GC第二步掉电情况,此时我们不是继续往下走,而是回滚,因为不知道GC第一步是否执行完毕。) |
PAGE_ERASED | PAGE_DATA | PAGE_SWAP_CLEAN | 将擦除页设置成数据页 |
PAGE_ERASED | PAGE_DATA | PAGE_SWAP_ DIRTY | 将该交换页改成数据页,第一个擦除页设为交换页,其他擦除页为数据页。(它就是GC第三步掉电情况) |
有了上述分析,再去浏览fds_init() -> pages_init()的源代码,其中的局部变量ret就是我们上面这套分析的代码实现。
现在我们知道了,FDS 的初始化其实做了许多事情,它可能涉及Flash的擦写操作,所以它肯定是异步的,在代码中,务必等收到初始化完成事件后再进行后续的读写擦操作。
(完)
不错的分析,上个月我们碰到过类似问题,原因就是fds在工作时异常下电,导致设备再次上电时peer_manage初始不过程序跑不下去,同时也碰到过因为设备回连导致storage full,storage full后触发了low rand 白名单信息的删除,导致配对绑定过的设备无法正常回连,15.x版本的sdk问题比较多,不过这些问题在即将release的sdk 16.0上都解决了。
多谢分享经验