引言

随着科技的倒退和网络技术的提高,计算机存储空间更加缓和,存储空间对文件系统的性能需要越来越大,大规模的数据增长为文件存储、非结构化数据存储提出了新的挑战。
对于许多物联网设施而言,领有一个小型且具备弹性的文件系统至关重要,littlefs文件系统应运而生。littlefs文件系统在2017年由Christopher Haster开发,遵循Apache 2.0协定,被利用在ARM的IoT设施Mbed操作系统。littlefs文件系统可能让嵌入式零碎在ROM和RAM资源无限的状况下,还具备文件系统根本的掉电复原、磨损平衡的性能。
littlefs是一种极简的嵌入式文件系统,适配于norflash,它所采纳的文件系统构造与运行机制,使得文件系统的存储构造更加紧凑,运行中对RAM的耗费更小。它的设计策略采纳了与传统“应用空间换工夫”齐全相同的“应用工夫换空间”的策略,尽管它极大地压缩了文件系统存储空间,然而运行时也减少了RAM的耗费,不可避免地带来了随机读写时IO性能的升高。
目前,OpenAtom OpenHarmony(以下简称“OpenHarmony”) liteos_m内核采纳了littlefs作为默认的文件系统。本文着重介绍了littlefs文件系统的存储构造,并依据对读写过程的剖析,解析引起littlefs文件系统随机读写IO性能瓶颈的根本原因,而后提出一些晋升littlefs随机读写IO性能优化策略。

littlefs文件系统构造

文件系统存储构造信息根本以SuperBlock为开始,而后寻找到文件系统根节点,再依据根节点,逐渐拓展成一个文件系统树形构造体。littlefs也与此相似,以SuperBlock和根目录为终点,构建了一个树形存储构造。不同的是littlefs的根("/")间接附加在SuperBlock之后,与其共享元数据对(metadata pair)。littlefs中目录或者文件都是以该根节点为终点,构建了与其余文件系统相似的树形构造。
littlefs文件系统树形存储构造如下:

                    图1 littlefs文件系统树形存储构造示意图

如图1所示,存储littlefs文件系统元数据的构造为元数据对,即两个互相轮转、互为表里的Block。存储SuperBlock的元数据对固定存储在block 0和block 1,并且文件系统根目录附加在SuperBlock的尾部,与SuperBlock共享元数据对。元数据的存储是以tag的格局存储在元数据对内,依照元数据的类型,将Tag分为标准文件、目录、用户数据、元数据对尾部指针等类型。littlefs借助于这些不同类型tag信息,将littlefs文件系统组织成结构紧凑的树形存储构造体。例如tail类型的tag能够将比拟大的目录构造应用多个元数据对存储,并且应用tail类型的tag将这些元数据对连接成一个单向的链表。而目录类型的tag则间接指向该目录的元数据对,例如"tag: dir_data"类型的tag指向目录"/data"的元数据对,而该元数据对中又能够蕴含子目录或者文件(Inline类型或者outline类型)。
littlefs目录存储构造
littlefs目录的援用为其父目录元数据对(metadata pair)内的一个dir类型的Tag,而其内容则占用一个或者多个元数据对。一个目录的元数据对内既能够蕴含子目录援用的Tag,也能够蕴含属于该目录下文件的Inline类型的Tag或者指向该文件的CTZ跳表的CTZ类型Tag指针。最终littlefs通过一层层目录或者文件的索引,组成了文件系统的树形存储构造。
littlefs文件存储形式
littlefs文件系统为极简的文件系统,应用最小的存储开销,同时实现对小文件(Bytes级别)和大文件(MB级别)的反对,对小于一个Block八分之一长度的文件,采纳Inline类型的形式存储,而大于或者等于Block八分之一长度的文件则采纳Outline的形式存储(CTZ Skip-list)。
 1.2.1 inline文件存储形式
Inline文件存储形式,如图2所示,行将文件内容与文件名称一起存储在其父目录的元数据对(metadata pair)内,一个Tag示意其名称,一个Tag示意其内容。

                    图2 littlefs Inline文件存储构造

 1.2.2 outline文件存储形式
Outline文件存储形式,如图3所示,文件其父目录的元数据对(metadata pair)内,一个Tag示意文件名称,另一个Tag为CTZ类型,其指向存储文件内容的链表头。

                   图3 littlefs Outline文件存储构造

CTZ跳表(CTZ skip-list)链表的特别之处是:
(1)CTZ跳表的头部指向链表的结尾;
(2)CTZ跳表内Block内蕴含一个以上的跳转指针。
若是应用惯例链表,存储文件前一个数据块蕴含指向后一个数据块指针,那么在文件追加或者批改内容的时候,则须要存储文件起始块到指标块的所有内容拷贝到新块内,并且更新对后一个数据块的指针。而若是采纳反向链表的形式,则在文件追加或者批改内容的时候,则只须要将存储文件指标块到链表结尾的块的所有内容拷贝到新块内,而后更新对后一个数据块内对前一个数据块指向的指针,这样对于文件追加模式能够缩小批改量。另外,为了放慢索引,采纳了跳表的形式,Block内蕴含一个以上的跳转指针,规定为:若一个数据块在CTZ skip-list链表内的索引值N能被 2^X整除的数,那么他就存在指向N – 2^X的指针,指针的数目为ctz(N)+1。如表1,对于block 2,蕴含了2个指针,别离指向block 0和block 1,其它块也是采纳雷同的规定。

表1 littlefs 块的skip-list链表计算样表

littlefs文件读写流程

以上章节针对littlefs文件系统构造进行了剖析,接下来开始探讨littlefs外部的运行机制,以读写流程为例,剖析littlefs随机读写的IO性能瓶颈。
须要提前理解的是,littlefs的文件只领有一个缓存,写时作为写缓存应用,读时作为读缓存应用。
littlefs文件读过程
以下图4是littlefs读文件的流程图,在读流程的开始先检测先前是否有对文件的写操作,即检测文件缓存是否作为写缓存。若是,则强制将缓存中的数据刷新到存储器,依据文件类型和拜访地位,或者间接从文件所在的元数据对读取,或者从存储文件内容的CTZ跳表内的块内读取,再将数据拷贝到用户缓存冲,并从存储器预读取数据将文件缓冲区填满。具体过程如下:

                     图4 littlefs文件系统读过程流程图

littlefs文件写过程
以下图5是littlefs写文件的流程图,在写流程的开始先检测先前是否有对文件的读操作,即检测文件缓存是否作为读缓存。若是,则革除缓存中的数据。若是APPEND类型的写操作,则间接减写地位定位到文件开端。若写地位超过文件长度,阐明文件结尾与写地位间存在空洞,则应用0填充文件中的空洞。对应Inline类型文件,若揣测到写后,文件长度超过了阈值,则将文件转成Outline类型。对于Outline类型的文件,若是批改文件的内容,则须要申请新块,并将指标块内拜访地位之前的所有内容都拷贝到新块,将buffer中的用户数据写到缓冲区或者刷新到存储器。
留神:写后并没有立即更新Inline文件的commit,或者更新Outline文件的CTZ跳表,这些操作被提早在文件敞开或者缓冲区再次作为读缓存的时候强制文件刷新时更新。

                     图5 littlefs文件系统写过程流程图

littlefs文件随机读写IO性能瓶颈剖析
littlefs文件只有一个缓冲区,为读写复用。依据littlefs运行机制,若是对文件先读后写,那么仅须要间接将缓冲区的数据清空,而后申请一个新块将指标块内拜访地位间接的数据拷贝到新块中,而后写数据到新块。若是先写后读,那么须要将数据刷新到存储器,同时更新文件的CTZ跳表。在这个过程中,不仅波及到刷新数据到存储器,而且波及到调配新块替换指标块之后的所有块从而更新CTZ跳表,呈现屡次费时的擦除块动作。在随机读写的过程中,频繁产生读写切换,也就频繁地产生申请新块、擦除新块(十分费时)、数据搬移等等动作,重大地影响了IO性能。

littlefs读写IO性能优化策略

由“2.3 littlefs文件随机读写IO性能瓶颈剖析”章节形容可知,影响littlefs文件随机读写IO性能的次要起因是文件只有一个的缓存且被读写复用,造成在读写切换的过程中频繁地产生文件刷新,申请新块,而后执行费时的块擦除,再将CTZ跳表上块内的block内容搬移到新块,进而更新CTZ跳表,这重大影响了随机读写IO的性能。
所以,在RAM空间容许的状况下,能够思考“应用空间换工夫”的策略,适当地减少文件缓存的数量,使一个文件领有多个缓冲区,而这些缓冲区对应着一个Block的大小,在肯定的条件下一次刷新一个Block,从而防止过多的数据搬移。另外,littlefs的策略是“应用工夫换空间”,然而每个文件都领有一个缓冲区显著节约空间。因为在一段时间内,只会有肯定数量的文件被执行读或者写,所以能够思考建设一个领有肯定数量的缓存池,使缓存在文件间共享。

                           图6 littlefs优化策略

优化的策略如图6所示,littlefs文件缓存池为一个双向链表fc_pool,缓存池随着被关上文件的个数的增长而缩短,直到用户设置的最大限度;缓存池随着文件的敞开而逐步缩减。
每个缓存挂载在fc_pool缓存池双向链表上,当缓存被写或者被读时,则将缓存移到链表结尾,那么缓存池链表开端的缓存则为待老化的缓存,能够优先被抉择回收。
在申请缓存时,优先从缓冲池链表开端抉择闲暇缓存;若无闲暇缓存,则思考抢占读缓存;若缓存池既没有闲暇缓存也没有读缓存,在缓存池长度没有达到优化限度的状况下,则创立新缓存,而后将新缓冲增加到链表头;若缓存池既没有闲暇缓存也没有读缓存,并且缓存池长度曾经达到用户限度,那么就须要从链表开端抢占一个写缓存,并强制占有该缓存的文件执行刷新,进而实现抢占。
在文件被敞开或者刷新时,被动开释缓存到缓存池,挂载在双向链表的开端。
应用上述策略对文件缓存进行优化,能够在肯定水平上缩小因更新文件内容而执行的存储器块擦除动作,从而减少随机读写的IO性能,也间接地缩短了NorFlash的寿命。

总结

通过本文的解说,置信大家对于littlefs文件系统有了较为全面的理解。总的来说,littlefs是一种极简的文件系统,实现了文件系统根本的数据缓存、掉电复原、磨损平衡等性能,在资源绝对富裕的环境中,开发者们能够对其运行机制甚至存储构造进行“应用空间换工夫”的优化策略,晋升读写的IO性能。学会无效地利用文件系统往往能起到事倍功半的作用,心愿开发者可能将所学常识无效利用到将来的开发工作中,从而进步开发工作的效率。