littlefs简介
- littlefs实现了
- 断电恢复
- 磨损均衡
- 有限的RAM和 ROM
常用文件系统简述
- 首先,我们有非弹性的、基于块的文件系统,比如 FAT 和 ext2。这些是最早的文件系统设计,通常是最简单的。这里存储被划分为多个块,每个文件都存储在一个块的集合。如果不进行修改,这些文件系统不会power-loss 弹性,因次更新文件就像重写块一样简单
.--------.
| root |
| |
| |
'--------'
.-' '-.
v v
.--------. .--------.
| A | | B |
| | | |
| | | |
'--------' '--------'
.-' .-' '-.
v v v
.--------. .--------. .--------.
| C | | D | | E |
| | | | | |
| | | | | |
'--------' '--------' '--------'
- 还有日志记录文件系统,比如 JFFS、YAFFS 和 SPIFFS,存储位置并不局限于一块data 的 JSON JSON 中,则整个存储都用于循环日志,即附加对文件系统所做的每项更改。编写附加新更改,而读取时需要遍历日志以重建文件
v
.--------.--------.--------.--------.--------.--------.--------.--------.
| C | new B | new A | | A | B |
| | | |-> | | |
| | | | | | |
'--------'--------'--------'--------'--------'--------'--------'--------'
- 除此以外还有日志文件系统, 日志文件系统相当你将基于块的文件系统与日志记录组合起来ext4 和 NTFS 就是很好的例子。在这里,我们采用一个普通的基于块的文件系统,并添加一个有界日志,我们在其中记录每个更改在它发生之前。 读取一个块C是,既要读取原块C,也要读取其修改C’日志文件系统相当复杂,因为实际上有两个文件系统在运行并行,这需要写更多的代码。 由于存储位置之间存在很强的关系,因此不是磨损均衡的
journal
.--------.--------.
.--------. | C'| D'| | E'|
| root |-->| | |-> | |
| | | | | | |
| | '--------'--------'
'--------'
.-' '-.
v v
.--------. .--------.
| A | | B |
| | | |
| | | |
'--------' '--------'
.-' .-' '-.
v v v
.--------. .--------. .--------.
| C | | D | | E |
| | | | | |
| | | | | |
'--------' '--------' '--------'
- 最后但同样重要的是,我们有写时复制(COW)文件系统,例如Btrfs和ZFS。它们与其他基于块的文件系统非常相似,但是,不是直接在原地更新块,而是通过创建新块来执行所有更新一份包含更改的副本,并将所有对旧块的引用替换为新块。这会将我们所有的修改递归地向上推,直到我们到达我们文件系统的根目录,这通常存储在一个非常小的日志中。
- COW文件系统很有趣。它们提供的性能与基于块的文件系统相当,同时设法实现原子更新,而不会将数据更改直接存储在日志中。他们甚至会断开存储与数据之间的关联数据的位置,这有利于耗损均衡。更新时无限制向上递归导致了一些问题。
- 因为COW文件系统的更新在完成之前不会停止一旦到达根节点,更新可能会引发更大范围的写入操作原始数据需要(某种处理或操作)。除此之外,还有向上的运动将这些写入操作集中到块中,这可能会比其他情况更早地耗损块文件系统的其余部分。
.--------. .--------.
| root | write |new root|
| | ==> | |
| | | |
'--------' '--------'
.-' '-. | '-.
| .-------|------------------' v
v v v .--------.
.--------. .--------. | new B |
| A | | B | | |
| | | | | |
| | | | '--------'
'--------' '--------' .-' |
.-' .-' '-. .------------|------'
| | | | v
v v v v .--------.
.--------. .--------. .--------. | new D |
| C | | D | | E | | |
| | | | | | | |
| | | | | | '--------'
'--------' '--------' '--------'
littlefs的实现:
- 如果我们观察现有的文件系统,会发现两种有趣的设计模式这些都很突出,但每个都有自己的问题。日志记录文件系统提供独立的原子性,运行时性能较差。COW文件系统性能良好的结构会将原子性问题向上推。我们可以绕过这些限制吗?考虑日志记录。它具有O(n²)运行时或O(n)RAM成本。我们无法避免这些成本,但如果我们对可以达到的大小设定上限最小限度地防止理论成本成为问题。通过限定输入,复杂度可以是O(1)。
在COW数据结构的情况下,我们可以尝试稍微扭曲定义。假设我们的COW结构不会在单个写入后复制,而是复制
n次写入后复制。这不会改变大多数COW属性(假设您可以原子写入!),但这样的成果是可以防止磨损这种有界写入上的复制(CObW)仍然关注磨损,但在在每个级别上,我们将磨损传播除以n (n>分支因子)磨损扩展不再是问题。 - 单独、日志记录和COW是不完美的解决方案, 它们都有限制其有用性的弱点。但如果我们将两者合并,他们可以
相互解决对方的局限性。这就是littlefs背后的想法。在子块级别,只用两个小数据块实现,并为任何地方的元数据提供原子更新(即A,B,C,D这一层)。 在文件系统上。 在超级块级别,littlefs是块的CObW树可以根据需要拆分和新建(即E、F、G这一层)。
root
.--------.--------.
| A'| B'| |
| | |-> |
| | | |
'--------'--------'
.----' '--------------.
A v B v
.--------.--------. .--------.--------.
| C'| D'| | | E'|new| |
| | |-> | | | E'|-> |
| | | | | | | |
'--------'--------' '--------'--------'
.-' '--. | '------------------.
v v .-' v
.--------. .--------. v .--------.
| C | | D | .--------. write | new E |
| | | | | E | ==> | |
| | | | | | | |
'--------' '--------' | | '--------'
'--------' .-' |
.-' '-. .-------------|------'
v v v v
.--------. .--------. .--------.
| F | | G | | new F |
| | | | | |
| | | | | |
'--------' '--------' '--------'
LittleFs移植
- 把移植所需的接口实现就好,主要是一些配置和块读写等操作函数,配置和快读写操作函数其实就是驱动。 整个过程和移植FreeRtos比较类似。嵌入式移植的原理大都相似
参考资料
LittleFS:一个完整的嵌入式文件系统介绍、移植使用教程_觉皇嵌入式的博客-CSDN博客
LittleFS简单移植 - CashQian Blog
littlefs-project/littlefs: A little fail-safe filesystem designed for microcontrollers (github.com)
littlefs/lfs.h at master · littlefs-project/littlefs · GitHub