了解更多开源请访问:开源基础软件社区https://ost.51cto.com前言在littlefs原理解析一文中,第一篇介绍了littlefs的总体结构,第二篇介绍了littlefs中记录元数据的方式,即commit机制。本文(littlefs原理分析:(3)fetch操作)的主要内容是介绍littlefs中的fetch操作。这部分还是和metadata有关,不过commit过程是写metadata,fetch操作是读metadata。commit时记录超级块、文件、目录的创建、删除等操作。而如何从这些记录中获取需要的信息(比如打开一个文件,需要从其父目录的元数据中获取该文件的块指针),就是通过遍历元数据中的标签来完成的。fetch操作其实就是遍历元数据中的标签。它的作用是遍历指定NAME类型的标签,比如搜索文件和目录,获取文件和目录id等数据。在提交过程中写入标签也是通过标签遍历完成的。不同的是,fetch操作一般只用于读取commit中记录的数据,而commit过程中调用的lfs_dir_traverse函数一般只用于写入。1、fetch函数说明fetch操作主要用于littlefs中读取文件和目录,以及遍历目录。下面结合具体的文件和目录操作,介绍相应的commit过程以及如何结合commit后的fetch操作获取相应的数据:1.文件和目录的读取是在知道文件的元数据块的情况下parentdirectory,文件和目录的读取分为以下两步:遍历找到REG(DIR)类型的标签,根据文件(目录)获取文件(目录)名和文件(目录)idid并再次遍历标签找到对应的数据标签,即INLINESTRUCT或CTZSTRUCT(DIRSTRUCT)fetch操作完成第一步。下面结合具体案例描述fetch过程:(1)文件创建后,文件刚创建时是inline文件:此时遍历找到符合文件路径的文件tagREG类型的可以获取文件名和文件id,然后通过其文件id再次遍历tag,获取文件数据。例如,打开新创建的文件:lfs_file_rawopencfg(lfs_t*lfs,lfs_file_t*file,|constchar*path,intflags,|conststructlfs_file_config*cfg)|//1.根据路径找到对应的tag和id|//此时搜索到的tag是REG类型,找到的文件id存放在file->id|->lfs_stag_ttag=lfs_dir_find(lfs,&file->m,&path,&file->id);||//2.根据文件id找到tag对应的文件数据|//此时搜索STRUCT类型标签,包括内联文件和大纲文件|->tag=lfs_dir_get(lfs,&file->m,LFS_MKTAG(0x700,0x3ff,0),|LFS_MKTAG(LFS_TYPE_STRUCT,file->id,8),&file->ctz);||->...(2)删除文件后,如果删除新创建的文件:删除文件或目录时,会写入DELETE和CRC类型标签。其中,CREATE和DELETE类型的标签中的id存放的是对应创建或删除的文件或目录的id。如果此时再次打开文件,遍历标签时,因为检查了包含对应id的DELETE类型标签,所以会返回失败。在遍历获取文件和目录数据的相关函数中,对DELETE类型标签的相关检测分析如下://该函数用于遍历时查找匹配的标签,比如查找匹配文件对应的标签打开文件时lfs_stag_tlfs_dir_fetchmatch(lfs_t*lfs,|lfs_mdir_t*dir,constlfs_block_tpair[2],|lfs_tag_tfmask,lfs_tag_tftag,uint16_t*id,|int(*cb)(void*data,lfs_tag_ttag,constvoid*缓冲区),void*data)|->...||//如果当前遍历的标签类型为DELETE,其id与tempbesttag中的id相同|//将tempbesttag设置为无效。|->if(tag==(LFS_MKTAG(LFS_TYPE_DELETE,0,0)||(LFS_MKTAG(0,0x3ff,0)&tempbesttag))){|tempbesttag|=0x80000000;|}|->...(3)其他目录的创建,删除后的读取过程和文件的创建,类似的目录删除后,文件的移动实际上是创建过程和删除过程的结合,先在新的父目录下创建,然后在旧的父目录中删除。阅读过程也类似。2、目录遍历littlefs中的目录遍历是通过TAIL类型标签进行的。TAIL类型的tag在littlefs的存储结构中已经解释过了,分为SOFTTAIL和HARDTAIL。每个目录对应的元数据块可以存储指向其他目录的SOFTTAIL或者指向该目录下的元数据块的HARDTAIL。如何从一个目录跳转到其后继目录,实际上是通过fetchtail操作来实现的。本节重点介绍fetchtail的过程,即在知道其父目录的情况下如何跳转到后续目录。目录的链接方法见以下文章。(1)fetchtail会在父目录中记录其子目录的创建信息,并且会有对应的SOFTTAIL指向该子目录。具体目录操作后目录的链接方法见下文。这里仅以一个父目录包含多个子目录为例来说明fetchtail的过程。下图中,父目录有两个元数据对,包括指向子目录A、B、C的SOFTTAIL:在fetch操作对应的函数lfs_dir_fetchmatch中,可以看到TAIL类型标签更新了参数dir->tail,保存TAIL指向的元数据对块。在littlefs中,一般是fetch的最后一个TAIL,也就是上图中的HARDTAIL和目录C。littlefs目录链接相关的机制保证了这个遍历可以从根目录开始遍历所有目录。相关函数是lfs_dir_fetch:intlfs_dir_fetch(lfs_t*lfs,lfs_mdir_t*dir,//fetchtail结果存放在dir->tailconstlfs_block_tpair[2]//父目录元数据对的block){//调用lfs_dir_fetchmatch实现//其中fmask和ftag为-1,表示不匹配,会fetch到元数据末尾return(int)lfs_dir_fetchmatch(lfs,dir,pair,(lfs_tag_t)-1,(lfs_tag_t)-1,NULL,NULL,NULL);}因为TAIL类型标签分为SOFTTAIL和HARDTAIL,dir->tail中保存的尾巴可能是HARDTAIL也可能是SOFTTAIL。以上图为例:第一次调用lfs_dir_fetch,dir->tail中保存的tail为HARDTAIL,指向目录的下一个元数据块。目录C(2)在fetch的下一个目录上面的部分中,lfs_dir_fetch可以fetchHARDTAIL或fetchSOFTTAIL。fetch过程中,dir->split成员会同时更新。该成员表示当前目录块是否已经被细分。当dir->split为false时,表示在当前目录块的末尾。所以在littlefs中常用下面的方法来获取下一个目录:lfs_mdir_tm=dir.m;while(m.split){lfs_dir_fetch(lfs,&m,m.tail);}(3)目录删除移动后,如果SOFTTAIL对应目录已经删除移动,应该有DELETE类型的标签在CREATE标签之后。但是DELETE类型标签对fetchtail过程没有影响。目录的链接方式只与SOFTTAIL类型标签有关。目录删除移动后具体链接变化见下文。2.fetch过程fetch操作的相关函数是lfs_dir_fetchmatch,它遍历tag并匹配,从中获取数据。这个函数的定义在上一节中已经提到。函数匹配到标签后,会执行相应的回调函数。回调函数一般是对标签和对应的数据进行进一步的比对和匹配。流程如下:代码分析如下:staticlfs_stag_tlfs_dir_fetchmatch(lfs_t*lfs,|lfs_mdir_t*dir,constlfs_block_tpair[2],|lfs_tag_tfmask,lfs_tag_tftag,uint16_t*id,|int(*cb)(void*data,lfs_tag_t标签,constvoid*buffer),void*data){|//保存与besttag匹配最好的结果|->lfs_stag_tbesttag=-1;||->...||//每次准备暂存匹配中的最佳匹配结果和更新等变量|->uint16_ttempcount=0;|lfs_block_ttemptail[2]={LFS_BLOCK_NULL,LFS_BLOCK_NULL};|booltempsplit=false;|lfs_stag_ttempbesttag=besttag;||处理|->while(true){|//1.从磁盘解析下一个标签,计算标签的CRC|->lfs_bd_read(lfs,|NULL,&lfs->rcache,lfs->cfg->block_size,|dir->pair[0],关闭,&tag,sizeof(tag));|crc=lfs_crc(crc,&tag,sizeof(tag));|tag=lfs_frombe32(tag)^ptag;||//2.检查Boundary,不在范围内或标签无效时跳出循环|->if(!lfs_tag_isvalid(tag)){|dir->erased=(lfs_tag_type1(ptag)==LFS_TYPE_校验码&&|dir->off%lfs->cfg->prog_size==0);|休息;|}elseif(off+lfs_tag_dsize(tag)>lfs->cfg->block_size){|目录->擦除=假;|休息;|}||//3.如果tag是CRC,检查CRC并更新最佳匹配结果|->if(lfs_tag_type1(tag)==LFS_TYPE_CRC){|//3.1检查CRC|->uint32_tdcrc;|lfs_bd_read(lfs,|NULL,&lfs->rcache,lfs->cfg->block_size,|dir->pair[0],off+sizeof(tag),&dcrc,sizeof(dcrc));|如果(crc!=dcrc){|目录->擦除=假;|休息;|}||//3.2更新最佳匹配结果等|->besttag=tempbesttag;|dir->off=off+lfs_tag_dsize(标签)|目录->etag=ptag;|目录->计数=临时计数;|dir->tail[0]=temptail[0];|dir->tail[1]=temptail[1];|目录->拆分=临时拆分;||//3.3重置CRC|->crc=0xffffffff;|继续;}||//4.计算条目的CRC|->for(lfs_off_tj=sizeof(tag);j
