#littlefs原理分析#[二]commit机制 原创 精华
作者:蒋卫峰 李涛
前言
回顾littlefs的存储结构,其中最为核心的是元数据。元数据以tag为单元进行信息的存储,以commit的方式进行信息的更新。当littlefs中进行文件、目录的创建、修改、删除等一系列操作时,实际上都是通过向元数据中进行commit操作的方式实现。本文将对littlefs中commit机制进行介绍。
1. commit过程
commit具体的过程如下图所示:
每一次commit时都会向元数据对中写入一系列的tag和数据,并计算CRC。因为在commit时总是会进行CRC计算,并且从元数据中读取数据时会做校验,因此一次commit是一次原子操作。
下面以具体的案例对commit过程进行说明:
1.1 超级块的创建
当littlefs进行格式化操作时,会进行超级块的创建。超级块创建主要是在根目录对应元数据对的块中进行commit:
如上图,超级块创建时调用lfs_dir_commit函数写入了CREATE、SUPERBLOCK、INLINESTRUCT和CRC类型的tag。lfs_dir_commit函数中进行了commit的实际写入操作,其主要分为以下两个步骤:
-
将tag和对应的数据依次写入元数据对应块中
-
计算CRC,并接着将其tag和CRC数据写入元数据对应块中
超级块创建相关函数:
1.1.1 tag和相应数据的写入
如上图,在commit过程中,ptag用于进行tag的异或运算,其初始化值为0xffffffff。ptag会依次与将要commit的tag(如上图中的tagA、tagB、tagC)进行异或运算,然后将运算后的结果存储到块中。同时每写一个tag后,将其对应的数据也进行写入。
相关函数分析:
1.1.2 CRC的写入
commit过程中,当写入tag(异或后的tag)或数据时,均会进行逐字节CRC的计算。最后commit完成后,再写入相应的CRC tag和对应的CRC值。同时为了写入对齐,在CRC tag后会设置相应的padding。
CRC计算的范围为从本次commit起始的tag和数据,一直到CRC tag(包括CRC tag)。
写入CRC后块中布局如下图:
相关函数分析:
1.1.3 crc的计算
littlefs中crc计算核心函数如下:
本文中不对crc的原理进行具体介绍,有兴趣的读者可参考以下链接:
littlefs中crc计算算法的特点是采用了32位的crc结果,输入数据以4bit为单位进行计算。其中还用到了lookup table进行加速,因为输入的数据以4bit为单位进行计算,每次有2^4即16种可能的输入,所以该lookup table的长度为16。从lookup table中还可看出该crc算法对应的多项式值为0x1db71064。
1.2 tag的遍历和写入总结
与遍历并写入tag等数据相关的函数为lfs_dir_traverse,该函数被lfs_dir_commit调用。该函数只用于commit等需要写入tag数据的过程,不用于遍历获取数据。其流程如下:
代码分析如下:
2. compact过程
当commit时,如果元数据对应块中的空间不够,则会尝试进行compact操作。如下图:
上图中左边元数据块中有两个commit,其中tag A’是tag A的更新版本。当commit一个tag B’作为tag B的更新版本后,如右图中右边的元数据块,冗余的tag A和tag B被去除,只剩下一个commit。
如果compact后元数据块中的空间足够的话,在进行完compact操作之后的元数据块中就只会剩下一个CRC tag,即只有一个commit。compact的主要过程其实就是筛除冗余的tag和数据之后,将剩下的tag和数据作为一次commit写入同元数据对中的另一个块中。
2.1 compact去除内容
下面对compact过程中具体会去除的tag和相应数据进行说明。
compact过程中调用了lfs_dir_traverse进行去除冗余tag并写入:
在lfs_dir_traverse中用下面的逻辑进行筛选tag:
其中,mask为LFS_MKTAG(0x7ff, 0, 0),tmask为LFS_MKTAG(0x400, 3ff, 0),ttag为LFS_MKTAG(LFS_TYPE_NAME, 0, 0)即LFS_MKTAG(0, 0, 0),则筛选逻辑可简化为:
总结有以下类型的tag会被去除:
-
LFS_TYPE_SPLICE:包括CREATE和DELETE
-
LFS_TYPE_TAIL:包括SOFTTAIL和HARDTAIL
-
LFS_TYPE_GLOBALS:即MOVESTATE
-
LFS_TYPE_CRC
2.2 compact写入过程
与commit中tag和数据的写入过程类似,compact中的写入过程包括:
-
写入更新后的revision count
-
写入去除冗余后的tag和数据
-
如果原来有SOFTTAIL或HARDTAIL,则将原来最后一个TAIL补充写入。因此,compact过程对目录的遍历方式无影响,具体目录的遍历见后面的文章。
-
如果原来有MOVESTATE且进行异或之后不为0,则将异或后的gstate作为MOVESTATE tag补充写入。gstate相关机制见后面的文章。
-
写入CRC
相关函数分析:
3. split过程
当commit时,进行compact操作后仍空间不足,则会进行split操作,将元数据对划分为多个块进行存储:
如上图,左图中右边的元数据块为最新的元数据块,其中包含一个commit的内容,即tag A’和tag B’。当再次commit tag C和tag D时,一个元数据块装不下,就会进行split操作。split操作在第一个元数据块commit的末尾加入一个HARDTAIL类型的tag,指向一个新的元数据块。新的元数据块中再装入剩下的tag和数据。
split操作中会递归调用compact和split,使得在一次split的容量无法满足commit需求的时候,进行多次split。
相关函数分析:
4. 异常情况
前文中提到每次commit之后,都会写入计算的CRC tag,用于校验。commit过程因此也是一次原子操作。当commit过程中发生掉电等情况导致commit过程失败时,磁盘中元数据末尾虽然有写入的部分tag和数据,但没有最终的CRC tag,因此当进行tag的遍历等操作时并不会将这次commit视为有效。compact过程和split过程中也类似,只要没有最终写入CRC tag,便不会被视为完成一次commit。如下图:
另外,当split为多个块时,由前文中相关分析,compact和split会递归调用,并提前检查块大小是否满足需求和分配相应块,最终写入多个块。split过程时若没有足够的块,则会报错,且并不写入实际内容。split过程时若中途commit失败,则会导致上一个元数据块末尾的HARDTAIL指向的块中没有有效的CRC tag,进行遍历时会直接返回错误,如下图:
总结
本文介绍了littlefs中的commit机制,包括commit、compact、split操作的过程,怎样写入tag和数据,怎样计算CRC,以及相应的异常情况处理等。commit是一个写入元数据的过程,后面的文章将会介绍怎样读取元数据,从tag中获取目标信息。
更多原创内容请关注:深开鸿技术团队
入门到精通、技巧到案例,系统化分享OpenHarmony开发技术,欢迎投稿和订阅,让我们一起携手前行共建生态。
前排学习littlefs教程