1.littlefs设计目的
littlefs 最初是作为一个实验而构建的,目的是在微控制器的环境中了解文件系统设计。目的是:构建一个在不使用无限制内存的情况下对电源丢失和闪存磨损具有弹性的文件系统。
这对嵌入式文件系统littlefs提出了三个主要要求:
- 断电恢复能力——在这些系统上,电力可能随时中断。如果断电损坏了任何持久数据结构,可能会导致设备无法恢复。嵌入式文件系统必须设计成能够在任何写入操作期间从断电情况中恢复。
- 磨损均衡 - 写入闪存具有破坏性。如果文件系统反复写入同一区块,最终该区块将磨损。不考虑磨损的文件系统很容易耗尽用于存储频繁更新的元数据的区块,并导致设备过早损坏。
- 有限的 RAM/ROM——如果上述要求还不够,这些系统的内存量也非常有限。这使得许多现有的文件系统设计无法实现,因为这些设计可能依赖相对大量的 RAM 来临时存储文件系统元数据。
因此,这也是我们此次分析的目的,也就是它是用什么方式和代码实现来完成上述的特性。我们聚焦如下: - 磨损均衡:
- 目的: 均衡闪存中每个块的擦写次数,以延长存储介质的使用寿命。
- 关键特性:
- 跟踪每个块的擦写次数。
- 动态分配新的块来替换频繁使用的块。
- 保证数据迁移时的一致性。
- 掉电安全:
- 目的: 在突然断电的情况下,确保文件系统的数据一致性。
- 关键特性:
- 日志结构设计,确保所有更改都是原子提交。
- 写时复制(Copy-on-Write, CoW)技术,避免部分更新。
- 元数据和用户数据分离,减少损坏范围。
- 自动恢复机制,在启动时检查并回滚未完成的事务。
- 配置问题:
- 目的:针对不同的硬件和开发环境,给出配置建议。
2.现有设计
目前已经有许多不同的文件系统,它们经常相互共享和借鉴特性。如果我们考虑掉电恢复能力和磨损均衡,我们可以将这些缩小到少数几种设计。
1.块文件系统(FAT和ext2):
首先,我们有非弹性的、基于块的文件系统,例如 FAT 和 ext2。这些是最早的文件系统设计,通常也是最简单的。在这里,存储被划分为块,每个文件存储在一组块中。如果不进行修改,这些文件系统不具有掉电恢复能力,因此更新文件就像重写块一样简单。
由于它们的简单性,这些文件系统通常是最快且最小的。然而,缺乏电源弹性不是很好,并且存储位置和数据的绑定关系消除了文件系统管理磨损的能力。
2.日志文件系统(JFFS、YAFFS和SPIFFS):
在一个完全不同的方向上,我们有日志文件系统,例如JFFS、YAFFS和SPIFFS,存储位置不与一块数据绑定,相反,整个存储用于一个循环日志,每次对文件系统进行更改时都会追加到该日志中。写入会追加新的更改,而读取需要遍历日志以重建文件。一些日志文件系统会缓存文件以避免读取成本,但这是以 RAM 为代价的。
日志文件系统非常优雅。通过校验和,我们可以轻松检测到电源故障,并通过忽略失败的追加操作回退到先前的状态。如果这还不够好,它们的循环性质意味着日志文件系统可以完美地在存储设备上
均匀分布磨损。
3.基于块的文件系统与日志文件系统结合(ext4、NTFS):
常见的文件系统类型还有:日志文件系统是当基于块的文件系统与日志文件系统的结合。ext4和NTFS是很好的例子。在这里,我们采用一个普通的基于块的文件系统,并添加一个有界日志,在每次更改发生之前我们都会在其中记录下来。
这种文件系统兼具两者的优点。性能可以像基于块的文件系统一样快(尽管更新日志确实有一点成本),并且对日志的原子更新允许文件系统在断电的情况下恢复。
但是,日志文件系统有几个问题。它们相当复杂,因为实际上有两个文件系统并行运行,这带来了代码大小的成本。它们也不能防止磨损,因为存储位置和数据之间有很强的关系。
4.写时复制(COW)文件系统(btrfs和ZFS):
写时复制文件系统与其他基于块的文件系统非常相似,但不是就地更新块,而是通过创建带有更改的副本并将对旧块的任何引用替换为我们的新块来执行所有更新。这会递归地将我们所有的问题向上推,直到我们到达文件系统的根,而文件系统的根通常存储在一个非常小的日志中。
COW 文件系统很有趣。它们提供与基于块的文件系统非常相似的性能,同时能够在不直接将数据更改存储在日志中的情况下实现原子更新。它们甚至将数据的存储位置分离,这为磨损均衡创造了机会。
限制的向上更新运动带来了一些问题。因为对写时复制文
件系统的更新在到达根目录之前不会停止,所以一次更新可能会级联成比原始数据所需的更大的一组写入操作。除此之外,向上的运动将这些写入操作集中到块中,这可能会比文件系统的其他部分更早地磨损。
3.littlefs的借鉴实现
我们查看现有的文件系统,有两种有趣的设计模式脱颖而出,但每种模式都有自己的一系列问题。
- 日志记录提供了独立的原子性,但运行时性能较差。
- 写时复制(COW)数据结构性能良好,但将原子性问题向上推。
littlefs的理念:
- 在小块级别,littlefs 由小型的两个块的日志构建而成,这些日志为文件系统上任何位置的元数据提供原子更新。
- 在大块级别,littlefs 是一个按需可驱逐的块的写时复制(Copy-on-Write)树。
它的核心是元数据对,也就是metadata pairs。这些是小的、两个块的日志,允许在文件系统中的任何位置进行原子更新。
为什么是两个块呢?嗯,日志通过将条目附加到存储在磁盘上的循环缓冲区来工作。但是请记住,闪存的写入粒度有限。我们可以将新数据增量式地编程到已擦除的块上,但我们需要一次擦除一整个块。这意味着为了使我们的循环缓冲区正常工作,我们需要不止一个块。