首页 > 其他分享 ># littlefs原理分析#[四]目录操作

# littlefs原理分析#[四]目录操作

时间:2022-11-15 12:32:11浏览次数:74  
标签:littlefs lfs tag LFS commit 原理 目录 dir

作者:蒋卫峰 李涛

前言

前面的三篇文章中分别介绍了littlefs的整体结构、commit机制和fetch操作。在介绍了 littlefs中元数据的读取和写入过程之后,这篇以及接下来的文章将开始对littlefs中的具体文件、目录操作和策略等进行介绍。

本文主要对目录的创建、删除和移动操作进行总结,包括目录操作的过程、操作之后目录的链接方式变化、目录操作中的一些特殊处理等。目录的读取、写入和遍历操作实际上在前面的文章中以及介绍过了,目录的读写实际上就是元数据的读写操作,目录的遍历实际上就是fetch tail的操作。

1. 目录创建

1.1 commit过程

目录创建会进行两次commit。第一次commit时,是在新创建的目录元数据中插入指向父目录中末尾目录的块指针;第二次commit时,是在父目录元数据中插入新创建目录的块指针。

目录创建的过程是原子性的,只有第二次commit完成,父目录元数据中才会记录新创建的目录。

commit过程如下:

  1. 创建新目录。其中,SOFTTAIL指向父目录元数据中最后一个有效的SOFTTAIL,如果父目录中没有有效的SOFTTAIL,则SOFTTAIL为空。

littlefsdirectorycreate new.drawio.png

  1. 父目录插入新目录。其中,SOFTTAIL指向子目录。

littlefsdirectorycreate in parent.drawio.png

1.2 链接方式变化

创建目录实际上是在parent->dir tail的单链表直接插入新目录,变成parent->new dir->dir tail

例如:向目录C中创建目录D,大致链接方式变化如下:

littlefsdirectorycreate.drawio.png

注:SOFTTAIL用箭头进行链接,只有SOFTTAIL为目录最后的TAIL时用实线表示。

用fetch遍历目录顺序的变化如下:

  • 前:A->C->B

  • 后:A->C->D->B

1.3 相关函数分析

lfs_mkdir(lfs_t *lfs, const char *path)
|-> lfs_rawmkdir(lfs_t *lfs, const char *path)
    |   // 1. 查找路径和父目录
    |-> lfs_dir_find(lfs, &cwd.m, &path, &id);
    |
    |   // 2. 分配新目录
    |-> lfs_dir_alloc(lfs, &dir);
    |
    |   // 3. 在新目录中进行commit
    |   // 存储一个指向父目录末尾目录的块指针
    |-> lfs_dir_commit(lfs, &dir, LFS_MKATTRS(
    |       {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), pred.tail}));
    |
    |   // 4. 在父目录中进行commit
    |   // 将新目录插入父目录
    |-> lfs_dir_commit(lfs, &cwd.m, LFS_MKATTRS(
    |       {LFS_MKTAG(LFS_TYPE_CREATE, id, 0), NULL},
    |       {LFS_MKTAG(LFS_TYPE_DIR, id, nlen), path},
    |       {LFS_MKTAG(LFS_TYPE_DIRSTRUCT, id, 8), dir.pair},
    |       {LFS_MKTAG_IF(!cwd.m.split,
    |           LFS_TYPE_SOFTTAIL, 0x3ff, 8), dir.pair}));

2. 目录删除

2.1 commit过程

目录删除的过程分为两个步骤:

  1. 在其父目录中commit一个DELETE类型的tag,表示从父目录中将目录删除。该步骤与文件删除的过程类似。如下图:

littlefsdirectoryremove from parent.drawio.png 2. 在被删除目录的前继目录(其tail指向被删除的目录)中commit新的SOFTTAIL类型的tag,表示断开与将要删除目录的链接。新的SOFTTAIL指向被删除目录的后继目录(被删除目录tail指向的目录)。如下图:

littlefsdirectoryremove from pred.drawio.png

注:上图的commit中还有一个MOVESTATE类型的tag,该tag与gstate和orphan目录有关,见后面目录删除和移动操作中异常情况的分析。

2.2 链接方式变化

例如,删除目录B,其链接方式变化如下:

littlefsdirectoryremove.drawio.png 用fetch遍历目录顺序的变化如下:

  • 前:parent->C->B->A

  • 后:parent->C->A

2.3 相关函数分析

lfs_remove(lfs_t *lfs, const char *path)
|-> lfs_rawremove(lfs_t *lfs, const char *path)
    |   // 1. 查找路径和父目录
    |-> lfs_dir_find(lfs, &cwd, &path, NULL);
    |
    |   // 2. 在父目录中commit一个DELETE tag
    |-> lfs_dir_commit(lfs, &cwd, LFS_MKATTRS(
    |       {LFS_MKTAG(LFS_TYPE_DELETE, lfs_tag_id(tag), 0), NULL}));
    |
    |   // 3. 找到删除目录的前继目录
    |-> lfs_fs_pred(lfs, dir.m.pair, &cwd);
    |
    |   // 4. 断开删除目录与前继目录的链接
    |-> lfs_dir_drop(lfs, &cwd, &dir.m);

2.4 orphan目录

目录删除的过程时,有可能因为掉电等原因产生一个中间状态,即第一次commit成功,而第二次commit失败。例如,删除目录B,但只完成了第一步:

littlefsdirectoryorphan in remove.drawio.png

此时目录B就成了orphan目录。

为了解决这个问题,littlefs中采用了gstate机制来进行异常状态的记录和检查。

2.4.1 gstate机制简介

gstate是littlefs内存中维护的一组全局状态,同时可作为MOVESTATE tag存储于磁盘中。简而言之,gstate机制通过如下方法记录和检查异常状态:

  • 当进行如目录删除这样可能因掉电导致异常状态的操作时,会将内存中维护的gstate在commit前标记为异常状态。因为这样可以使得commit过程中将异常状态作为MOVESTATE tag写入磁盘。(lfs_dir_commit函数会检查内存中的gstate变量,并根据gstate增加写入MOVESTATE tag)

  • 当读取磁盘元数据时,根据MOVESTATE tag中的信息,可以知道有无异常情况发生、异常情况是否解决等信息。这样检查到异常状态后,就可以根据具体情况执行修复操作。

    gstate检查时是通过异或操作计算所有MOVESTATE tag中的值,结果不为0则表示异常。

2.4.2 orphan状态的记录和修复

当进行目录删除操作时,磁盘中orphan状态的记录和修复步骤如下:

  1. 第一次commit,从父目录中将目录删除。此时记录MOVESTATE tag于父目录的元数据中。链接方式变化如下图:

littlefsdirectoryorphan in remove.drawio.png

  1. 第二次commit,这次即可能发生在第一次commit后,也可能是掉电后通过检查gstate发现异常后的修复操作。此时记录MOVESTATE tag于被删除目录的前继目录的元数据中。该MOVESTATE tag数据与在父目录元数据中记录的值相对应,这样gstate检查时进行异或计算就可与前面记录的MOVESTATE tag进行抵消,表示异常已解决。链接方式变化如下图:

littlefsdirectoryfix orphan.drawio.png

当进行目录删除操作时,内存gstate中orphan状态的记录和恢复步骤如下:

  1. 第一次commit前,标记gstate为orphan状态。这样第一次commit时就可以记录MOVESTATE tag。

  2. 第一次commit后,还原gstate

记录orphan状态相关代码分析如下:

lfs_remove(lfs_t *lfs, const char *path)
|-> lfs_rawremove(lfs_t *lfs, const char *path)
 |-> ...
 |
 |   // 在第一次commit前记录gstate
 |-> lfs_fs_preporphans(lfs, +1);
 |
 |   // 第一次commit,会记录MOVESTATE tag
 |-> lfs_dir_commit(lfs, &cwd, LFS_MKATTRS(
 |       {LFS_MKTAG(LFS_TYPE_DELETE, lfs_tag_id(tag), 0), NULL}));
 |
 |   // 在第一次commit后恢复gstate
 |-> lfs_fs_preporphans(lfs, -1);
 |
 |-> ...

修复orphan状态相关代码分析如下:

// 该函数在mount后进行检查时被调用
lfs_fs_deorphan(lfs_t *lfs)
|   // 遍历文件系统
|-> while (...) {
    |   // 1. 查找当前orphan目录的父目录
    |-> lfs_stag_t tag = lfs_fs_parent(lfs, pdir.tail, &parent);
    |
    |   // 2. 如果当前目录没有父目录,则当前目录为orphan目录,进行恢复
    |-> if (tag == LFS_ERR_NOENT) {
    |       lfs_dir_drop(lfs, &pdir, &dir);
            |   // 2.1 检查目录中的异常状态并记录于gstate
            |-> lfs_dir_getgstate(lfs, tail, &lfs->gdelta);
            |
            |   // 2.2 commit新的TAIL类型tag,完成目录删除的第二次commit操作
            |   // 同时写入MOVESTATE tag
            |-> lfs_dir_commit(lfs, dir, LFS_MKATTRS(
            |        {LFS_MKTAG(LFS_TYPE_TAIL + tail->split, 0x3ff, 8), tail->tail}));
    |   }
|   }

3. 目录移动

3.1 commit过程

littlefs中将目录或文件从旧的父目录移动到新的父目录下主要经过两个步骤:

  1. 在新父目录中commit,创建目录并指向将要移动的目录。其中,如果新父目录下已经存在一个同名的文件或目录,需要先将其删除。值得注意的是,与创建目录时不同,这里父目录下并没有commit一个SOFTTAIL类型的tag。如下图:

littlefsdirectorymove in new.drawio.png

  1. 在旧父目录中commit,删除要移动的目录。如下图:

littlefsdirectoryremove from parent.drawio.png

注:上图的commit中还有一个MOVESTATE类型的tag,该tag与gstate和move状态有关,见后面move状态相关分析。

3.2 链接方式变化

在目录的移动过程中,新父目录中没有commit一个新的SOFTTAIL,旧父目录中也没有commit一个新的SOFTTAIL覆盖原来的SOFTTAIL。由于链接方式和遍历顺序只与TAIL类型的tag有关,因此目录移动后,其链接方式并没有变化,只是存储结构发生了变化,遍历时目录的顺序仍然不变。

3.3 相关函数分析

lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath)
|-> lfs_rawrename(lfs_t *lfs, const char *oldpath, const char *newpath)
    |   // 1. 查找旧路径和旧父目录
    |-> lfs_stag_t oldtag = lfs_dir_find(lfs, &oldcwd, &oldpath, NULL);
    |
    |   // 2. 查找新路径和新父目录
    |-> lfs_stag_t prevtag = lfs_dir_find(lfs, &newcwd, &newpath, &newid);
    |
    |   // 3. 在新父目录中进行commit
    |   // 3.1 如果新路径下已经存在一个文件或目录,则将其删除
    |   // 3.2 在新父目录下创建将要移动的目录
    |-> lfs_dir_commit(lfs, &newcwd, LFS_MKATTRS(
    |       {LFS_MKTAG_IF(prevtag != LFS_ERR_NOENT,
    |           LFS_TYPE_DELETE, newid, 0), NULL},
    |       {LFS_MKTAG(LFS_TYPE_CREATE, newid, 0), NULL},
    |       {LFS_MKTAG(lfs_tag_type3(oldtag), newid, strlen(newpath)), newpath},
    |       {LFS_MKTAG(LFS_FROM_MOVE, newid, lfs_tag_id(oldtag)), &oldcwd},
    |       {LFS_MKTAG_IF(samepair,
    |           LFS_TYPE_DELETE, newoldid, 0), NULL}));
    |
    |   // 4. 在旧父目录中删除被移动目录
    |-> lfs_dir_commit(lfs, &oldcwd, LFS_MKATTRS(
    |           {LFS_MKTAG(LFS_TYPE_DELETE, lfs_tag_id(oldtag), 0), NULL})

3.4 move状态

与目录删除过程中类似,在目录移动的过程中,当第一次commit成功,但第二次commit因为掉电等原因未完成时,也产生一个中间状态。例如,将目录C从A移动到B:

littlefsdirectorymove problem.drawio.png

注:上图中实线只表示存储结构关系。

此时目录B标记为move状态。同样的,move状态也是通过gstate机制进行检查和修复。

3.4.1 move状态的记录和修复

当进行目录移动操作时,与orphan状态的记录和恢复类似,磁盘中orphan状态的记录和修复步骤如下:

  1. 第一次commit,在新父目录下创建目录,此时记录MOVESTATE tag于新父目录的元数据中。存储结构变化如下图:

littlefsdirectorymove problem.drawio.png 2. 第二次commit,从旧父目录中删除目录,此时记录MOVESTATE tag于旧目录的元数据中。类似的,这次即可能发生在第一次commit后,也可能是掉电后通过检查gstate发现异常后的修复操作。链接方式变化如下图: littlefsdirectoryfix move.drawio.png 当进行目录删除操作时,内存gstate中move状态的记录和恢复步骤如下:

  1. 第一次commit前,标记gstate为move状态。这样第一次commit时就可以记录MOVESTATE tag。

  2. 第一次commit后,还原gstate

记录move状态相关代码分析如下:

lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath)
|-> lfs_rawrename(lfs_t *lfs, const char *oldpath, const char *newpath)
    |-> ...
    |
    |   // 1. 在第一次commit前记录move状态到gstate
    |-> lfs_fs_prepmove(lfs, newoldid, oldcwd.pair);
    |
    |   // 2. 在新父目录中进行commit
    |-> lfs_dir_commit(lfs, &newcwd, LFS_MKATTRS(
    |       {LFS_MKTAG_IF(prevtag != LFS_ERR_NOENT,
    |           LFS_TYPE_DELETE, newid, 0), NULL},
    |       {LFS_MKTAG(LFS_TYPE_CREATE, newid, 0), NULL},
    |       {LFS_MKTAG(lfs_tag_type3(oldtag), newid, strlen(newpath)), newpath},
    |       {LFS_MKTAG(LFS_FROM_MOVE, newid, lfs_tag_id(oldtag)), &oldcwd},
    |       {LFS_MKTAG_IF(samepair,
    |           LFS_TYPE_DELETE, newoldid, 0), NULL}));
    |
    |   // 3. 恢复gstate中的move状态
    |-> lfs_fs_prepmove(lfs, 0x3ff, NULL);
    |
    |   // 4. 在旧父目录中删除被移动目录
    |-> lfs_dir_commit(lfs, &oldcwd, LFS_MKATTRS(
    |           {LFS_MKTAG(LFS_TYPE_DELETE, lfs_tag_id(oldtag), 0), NULL})

修复move状态相关代码分析如下:

// 该函数在mount后进行检查时被调用
lfs_fs_demove(lfs_t *lfs)
|-> ...
|
|   // 在新目录中删除被移动目录的id,并恢复gstate
|-> uint16_t moveid = lfs_tag_id(lfs->gdisk.tag);
|   lfs_fs_prepmove(lfs, 0x3ff, NULL);
|   lfs_dir_commit(lfs, &movedir, LFS_MKATTRS(
|           {LFS_MKTAG(LFS_TYPE_DELETE, moveid, 0), NULL}));

总结

本文对目录创建、目录删除和目录移动操作进行了分析,包括目录操作的过程、操作之后目录的链接方式变化、目录操作中的一些特殊处理等内容。接下来的文章将会介绍littlefs系统的文件相关操作。

更多原创内容请关注:深开鸿技术团队

入门到精通、技巧到案例,系统化分享OpenHarmony开发技术,欢迎投稿和订阅,让我们一起携手前行共建生态。

本文作者:深开鸿Kaihong

想了解更多关于开源的内容,请访问:​

​51CTO 开源基础软件社区​

​https://ost.51cto.com/#bkwz​

标签:littlefs,lfs,tag,LFS,commit,原理,目录,dir
From: https://blog.51cto.com/harmonyos/5848558

相关文章

  • shell 获取 目录名 当前目录名
    FourwaystoextractthecurrentdirectorynameBy  SergioGonzalezDuran onNovember06,2007(9:00:00AM)   Whenyou'reprogrammingashellscript,y......
  • 异或操作的加密,解密,原理。
    异或加密异或加密是一种很简单的加密算法。原理:根据异或的运算规则,在二进制中,相同为0,不同为1。且:某个数与0异或等于这个数的本身,与1异或等于这个数的相反。特性:异或运算......
  • Linux-文件和目录常用命令-笔记
    目标查看目录内容​​ls​​切换目录​​cd​​创建和删除操作​​touch​​​​rm​​​​mkdir​​拷贝和移动文件​​cp​​​​mv​​查看文件内容​​cat​​​​more......
  • VRRP原理和实战
    一、VRRP基本概述·VRRP能够在不改变组网的情况中,将多台路由器虚拟成一个虚拟路由器,通过配置虚拟路由器的IP地址为默认网关,实现网关的备份。·协议版本:VRRPv2(常用)和VRR......
  • 详解React的Transition工作原理原理
    Transition使用姿势Transition是react18引入的新概念,用来区分紧急和非紧急的更新。紧急的更新,指的是一些直接的用户交互,如输入、点击等;非紧急的更新,指的是UI界面......
  • 经常被问到的react-router实现原理详解
    在单页面应用如日中天发展的过程中,备受关注的少了前端路由。而且还经常会被xxx面试官问到,什么是前端路由,它的原理的是什么,它是怎么实现,跳转不刷新页面的...一大堆为什么,......
  • vue源码分析-响应式系统工作原理
    上一章,我们讲到了Vue初始化做的一些操作,那么我们这一章来讲一个Vue核心概念响应式系统。我们先来看一下官方对深入响应式系统的解释:当你把一个普通的JavaScript对象传......
  • Java注解与原理分析
    目录一、注解基础二、注解原理三、常用注解1、JDK注解2、Lombok注解四、自定义注解1、同步控制2、类型引擎五、参考源码使用的太多,被忽略的理所当然;一、注解基础注解......
  • overflow:hidden解决高度塌陷原理
    https://www.jianshu.com/p/4473bffef8a0?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation我们大家理解的overflo......
  • MapReduce框架原理
    1MapReduce工作流程1)流程示意图2)流程详解上面的流程是整个mapreduce最全工作流程,但是shuffle过程只是从第7步开始到第16步结束,具体shuffle过程详解,如下:1)maptask收集我们的m......