为SPI Flash设计的简单文件系统

本篇随笔是github项目:https://github.com/Yanye0xFF/SPIFS-V2 的文档说明部分。

设计思路

SPIFS-V2的设计理念非常简单,参考书籍中目录->内容的结构,在flash的固定区域存放创建的文件块(文件索引)结构,在剩下的区域存放数据。文件索引放在固定的区域对文件查找速度提升有所帮助(相比于文件索引块分散存放于每个文件的头部,列出文件需要遍历flash的全部扇区),但是缺点也很明显:1.限制了文件创建的数量,2.对于文件状态发生改变时(重命名/追加写后大小发生变化)更新文件索引结构信息比较困难(flash仅支持扇区擦)。3.难以支持长文件名,SPIFS-V2支持的文件名为8字符文件名+4字符拓展名的固定结构(和Dos的8+3文件名类似,在windows下为短文件名)。
本文件系统实现了一般程度上的读写均衡,但不支持磨损均衡(即不会自动搬运长期不动的文件)。由于文件的簇大小和flash的扇区大小一致,实现的过程也非常简单,采取标记-清除的方式,删除的文件会先被标记,直至空间不够用时调用垃圾回收进行擦除。

支持的特性

  • 创建文件,包含基本的时间,文件属性信息
  • 对已存在文件可以进行追加写入,覆盖写入
  • 文件重命名,文件删除,文件列表
  • 随机读取文件
  • flash操作的4字节对齐访问,适配ESP8266

不支持的特性

  • 随机写文件
  • 长文件名
  • 文件数量的限制(取决于配置的文件索引区的大小)

应用的案例

ESP8266+汉朔电子价签屏幕制作的天气站,SPIFS-V2作为其文件系统(图片/点阵字库/查找表/配置信息...)。

详情请查看我的文章分类 “像素天气站服务文档”

SPIFS-V2文件系统

文件索引区

下图是文件索引区的flash布局,单个FileBlock占用24字节,并且在flash的一个扇区(4KB)内24字节对齐,因此一个扇区最多有4096/24=170个文件索引块

// 文件索引块结构(24字节)
typedef struct _file_block {
    uint8_t filename[8]; // 文件名
    uint8_t extname[4]; // 拓展名
    uint32_t cluster;  // 首簇地址
    uint32_t length;  // 文件大小
    FileInfo info;   // 文件信息
} FileBlock;

FileBlock在flash中的存储布局与结构体布局一致

FileBlock末尾4字节文件信息结构

// 文件索引块文件信息结构(4字节)
typedef struct _file_info {
    uint8_t day;        // 创建日期
    uint8_t month;     // 创建月份
    uint8_t year;     // 创建年份,以2000年为基准,记录经过年数
    FileState state; // 文件状态字
} FileInfo;

FileBlock最高字节的文件状态字结构,为适配flash擦除后值为1,因此0为有效状态

// 文件状态字 (1字节) 权限描述: x表示禁止, o表示允许; 置为状态值:0, 默认状态值:1
typedef struct _file_state {
    // delete 0:删除, 1:正常文件
    // 置位权限: 读x 写x 重命名x 打开x
    // GC时将回收该文件占用区域
    uint8_t del : 1;

    // deprecate 0:失效文件, 1:正常文件
    // 置位权限: 读x 写x 重命名x 打开x
    // GC时将回收该文件占用区域
    uint8_t dep : 1;

    // read & write 0:只读文件, 1:读写文件
    // 置位权限: 读o 写x 重命名x 打开o
    uint8_t rw : 1;

    // system 0:系统文件, 1:普通文件
    // 置位权限: 文件系统层不做限制, 等同普通文件
    uint8_t sys : 1;

    // 未使用状态字, 可根据需求自定义
    uint8_t reserve : 4;
} FileState;


——删除标记位:
1:正常文件,GC时跳过该文件索引块
0:标记删除,GC时删除文件索引块,同时遍历数据链表删除数据区
——过期文件标记:
1:正常文件
0:标记为失效,列出文件列表时忽略该文件,GC时删除该文件索引块,但不处理数据区
用于文件更新:例如覆盖写/重命名情形,避免反复擦写文件索引区
——文件属性位:
1:读写文件
0:只读文件,先创建正常文件,然后可以:覆盖写入/追加写入/重命名,操作完成后修改属性为只读。
--系统文件属性位:
1:正常文件
0:系统文件,该属性交由上层应用处理,文件系统不对该属性做特殊处理。

文件数据区

文件数据区(文件簇)的大小 = spi flash的扇区大小 = 4KB

文件索引扇区集与文件数据扇区集之间不要求扇区号连续,可以在FLASH任意不重叠的区域内指定二者地址范围,只需保持其集合内部连续;由于文件系统分离了底层IO操作,因此文件索引扇区集与文件数据扇区集可以在两个不同的FLASH上分开存放。

文件簇内结构

系统移植

SPIFS-V2的底层操作位于spi_flash.c中,必须要实现的方法如下:

// 对flash单个扇区的擦除操作,sec为扇区号,w25q32为0~1023, w25q64为0~2047,其它型号需要自行查询datasheet。
SpiFlashOpResult spi_flash_erase_sector(uint16_t sec);

// 对flash的写入操作
// 其中des_addr为flash物理地址;
// src_addr为输出缓冲区指针,SPIFS-V2可以保证src_addr在四字节边界上,
// 且每次调用spi_flash_write时的size为4的整数倍,因此spi_flash_write的实现中可以安全地将src_addr强制指针转换为uint8_t */uint16_t *以兼容byte/half word读写设备。
SpiFlashOpResult spi_flash_write(uint32_t des_addr, uint32_t *src_addr, uint32_t size);

// 对flash的读取操作
// des_addr也经过对齐处理,因此实现的要求和spi_flash_write一样。
SpiFlashOpResult spi_flash_read(uint32_t src_addr, uint32_t *des_addr, uint32_t size);

文件系统的配置位于spifs.h中,如下4个值可以在'文件数量'与'文件实体空间'之间根据需求调整,文件数量多且小可以适当增大文件索引区减小文件数据区,反之同理。

// 文件索引占用扇区号范围[FB_SECTOR_START ~ FB_SECTOR_END]
#define FB_SECTOR_START     0
#define FB_SECTOR_END       3

// 文件数据区占用扇区号范围[DATA_SECTOR_START ~ DATA_SECTOR_END]
#define DATA_SECTOR_START   4
#define DATA_SECTOR_END     255

文件的状态位留有4个空余的bit,可以根据需求自定义

// 文件状态字 (1字节) 权限描述: x表示禁止, o表示允许; 置位状态值:0, 默认状态值:1
typedef struct _file_state {
    省略前面的字段.....
    // 未使用状态字, 可根据需求自定义
    uint8_t reserve : 4;
} FileState;
posted @ 2021-04-04 20:34  Yanye  阅读(2266)  评论(1编辑  收藏  举报