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,对应页为索引页。

3. 参考