FDS的掉电分析

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

1. 写入过程掉电

一个Record的写入按照如下顺序执行:

  1. TL Part
  2. ID Part
  3. Content
  4. 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 可以有多个数据页

如果交换页存在,它只可能存在一个,从代码实现上,不可能出现多个交换页的情况。所以我们更关注数据页和交换页的存在与否,而非具体数目。

使用逻辑“或”操作来枚举各种情况:

  1. 所有页均是擦除页:PAGE_ERASED
  2. 所有页均是数据页:PAGE_DATA
  3. 存在擦除页,存在数据页: (PAGE_ERASED | PAGE_DATA)
  4. 存在擦除页,存在交换页:(PAGE_ERASED | PAGE_SWAP_CLEAN) or ( PAGE_ERASED | PAGE_SWAP_DIRTY)
  5. 存在数据页,存在交换页: (PAGE_DATA | PAGE_SWAP_CLEAN) or ( PAGE_DATA | PAGE_SWAP_DIRTY)
  6. 存在擦除页,存在数据页,存在交换页: ( 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的擦写操作,所以它肯定是异步的,在代码中,务必等收到初始化完成事件后再进行后续的读写擦操作。

(完)