引言
随着科技的倒退和网络技术的提高,计算机存储空间更加缓和,存储空间对文件系统的性能需要越来越大,大规模的数据增长为文件存储、非结构化数据存储提出了新的挑战。
对于许多物联网设施而言,领有一个小型且具备弹性的文件系统至关重要,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 性能。学会无效地利用文件系统往往能起到事倍功半的作用,心愿开发者可能将所学常识无效利用到将来的开发工作中,从而进步开发工作的效率。