了解更多开源请访问:开源基础软件社区https://ost.51cto.com前言littlefs是一个小型文件系统,具有以下特点:(1)具有磨损均衡功能。(2)具有掉电保护能力。(3)适用于ROM和RAM受限的场景。本系列文章将分析littlefs的原理。作为系列文章的第一篇,先介绍一下littlefs的整体存储结构,然后在后面的文章中分析具体的目录和文件操作。一、概述littlefs的存储结构大致如上图所示。superblock是littlefs存放目录和文件的起点,后面是根目录。littlefs中的目录可以指向其他目录,形成树状结构。一个目录可以包含多个文件。上图右边的目录包含了一个inline文件和一个outline文件。2、元数据在详细介绍littlefs的存储结构之前,先介绍一下littlefs中的一个核心数据结构,即元数据。Littlefs使用元数据来存储目录信息、超级块信息、文件信息、内联文件数据等,这是其设计的核心数据结构。元数据对的存储结构如下图所示:下面是具体的描述:一个元数据对对应两个物理块,每个块记录一个修订计数。revisioncount越大的block存储的内容越新,每当其中的数据更新时,revisioncount就会加1。使用两个block的好处是,当一个block不能容纳更新的内容时,可以压缩数据并转移到另一个块(例如执行压缩操作)以避免直接破坏原始数据。每个超级块和目录都有其对应的一对或多对元数据,元数据对记录与超级块或目录相关的信息。例如,目录对应的元数据对可以存储目录下的文件信息。Metadata以tag为单位存储信息,并以commit的形式更新信息。下面是使用日志文件系统的做法,供参考。如果创建了目录,则会在对应的元数据对中进行commit,并记录CREATE、DIR、DIRSTRUCT等标签,最后计算CRC。每次元数据提交时都会计算CRC,实现数据校验等功能。(1)tag如上所述,tag是元数据中存储信息的单位,其结构如下:[----32----][1|--11--|--10--|--10--]^。^.^^-长度|。|.'------------ID|。'-----.--------------------类型(type3)'.------------.------------------validbit[-3-|--8--]^^-chunk'------type(type1)其中包含有效位,类型,id,标签的长度等信息。对于不同类型的标签,存储的内容也不同。通常,相应数据的内容会紧跟在标签之后。例如CTZSTRUCT类型的tag后面的数据存储了文件大小和文件skipheader所在块号:tagdata[--32--][--32--|--32--][1|-11-|10|10][--32--|--32--]^^^^^^-文件大小||||'------------------文件头|||'-尺寸(8)||'------ID|'------------type(0x202)'----------------validbit3,superblocksuperblock是littlefs的起点存储目录和文件,元数据对所在的块编号从0、1开始。超级块存储为单个或多个元数据对。下图展示了单个元数据对存储超级块的具体情况:包含LFS_TYPE_CREATE、LFS_TYPE_SUPERBLOCK等类型的标签。超级块的具体数据信息保存在LFS_TYPE_INLINESTRUCT类型的标签中。相关数据结构如下:typedefstructlfs_superblock{uint32_tversion;//littlefs版本lfs_size_tblock_size;//一个块的大小lfs_size_tblock_count;//文件系统的总块数lfs_size_tname_max;//文件名的最大字节数lfs_size_tfile_max;//文件大小字节的最大值lfs_size_tattr_max;//文件属性字节的最大值}lfs_superblock_t;4、目录的存储结构如上图所示,以单个或多个元数据对的方式存储。从根目录开始,通过指向末尾其他元数据对的块指针可以形成树结构。单个目录元数据对的具体存储如下:上图中,中间目录使用两个元数据对进行存储。第一个元数据对中的SOFTTAIL类型标签存储了一个块指针,指向父目录中的最后一个目录(即父目录中最后创建的子目录。当父目录中还没有创建子目录时,块指针为无效的)。第二个元数据对存储创建子目录的信息(包括CREATE、DIR、DIRSTRUCT等标签),指向子目录。注:上述目录与其父目录、子目录的链接方式只是一种可能的情况。随着目录的创建、删除、移动,具体的链接方式会发生变化,详见后续文章。相关标签表示如下:元数据对的块指针相关:HARDTAIL:表示同一目录下一个元数据对的块指针。SOFTTAIL:代表不同目录的下一个元数据对的块指针。目录创建信息相关:CREATE、DIR、DIRSTRUCT、SOFTTAIL等类型的标签都会记录在父目录中。DIR:存放目录名和id。DIRSTRUCT:用于存储已创建子目录的元数据对的块指针。SOFTTAIL:一个块指针,记录创建的子目录的元数据对。(1)相关数据结构目录信息在内存中表示如下:typedefstructlfs_mdir{lfs_block_tpair[2];//元数据对块指针uint32_trev;//修订计数//当前在元数据块中的偏移量//用于commit和fetch相关函数//作为起始偏移量传入,写入后的偏移量保存在末尾lfs_off_toff;//entrytag,用来记录当前的ptag//ptag用于commit过程计算XORtag,计算CRC等,看commit机制和tag遍历//fetch时,fetch到达一个commit时,计算出的ptag将存储在etag中//commit时,可以将ptag初始化为etaguint32_tetag;uint16_t计数;//目录中的属性数(文件数、子目录数)//表示是否写入下一次提交。//用于commit和fetch相关的函数,参见commit机制和tag遍历//当fetch结束时fetch还没有匹配到,就会将erased置为true//在commit函数中,只有当erased为true时,提交被执行布尔擦除;布尔拆分;//表示当前目录块后面是否有块,为false时表示结束//表示当前目录块中的最后一个TAIL//可能是HARDTAIL也可能是SOFTTAIL//跟fetch机制有关,目录遍历等lfs_block_ttail[2];//注意:off、etag、erased、tail与commit机制、tag遍历等相关,见后文}lfs_mdir_t;另外,在littlefs中,使用lfs_dir_t类型的数据结构记录在内存中打开的目录。参见littlefs中mlist的介绍。5.文件的标签存储在其父目录的元数据对中。文件又分为内联文件和大纲文件。刚创建文件时,它默认为内联文件。当文件大小超过block_size的1/8,或者超过文件缓存大小时,将重新分配为大纲文件。(1)inline文件具体的标签存储信息如下:REG:存储文件名idINLINESTRUCT:存储inline文件的数据(2)outline文件如上图所示,其中的数据littlefs中的大纲文件存储在跳跃表中。其中,CTZSTRUCT类型标签存储了文件大小和skipheader指针信息,skipheader指针指向文件末尾的block。每个块指向跳表中其他块的指针存储在块的块头中。跳转表中的块指针按照一个固定的规则分布:对于一个块,如果它是可分的,那么这个块包含一个指向该块的块指针。以block4为例:4可整除,则block4包含block3的block指针。4可整除,则block4包含block2的block指针。4可整除,则block4包含block的block指针0.根据这个规律,又因为块的大小是固定的,只要知道文件的偏移位置,就可以在跳转中得到偏移位置所在块的序号等信息table,howmanyblockpointersontheblock:跳转表中的块号:根据文件偏移量和块大小计算,相关函数为lfs_ctz_index。获取区块头中区块指针的个数:使用ctz命令,ctz(区块号)。(3)相关数据结构文件在内存中表示如下:typedefstructlfs_file{//下面4个成员与mlist相关,见后面mlist的介绍structlfs_file*next;uint16_tid;uint8_t类型;lfs_mdir_tm;结构lfs_ctz{lfs_block_t头;//跳过头指针,LFS_BLOCK_INLINE用于内联文件lfs_size_tsize;//文件大小,内联文件和大纲文件都使用这个记录}ctz;uint32_t标志;//INLINE、OUTLINE、DIRTY、WRITING和其他标志lfs_off_tpos;//文件lfs_block_t块的当前偏移字节数;//文件的当前块lfs_off_toff;//文件在当前块lfs_cache_t缓存中的偏移量;//文件缓存,用于读写等操作conststructlfs_file_config*cfg;//文件的其他配置信息}lfs_file_t;6、文件和目录在内存中的表示(mlist)在littlefs中,mlist用于记录打开的文件和目录,这些文件和目录存在于内存中。mlist主要用来遍历打开的文件和目录。(1)相关数据结构mlisttypedefstructlfs{...structlfs_mlist{structlfs_mlist*next;//下一个链表中的节点uint16_tid;//文件或目录在其父目录中的iduint8_ttype;//类型,表示是文件还是目录lfs_mdir_tm;//父目录元数据对信息}*mlist;...}lfs_t;打开的文件typedefstructlfs_file{structlfs_file*next;//下一个链表中的节点uint16_tid;//父目录下文件的iduint8_ttype;//类型,文件类型应该是LFS_REG_TYPElfs_mdir_tm;//父目录的元数据对信息//下面的成员见上面的存储结构...}lfs_file_t;打开目录typedefstructlfs_dir{structlfs_dir*next;//下一个链表中的节点uint16_tid;//父目录中目录的iduint8_t类型;//类型,目录应该是LFS_DIR_TYPElfs_mdir_tm;//父目录元数据对信息lfs_off_tpos;//当前目录或文件在父目录中的位置,.and..分别为0和1lfs_block_thead[2];//第一个元数据对的块号}lfs_dir_t;(2)记录打开的文件和目录由前面的数据结构决定,littlefs中的mlist是一个单向链表,记录了打开的文件和目录。mlist可以同时插入lfs_file_t和lfs_dir_t,lfs_mlist的前几个成员lfs_file_t和lfs_dir_t的结构是一样的。在打开文件的过程中打开文件时,将对应lfs_file_t类型的文件数据添加到mlist中:lfs_file_open(lfs_t*lfs,lfs_file_t*file,constchar*path,intflags)|->lfs_file_rawopen(lfs_t*lfs,lfs_file_t*file,|constchar*path,intflags)|->lfs_file_rawopencfg(lfs_t*lfs,lfs_file_t*file,|constchar*path,intflags,|conststructlfs_file_config*cfg)|->...||//转换文件加入mlist|->lfs_mlist_append(lfs,(structlfs_mlist*)file);||->...在文件关闭过程中关闭一个文件时,mlist会删除对应的文件:lfs_file_close(lfs_t*lfs,lfs_file_t*file)|->lfs_file_rawclose(lfs_t*lfs,lfs_file_t*file)|->lfs_mlist_remove(lfs,(结构lfs_mlist*)文件);||->...在打开目录的过程中打开命令时,将对应lfs_dir_t类型的目录添加数据到mlist中:lfs_dir_open(lfs_t*lfs,lfs_dir_t*dir,constchar*path)|->lfs_dir_rawopen(lfs_t*lfs,lfs_dir_t*dir,constchar*path)|->...||->lfs_mlist_append(lfs,(structlfs_mlist*)dir);在关闭目录的过程中关闭目录时,mlist中会删除对应的目录:lfs_dir_close(lfs_t*lfs,lfs_dir_t*dir)|->lfs_dir_rawclose(lfs_t*lfs,lfs_dir_t*dir)|->lfs_mlist_remove(lfs,(structlfs_mlist*)dir);littlefs的整体结构,包括superblocks、文件、目录等在磁盘上的存储,以及打开后文件和目录在内存中的表示,希望能给读者一个littlefs的大概印象。后续文章会继续分析littlefs的原理。了解更多开源知识,请访问:开源基础软件社区https://ost.51cto.com。
