littlefs 源码分析

littlefs 源码分析

1. 概述

littlefs 是一个为微控制器设计的小型故障安全文件系统,具有以下特点:

  • 掉电恢复能力:所有文件操作都有强写时复制保证,掉电后文件系统会回退到最后一个已知的良好状态
  • 动态磨损均衡:专为闪存设计,提供动态块的磨损均衡,能检测并绕过坏块
  • 有限的 RAM/ROM 使用:内存使用严格受限,不随文件系统大小增长而变化,无无限递归,动态内存限于可静态提供的可配置缓冲区

2. 代码分层结构

littlefs 采用清晰的分层设计,从底层到上层依次为:

复制代码
+-------------------+
|     API 层       |
+-------------------+
| 核心文件系统层    |
+-------------------+
|    块设备层       |
+-------------------+
|  物理存储介质     |
+-------------------+

2.1 块设备层

块设备层是文件系统与物理存储介质之间的接口,由用户实现,主要包括以下操作:

  • read:从块中读取数据
  • prog:向块中编程数据(块必须先被擦除)
  • erase:擦除块
  • sync:同步底层块设备状态

这些操作在 lfs_config 结构体中定义,作为文件系统初始化的一部分。

2.2 核心文件系统层

核心文件系统层是 littlefs 的核心,实现了文件系统的主要功能:

  • 元数据管理:使用元数据对(metadata pairs)实现原子更新
  • 文件操作:实现文件的创建、读写、关闭等操作
  • 目录操作:实现目录的创建、遍历等操作
  • 块分配:实现动态磨损均衡的块分配器
  • 垃圾回收:清理过时数据,优化存储空间

核心文件系统层的主要数据结构包括:

  • lfs_t:文件系统实例
  • lfs_file_t:文件实例
  • lfs_dir_t:目录实例
  • lfs_cache_t:缓存实例
  • lfs_mdir_t:元数据目录实例

2.3 API 层

API 层提供给用户的接口,模仿 POSIX 文件系统接口,主要包括:

  • 文件系统操作lfs_formatlfs_mountlfs_unmount
  • 文件操作lfs_file_openlfs_file_readlfs_file_writelfs_file_close
  • 目录操作lfs_mkdirlfs_dir_openlfs_dir_read
  • 通用操作lfs_removelfs_renamelfs_stat

3. 核心数据结构

3.1 lfs_t

文件系统的主结构,包含文件系统的所有状态:

c 复制代码
typedef struct lfs {
    lfs_cache_t rcache;     // 读缓存
    lfs_cache_t pcache;     // 写缓存

    lfs_block_t root[2];    // 根目录的元数据对
    struct lfs_mlist *mlist; // 元数据列表
    uint32_t seed;          // 随机种子

    lfs_gstate_t gstate;    // 全局状态
    lfs_gstate_t gdisk;     // 磁盘上的全局状态
    lfs_gstate_t gdelta;    // 全局状态增量

    struct lfs_lookahead {  // 预分配块的 lookahead 缓冲区
        lfs_block_t start;
        lfs_block_t size;
        lfs_block_t next;
        lfs_block_t ckpoint;
        uint8_t *buffer;
    } lookahead;

    const struct lfs_config *cfg; // 配置
    lfs_size_t block_count;  // 块数量
    lfs_size_t name_max;     // 文件名最大长度
    lfs_size_t file_max;     // 文件最大大小
    lfs_size_t attr_max;     // 属性最大大小
    lfs_size_t inline_max;   // 内联文件最大大小
} lfs_t;

3.2 lfs_config

文件系统配置结构体,定义了文件系统的操作和参数:

c 复制代码
struct lfs_config {
    void *context;           // 用户提供的上下文

    // 块设备操作
    int (*read)(const struct lfs_config *c, lfs_block_t block,
            lfs_off_t off, void *buffer, lfs_size_t size);
    int (*prog)(const struct lfs_config *c, lfs_block_t block,
            lfs_off_t off, const void *buffer, lfs_size_t size);
    int (*erase)(const struct lfs_config *c, lfs_block_t block);
    int (*sync)(const struct lfs_config *c);

    // 块设备配置
    lfs_size_t read_size;    // 读操作的最小大小
    lfs_size_t prog_size;    // 编程操作的最小大小
    lfs_size_t block_size;   // 块大小
    lfs_size_t block_count;  // 块数量
    int32_t block_cycles;    // 块循环计数

    // 性能和内存配置
    lfs_size_t cache_size;   // 缓存大小
    lfs_size_t lookahead_size; // 预分配缓冲区大小
    lfs_size_t compact_thresh; // 元数据压缩阈值

    // 静态缓冲区
    void *read_buffer;       // 静态读缓冲区
    void *prog_buffer;       // 静态编程缓冲区
    void *lookahead_buffer;  // 静态预分配缓冲区

    // 限制
    lfs_size_t name_max;     // 文件名最大长度
    lfs_size_t file_max;     // 文件最大大小
    lfs_size_t attr_max;     // 属性最大大小
    lfs_size_t metadata_max; // 元数据最大大小
    lfs_size_t inline_max;   // 内联文件最大大小
};

3.3 元数据对 (Metadata Pairs)

元数据对是 littlefs 的核心数据结构,用于实现原子更新:

c 复制代码
typedef struct lfs_mdir {
    lfs_block_t pair[2];     // 元数据对的两个块
    uint32_t rev;            // 修订号
    lfs_off_t off;           // 偏移量
    uint32_t etag;           // 标签
    uint16_t count;          // 计数
    bool erased;             // 是否已擦除
    bool split;              // 是否已分割
    lfs_block_t tail[2];     // 尾部指针
} lfs_mdir_t;

3.4 文件和目录结构

c 复制代码
// 文件结构
typedef struct lfs_file {
    struct lfs_file *next;   // 下一个文件
    uint16_t id;             // 文件 ID
    uint8_t type;            // 文件类型
    lfs_mdir_t m;            // 元数据目录

    struct lfs_ctz {         // CTZ 跳表
        lfs_block_t head;     // 头部块
        lfs_size_t size;      // 大小
    } ctz;

    uint32_t flags;          // 标志
    lfs_off_t pos;           // 位置
    lfs_block_t block;       // 当前块
    lfs_off_t off;           // 当前偏移
    lfs_cache_t cache;       // 缓存

    const struct lfs_file_config *cfg; // 文件配置
} lfs_file_t;

// 目录结构
typedef struct lfs_dir {
    struct lfs_dir *next;    // 下一个目录
    uint16_t id;             // 目录 ID
    uint8_t type;            // 目录类型
    lfs_mdir_t m;            // 元数据目录

    lfs_off_t pos;           // 位置
    lfs_block_t head[2];     // 头部块
} lfs_dir_t;

4. 核心算法

4.1 元数据管理

littlefs 使用元数据对(两个块的日志)来实现原子更新:

  1. 元数据写入:将更新追加到日志中,使用校验和确保数据完整性
  2. 元数据压缩:当日志满时,清理过时数据并将有效数据复制到新块
  3. 元数据分割:当压缩后仍然满时,将元数据对分割为两个,形成链表

4.2 CTZ 跳表

CTZ(Count Trailing Zeros)跳表是 littlefs 用于存储文件数据的高效数据结构:

  1. 结构:多层链表,每个块包含指向前面块的指针
  2. 指针规则:对于第 n 个块,包含 ctz(n)+1 个指针
  3. 优点:支持高效的追加操作(O(1))和随机访问(O(log n))
  4. 存储开销:平均每个块仅需 2 个指针的开销

4.3 块分配

littlefs 实现了动态磨损均衡的块分配器:

  1. 随机分配:使用伪随机数生成器选择块,避免集中磨损
  2. 块循环计数:跟踪块的擦除次数,优先选择擦除次数少的块
  3. 坏块检测:检测并绕过坏块

4.4 垃圾回收

littlefs 实现了高效的垃圾回收机制:

  1. 元数据垃圾回收:通过压缩和分割元数据对清理过时数据
  2. 数据块垃圾回收:当需要新块时,回收包含过时数据的块

5. 移植方法

5.1 实现块设备操作

要移植 littlefs,首先需要实现块设备操作函数:

c 复制代码
// 读操作
int user_provided_block_device_read(const struct lfs_config *c, lfs_block_t block,
        lfs_off_t off, void *buffer, lfs_size_t size) {
    // 从物理存储中读取数据
    // ...
    return LFS_ERR_OK;
}

// 编程操作
int user_provided_block_device_prog(const struct lfs_config *c, lfs_block_t block,
        lfs_off_t off, const void *buffer, lfs_size_t size) {
    // 向物理存储中写入数据
    // ...
    return LFS_ERR_OK;
}

// 擦除操作
int user_provided_block_device_erase(const struct lfs_config *c, lfs_block_t block) {
    // 擦除物理存储中的块
    // ...
    return LFS_ERR_OK;
}

// 同步操作
int user_provided_block_device_sync(const struct lfs_config *c) {
    // 同步物理存储
    // ...
    return LFS_ERR_OK;
}

5.2 配置文件系统

配置 lfs_config 结构体,设置块设备参数和内存配置:

c 复制代码
const struct lfs_config cfg = {
    // 块设备操作
    .read  = user_provided_block_device_read,
    .prog  = user_provided_block_device_prog,
    .erase = user_provided_block_device_erase,
    .sync  = user_provided_block_device_sync,

    // 块设备配置
    .read_size = 16,
    .prog_size = 16,
    .block_size = 4096,
    .block_count = 128,
    .block_cycles = 500,

    // 内存配置
    .cache_size = 16,
    .lookahead_size = 16,
};

5.3 初始化和使用文件系统

使用 lfs_mount 挂载文件系统,使用 lfs_format 格式化文件系统:

c 复制代码
lfs_t lfs;

// 挂载文件系统
int err = lfs_mount(&lfs, &cfg);

// 如果挂载失败,格式化文件系统
if (err) {
    lfs_format(&lfs, &cfg);
    lfs_mount(&lfs, &cfg);
}

// 使用文件系统
// ...

// 卸载文件系统
lfs_unmount(&lfs);

6. 使用示例

6.1 基本文件操作

以下是一个完整的示例,展示如何使用 littlefs 进行基本的文件操作:

c 复制代码
#include "lfs.h"

// 文件系统变量
lfs_t lfs;
lfs_file_t file;

// 块设备操作
int user_provided_block_device_read(const struct lfs_config *c, lfs_block_t block,
        lfs_off_t off, void *buffer, lfs_size_t size) {
    // 实现读操作
    return LFS_ERR_OK;
}

int user_provided_block_device_prog(const struct lfs_config *c, lfs_block_t block,
        lfs_off_t off, const void *buffer, lfs_size_t size) {
    // 实现编程操作
    return LFS_ERR_OK;
}

int user_provided_block_device_erase(const struct lfs_config *c, lfs_block_t block) {
    // 实现擦除操作
    return LFS_ERR_OK;
}

int user_provided_block_device_sync(const struct lfs_config *c) {
    // 实现同步操作
    return LFS_ERR_OK;
}

// 文件系统配置
const struct lfs_config cfg = {
    // 块设备操作
    .read  = user_provided_block_device_read,
    .prog  = user_provided_block_device_prog,
    .erase = user_provided_block_device_erase,
    .sync  = user_provided_block_device_sync,

    // 块设备配置
    .read_size = 16,
    .prog_size = 16,
    .block_size = 4096,
    .block_count = 128,
    .cache_size = 16,
    .lookahead_size = 16,
    .block_cycles = 500,
};

// 主函数
int main(void) {
    // 挂载文件系统
    int err = lfs_mount(&lfs, &cfg);

    // 格式化文件系统(如果需要)
    if (err) {
        lfs_format(&lfs, &cfg);
        lfs_mount(&lfs, &cfg);
    }

    // 读取启动计数
    uint32_t boot_count = 0;
    lfs_file_open(&lfs, &file, "boot_count", LFS_O_RDWR | LFS_O_CREAT);
    lfs_file_read(&lfs, &file, &boot_count, sizeof(boot_count));

    // 更新启动计数
    boot_count += 1;
    lfs_file_rewind(&lfs, &file);
    lfs_file_write(&lfs, &file, &boot_count, sizeof(boot_count));

    // 关闭文件(确保数据写入)
    lfs_file_close(&lfs, &file);

    // 卸载文件系统
    lfs_unmount(&lfs);

    // 打印启动计数
    printf("boot_count: %d\n", boot_count);
}

6.2 目录操作

c 复制代码
// 创建目录
lfs_mkdir(&lfs, "/mydir");

// 打开目录
lfs_dir_t dir;
lfs_dir_open(&lfs, &dir, "/mydir");

// 读取目录内容
struct lfs_info info;
while (lfs_dir_read(&lfs, &dir, &info) > 0) {
    printf("%s\n", info.name);
}

// 关闭目录
lfs_dir_close(&lfs, &dir);

6.3 属性操作

c 复制代码
// 设置属性
uint8_t data[] = {1, 2, 3, 4};
lfs_setattr(&lfs, "file.txt", 0x01, data, sizeof(data));

// 获取属性
uint8_t buffer[4];
lfs_getattr(&lfs, "file.txt", 0x01, buffer, sizeof(buffer));

7. 总结

littlefs 是一个专为微控制器设计的文件系统,具有以下优点:

  • 掉电恢复能力:通过写时复制和校验和确保数据完整性
  • 动态磨损均衡:延长闪存寿命
  • 有限的内存使用:适合资源受限的微控制器
  • 简单易用的 API:模仿 POSIX 接口,易于使用
  • 高度可移植:只需实现块设备操作即可移植到不同平台

littlefs 的设计思想和实现技术对于理解嵌入式文件系统的工作原理具有重要参考价值,其代码结构清晰,算法设计巧妙,是学习嵌入式系统存储管理的优秀案例。

8. 参考资料

相关推荐
天涯铭6 小时前
深入浅出:单片机I/O口串联电阻选型
单片机·嵌入式硬件·io口串联电阻
国科安芯6 小时前
ASP7A84AS——航天级低噪声高PSRR线性稳压器
网络·单片机·嵌入式硬件·架构·安全性测试
普中科技7 小时前
【普中STM32F1xx开发攻略--标准库版】-- 第 42 章 STM32 内部 FLASH 实验
stm32·单片机·嵌入式硬件·开发板·普中科技·内部flash
破晓单片机9 小时前
012、STM32项目分享:智能台灯系统
stm32·单片机·嵌入式硬件
悠哉悠哉愿意9 小时前
【单片机复习笔记】十五届国赛复盘
笔记·单片机·嵌入式硬件·学习
DS小龙哥10 小时前
基于ESP32-S3设计的智能人脸识别门禁系统
stm32·单片机·嵌入式硬件
一棵树735110 小时前
信号与通信
单片机·嵌入式硬件
JNX_SEMI11 小时前
Hi6000C可与H6912直接对标,管脚完全兼容
单片机·嵌入式硬件·物联网·硬件工程
zlinear数据采集卡11 小时前
LC滤波电路深度解析:从电容与电感的“强强联合”到ZLinear采集卡的电源净化实战
单片机·嵌入式硬件