1、quick notes
littlefs是block-based文件系统。flash被划分成多个相同大小的block
block pointer是32bits,0xFFFFFFFF表示空指针
除了逻辑block size(通常是erase block size),littlefs还有program block size和read block size,后面两个block size主要是用于flash 读写时的对齐
littlefs采用little-endian order
2、Directories/Metadata pairs
metadata pair是littlefs的基石,提供了原子更新的能力。superblock也是存储在metadata pair中。metadata pair存储在两个block中,一个block在另一个block erase的时候提供备份。同一个metadata pair中的两个block并不要求是连续的,可以分散在不同的位置,通过指针链接。
每一个metadata block类似于一个可以追加的log,包含多个commit。commit可以按照顺序追加到metadata block中而不需要erase操作。同时,后续的commit可以取代旧的commit,只有最新的commit才被认为是有效的。
每个metadata block包含一个32-bit的revision count,后面跟着多个commit。每个commit包含一个或多个entry,每个commit的最后是32-bit的CRC。entry不一定是word-aligned。但是每个commit都是program block size对齐的,因此commit与commit之间可能存在padding。
metadata block的主要域:
1)revision count:32bit长。每擦除一次就增加1,如果两个metadata block都包含有效的数据,则使用最新的revision count所在的block。
2)CRC:32bit长。使用0x04c11db7多项式,初始值为0xFFFFFFFF。
3)entry:每个entry以32-bit的tag开始,后面跟着可变长度的数据。
metadata block支持前向和后向的迭代。为了减少重复空间,相邻entry的tag通过XOR连接起来,初始值是0xffffffff。
每个entry中还包含一个valid bit用于指示当前entry和commit有效。
下图是包含4个entry的block。
.---------------------------------------.
.-| revision count | tag ~A | \
| |-------------------+-------------------| |
| | data A | |
| | | |
| |-------------------+-------------------| |
| | tag AxB | data B | <--. |
| |-------------------+ | | |
| | | | +-- 1st commit
| | +-------------------+---------| | |
| | | tag BxC | | <-.| |
| |---------+-------------------+ | || |
| | data C | || |
| | | || |
| |-------------------+-------------------| || |
| | tag CxCRC | CRC | || /
| |-------------------+-------------------| ||
| | tag CRCxA' | data A' | || \
| |-------------------+ | || |
| | | || |
| | +-------------------+----| || +-- 2nd commit
| | | tag CRCxA' | | || |
| |--------------+-------------------+----| || |
| | CRC | padding | || /
| |--------------+----+-------------------| ||
| | tag CRCxA'' | data A'' | <---. \
| |-------------------+ | ||| |
| | | ||| |
| | +-------------------+---------| ||| |
| | | tag A''xD | | < ||| |
| |---------+-------------------+ | |||| +-- 3rd commit
| | data D | |||| |
| | +---------| |||| |
| | | tag Dx| |||| |
| |---------+-------------------+---------| |||| |
| |CRC | CRC | | |||| /
| |---------+-------------------+ | ||||
| | unwritten storage | |||| more commits
| | | |||| |
| | | |||| v
| | | ||||
| | | ||||
| '---------------------------------------' ||||
'---------------------------------------' |||'- most recent A
||'-- most recent B
|'--- most recent C
'---- most recent D
从上图中可以看出,第一个entry的tag是本entry的原始tag和0xFFFFFFFF进行XOR计算的结果。每个commit的最后一个entry是CRC,包含前一个entry的tag与CRC进行XOR计算的结果,例如1st commit中的entry C之后有tag CxCRC。
3、metadata tag
在littlefs中,32-bit的tag代表了所有的metadata类型,包含file entry, directory, global state和CRC。
32-bit的tag格式如上图。32-bit的tag是按照big-endian存储的。这也是littlefs中唯一按照big-endian存储的数据结构。主要是因为valid bit必须是commit的第一个bit,按照big-endian存储会简化代码。0x00000000和0xffffffff都是非法的tag。metadata tag各个域的详细解释如下:
1)valid bit(1-bit):指示tag是否valid
2)type3(11-bits):tag的类型。11bit又分为3bit的type1,又叫abstract type和8bit的chunk。0x000是非法的type
3)type1(3-bit):又叫abstract type
4)chunk(8-bits)和前面的abstract type组合表示多种不同的含义。type1+chunk+id组成一个唯一的id。
5)id(10-bits):metadata block中的每个文件都有一个唯一的id用于连接文件和tag。0x3ff用来表示不和文件关联的tag,比如目录和global metadata
6)length(10-bits):data的长度,0x3ff表示这个tag被删除
4、metadata type
1)0x401 LFS_TYPE_CREATE
创建新文件时会使用这个id。但是inline file不需要这个tag。创建所做的全部工作就是移动使用此ID的任何文件。从这个意义上说,创建类似于插入虚构的文件数组中。
2)0x4ff LFS_TYPE_DELETE
删除文件时使用此id。和create相反,此标记将移至与该id相邻的任何文件,类似于从虚构的文件数组中删除。
3)0x0xx LFS_TYPE_NAME
使用此id关联文件名和文件类型。chunk域表格8-bit的文件类型。
name tag的域如下图:
a)file type(8-bits):文件类型
b)file name:文件名,ASCII字符串。
4)0x001 LFS_TYPE_REG
初始化id+name为普通文件。
5)0x002 LFS_TYPE_DIR
初始化id+name为目录。littlefs中目录是通过metadata pair链表的形式存储的,每个pair都包含任意数量的文件,文件按照字母顺序排列。一个指向目录的指针存储在tag结构中。
6)0x0ff LFS_TYPE_SUPERBLOCK
初始化id为superblock。super block存储右格式化的配置信息和文件系统识别信息。superblock entry是存储在block0和block1上的一个metadata pairs,并且最后一个通过metadata pair会重复一次作为根目录。
superblock entry的内容存储在type为superblock的name tag和inline tag里。name tag包含『littlefs』字符串,而inline tag包含版本和配置信息。name tag和inline tag的示意图如下:
各域的定义:
a)magic string(8-bytes):『littlefs』字符串
b)version(32-bits):高16 bit为主版本号,低16 bit为次版本号
c)block size(32-bits):logical block size
d)block count(32-bits):number of blocks
e)name max(32-bits):文件名的最大长度
f)file max(32-bits):文件的最大长度
g)attr max(32-bits):文件属性的最大长度
superblock必须是metadata pair的第一个entry(id 0),也必须是第一个写入到block的entry。这意味着superblock可以通过地址偏移的方式直接读取。
7)0x2xx LFS_TYPE_STRUCT
通过id来关联数据结构。数据结构的具体布局取决于数据结构的type(存储在chunk域)。
8)0x200 LFS_TYPE_DIRSTRUCT
目录数据结构。littlefs中目录是通过metadata pair链表的形式存储的,每个pair都包含任意数量的文件,文件按照字母顺序排列。
目录结构tag只包含指向本目录的第一个metadata pair的指针。目录的大小只能通过遍历获取。指向本目录另一个metadata pair的指针存储在tail tag中。目录tag的布局如下:
a)metadata pair(8-bytes):指向本目录的第一个metadata pair。
9)0x201 LFS_TYPE_INLINESTRUCT
inline结构。inline结构主要用于存储小文件。文件直接存储在metadata pair中。文件的数据存储在tag的data字段中。
a)inline data:直接存储在metadata pair的文件数据。
10)0x202 LFS_TYPE_CTZSTRUCT
CTZ skip-list结构。CTZ skip-list结构用来存储大文件。文件存储在反向的skip-list中,一个指针指向skip-list的head。通过skip-list head和文件size信息就足够读取文件了。skip-list的示意图:
对于skip-list可以简单描述如下:
一个block中指针的最大数量由文件的最大长度和block size确定。对于32 bits的文件长度,最小的block size是104 bytes。
CTZ tag的示意图如下:
a)file head(32 bits):指向位于skip-list的head的block
b)file size(32 bits):文件大小
11)0x3xx LFS_TYPE_USERATTR
用户属性结构。
user_attr各域:
a)attr type(8-bits):user attribute类型
b)attr data:user attribute数据
12)0x6xx LFS_TYPE_TAIL
metadata pair的tail指针。tail指针用来把所有的metadata pairs连接成链表。chunk域包含tail指针的类型。tail指针分为hardtail和softtail。
hardtail:下一个metadata pair属于当前目录
softtail:下一个metadata pair不属于当前目录,只是为了方便文件系统的遍历
通常,metadata pair列表包含所有的metadata pair。但是有些操作导致out of sync,比如掉电,这是『global state』中的sync标志置位。当sync标志置位时:
a)metadata pair链表可能包含孤儿目录
b)metadata pair链表中可能包含和bad block关联的metadata pair,而这个bad block已经被替换了
一旦发现sync被置位,线索链表必须进行校验,如果littlefs是只读的则可以不用。
tail tag 的布局:
各域:
a)tail type(8-bits):tail指针的类型
b)metadata pair(8-bytes):指向下一个metadata pair
13)0x600 LFS_TYPE_SOFTTAIL
softtail类型的tail结构,说明下一个metadata pair不属于当前目录,只有在文件系统遍历时才会使用。
14)0x601 LFS_TYPE_HARDTAIL
hardtail类型的tail结构,说明下一个metadata pair属于当前目录。在littlefs中,目录是按照字母顺序排列的,因此下一个metadata pair中包含的文件名只能比当前的要大。
15)0x7xx LFS_TYPE_GSTATE
『global state』结构。
『global state』的大小和格式依赖于type,type存储在chunk域。目前只支持MOVE STATE.
16)0x7ff LFS_TYPE_MOVESTATE
提供『global state』中的move state相关的delta结构。move state主要存储可能会导致文件系统out of sync的操作信息。比如在metadata pairs之间移动文件或者对metadata pair链表本身的操作。
对于move操作,move state包含tag+源metadata pair信息。如果tag不为0,说明在move的过程中可能发生可掉电或者复位,而文件存在于两个不同的位置。对于这种情况,源文件应该被删除。
对于metadata pair链表本身的操作,单独的sync标志用来指示修改,如果sync被置位,线索链表需要校验,
move state tag的布局:
各域:
a)sync bit(1-bit):指示metadata pair线索链表处于in-sync。如果sync被置位,则链表在使用前需要校验
b)move type(11-bits):move的类型。0x000表示没有任何move,0x4ff表示源文件需要删除,其他的值均非法
c)move id(10-bits):需要move的file id
d)metadata pair(8-bytes):指向含有move的metadata pair
17)0x5xx LFS_TYPE_CRC
crc tag。crc表示了commit的结束,同时也提供校验信息。
data字段的前32-bit包含crc32的值。crc32计算时使用的多项式是0x04c11db7,初值是0xffffffff。对于第一个commit,计算crc时会包含revision count。
crc tag的布局:
a)valid state(1-bit):指出下一个tag的valid bit的值。这样可以确保metadata block中未写入的区域会被识别为无效区域。
b)crc(32-bits)
c)padding:program size对齐
————————————————
版权声明:本文为优快云博主「nstcl」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/nstcl/article/details/108077923