Spiffs简介
本文对spiffs(0.3.7)进行简单介绍,包括它的功能、适用范围 和基本架构,不包含源码实现细节分析。
1. 简介
Spiffs是一个轻量级、为嵌入式设备打造的用于 SPI NOR flash的文件系统,它不追求高性能与 复杂的功能,优先考虑对flash友好,占用小。
原项目地址:https://github.com/pellepl/spiffs
Spiffs设计时主要关注以下几点:
- 嵌入式设备可用代码空间有限,RAM容量小,可能不具备堆内存;
- flash设备仅能以块的方式进行擦除;
- 擦除操作把bit从0变为1;
- 写操作把1变为0;
- 被写0的单元仅可通过擦除操作重新恢复为1;
- 磨损均衡。
具备以下的优势:
- 针对小RAM容量设计;
- 运行时占用固定大小的RAM buffer空间,与文件数量无关;
- 类似posix api:open, close, read, write, seek, stat等;
- 不仅是SPI flash,理论上也支持MCU片上flash;
- 可在同一设备(甚至是同一片SPI flash)上同时运行多份 不同自定义选项的spiffs;
- 实现了静态磨损均衡;
- 包含文件系统一致性检查;
- 高度可配置性,可裁剪。
存在以下的不足:
- 不支持目录结构:创建位于
tmp/路径下的myfile.txt文件 实际上是创建一个命名为tmp/myfile.txt的文件; - 不检测和处理坏块;
- 设计非实时性、非高性能导向,文件查找和读写效率不高, 不适合频繁创建/删除大量文件。
与spiffs相比,同样适用SPI NOR flash的littlefs提供了目录结构、 坏块检测和管理,掉电安全,在效率和可靠性方面更具优势。项目地址: https://github.com/littlefs-project/littlefs
2. 组织方式
2.1 逻辑结构
与具体所使用的SPI flash相对应,spiffs在逻辑上分为 页和块的结构。一个逻辑页通常包含一个或多个物理页, 是存储文件数据的基本单元;一个逻辑块通常包含一个 或多个物理块。一个逻辑块包含若干个逻辑页,一个spiffs 文件系统中逻辑块与块和页与页之间的大小是相同的。 型号为W25Q128BV的SPI NOR flash,具有256个块,每块 256个页,页大小为256字节,每个块64KB,总容量16MB, 如果将整片分配同一个文件系统,可以采用逻辑页和块 分别和物理页和块一一对应,也就逻辑页大小等同与 物理页大小,逻辑块大小等同与物理块大小的分配方案。
2.2 对象、索引和查找表
spiffs中每个数据页包含一段5至9字节的头,称为metadata。
文件,或称对象(object),用对象id加以区分,对象id 是页头(metadata)的一部分。因此,除了空闲页,每个 数据页都对应于一个文件/对象。
每个对象都由两种类型的页组成:索引页(object index pages)和数据页 (data pages)。数据页存储用户所写的文件数据,索引页包含对象 的metadata,记录哪些数据页属于某个文件的一部分。
页头还包含该页在其所属对象中的序号(span index),例如某个 文件占用了3个数据页,那么该对象第一个数据页的span index即 为0,第二个数据页span index为1,第三个数据页span index为3。
最终,页头还包含用于记录该页是否使用、是否删除、是否最新、 是索引还是数据页等信息的字段。
对象索引页同样具有span index,其中span index为0的索引页 为对象索引头(object index header),该页不仅包含数据页 的引用,还记录对象的命名和所占字节数等方面的信息。
假设创建一个占据3个数据页的文件“spandex-joke.txt”,它的 对象id是12,该文件在spiffs文件系统中可能是按以下方式 存放的:
PAGE 0 <此页内容稍后解释>
PAGE 1 page header: [obj_id:12 span_ix:0 flags:USED|DATA]
<文件“spandex-joke.txt”的第一个数据页>
PAGE 2 page header: [obj_id:12 span_ix:1 flags:USED|DATA]
<第二个数据页>
PAGE 3 page header: [obj_id:545 span_ix:13 flags:USED|DATA]
<object id 为545的其他文件的数据页>
PAGE 4 page header: [obj_id:12 span_ix:2 flags:USED|DATA]
<第三个数据页>
PAGE 5 page header: [obj_id:12 span_ix:0 flags:USED|INDEX]
obj ix header: [name:spandex-joke.txt size:600 bytes flags:FILE]
obj ix: [1 2 4]
注意第5页的内容,该页即为对象索引头所在的页,obj ix列表中的
内容表示按顺序存放的、该文件所有数据页的页号,该列表的索引号
与数据页的span index对应:
entry ix: 0 1 2
obj ix: [1 2 4]
| | |
PAGE 1, DATA, SPAN_IX 0 --------/ | |
PAGE 2, DATA, SPAN_IX 1 --------/ |
PAGE 4, DATA, SPAN_IX 2 --------/
每个逻辑块,开始部分的逻辑页作为对象查找表(object look-up)。 不同于索引页和数据页,这些页没有页头,它们记录其所在逻辑块 其他页的object id,用于加快查找速度,仅需要扫描这些页即可 获取该逻辑块包含的数据页和索引页的object id和它们所在的位置。 对象查找表的引入避免了查找文件时一一扫描flash的所有页,而是 仅仅每个逻辑块读取开头少量的页,提高了效率。
以上“spandex-joke.txt”文件的例子中,第0页和其他逻辑页的 内容可能如下:
PAGE 0 [ 12 12 545 12 12 34 34 4 0 0 0 0 ...]
PAGE 1 page header: [obj_id:12 span_ix:0 flags:USED|DATA] ...
PAGE 2 page header: [obj_id:12 span_ix:1 flags:USED|DATA] ...
PAGE 3 page header: [obj_id:545 span_ix:13 flags:USED|DATA] ...
PAGE 4 page header: [obj_id:12 span_ix:2 flags:USED|DATA] ...
PAGE 5 page header: [obj_id:12 span_ix:0 flags:USED|INDEX] ...
PAGE 6 page header: [obj_id:34 span_ix:0 flags:USED|DATA] ...
PAGE 7 page header: [obj_id:34 span_ix:1 flags:USED|DATA] ...
PAGE 8 page header: [obj_id:4 span_ix:1 flags:USED|INDEX] ...
PAGE 9 page header: [obj_id:23 span_ix:0 flags:DELETED|INDEX] ...
PAGE 10 page header: [obj_id:23 span_ix:0 flags:DELETED|DATA] ...
PAGE 11 page header: [obj_id:23 span_ix:1 flags:DELETED|DATA] ...
PAGE 12 page header: [obj_id:23 span_ix:2 flags:DELETED|DATA] ...
注意到对象查找表中与第9至12页(object id 23)对应的记录为0, 这表示这些页已经被删除,数据/索引页被删除会在页头的flags字段 标记并在对象查找表对应位置将object id写为0。
对象查找表中,有两种特殊的object id:
- object id 为0(所有数据bit为0),对应一个被删除的页;
- object id 为0xff…(所有数据bit为1),对应一个空闲页。
为了加速查找对象索引页,对象查找表中,object id的最高位为1时, 表示其对应的页为索引页;最高bit为0时,表示其对应的页为数据页。 所以,以上例子中第0页的内容应该是这样的:
PAGE 0 [ 12 12 545 12 *12 34 34 *4 0 0 0 0 ...]
*号表示该object id的最高位为1,对应页为索引页。