LittleFS移植实践

LittleFS移植实践

  • 前言
  • 相关资料链接
  • 先吐为快
  • 移植的要点
    • 块设备接口
      • 对struct lfs_config的说明
    • read 接口解读
    • prog接口解读
    • erase接口解读
    • 关于动态内存

前言

LittleFS是ARM mbedOS的官方推荐文件系统,具有轻量级、掉电安全的特性。

相关资料链接

  • 开源项目:https://github.com/ARMmbed/littlefs
  • 文档:开源项目中的README.md就是对应的文档

先吐为快

本着不吐不快的原则,先就个人对LittleFS的使用及移植进行单方面的吐槽,不喜请跳过。

  • 文档很有限,讲解得也不太透彻。
  • 读性能很优秀,连续写性能也不错,但随机写性能有点糟糕。
  • 对于SPI-FLASH文件的close和seek有可能会非常慢

移植的要点

  • 对struct lfs_config进行填充
  • 使用lfs_mount挂载

示例如下:

/**
 * @brief Mount LFS
 */
static int my_lfs_mount(void)
{
    int err = 0;

    // Check if block device available
    if (hal_blk_probe() < 0) {
        LFS_ERROR("ERROR: %s, %d\r\n", __func__, __LINE__);
        err = -1;
        goto ERR_EXIT;
    }

    memset(&config, 0, sizeof(config));
    config.read  = blk_device_read;
    config.prog  = blk_device_prog;
    config.erase = blk_device_erase;
    config.sync  = blk_device_sync;
    config.read_size   = hal_blk_get_readsize();
    config.prog_size   = hal_blk_get_progsize();
    config.block_size  = hal_blk_get_erasesize();
    config.block_count = hal_blk_get_blockcnt();
    config.lookahead = ROUND_UP(config.block_count, 32);

    // 需要使用的内存数量 = lookahead/8;
    // 因此,此处需要限制最大数量,避免消耗过多内存
    if (config.lookahead > MAX_LFS_LOOKAHEAD) {
        config.lookahead = MAX_LFS_LOOKAHEAD;
    }

    LFS_PRINTF("block_count -> %d\r\n", config.block_count);
    LFS_PRINTF("lookahead -> %d\r\n", config.lookahead);

    memset(&the_lfs, 0, sizeof(the_lfs));
    err = lfs_mount(&the_lfs, &config);
    LFS_PRINTF("mount -> %d\r\n", err);

ERR_EXIT:
    return err;
}

块设备接口

实际移植的时候,会发现最核心的就是对块设备的接口封装。
关键是LittleFS对这个部分的说明及注释实际上并不十分详细,这里加以补充说明。

对struct lfs_config的说明

  • context 用于传递信息给块设备,便于块设备驱动进行特定的处理,比如:告诉块设备驱动具体哪个范围用于文件系统。这个内容的数据结构由块设备驱动来定义。
  • read 读块接口,用于从块内读取一个块数据
  • prog 写块接口,用于将一段数据写入到块中,这个块必须是已经被擦除的
  • erase 擦块接口,用于擦除一个块
  • sync 同步,有的块设备有缓存需要进行同步操作才能将缓存里的内容写出
  • read_size 每次读取的字节数,可以比物理读单元大以改善性能,这个数值决定了读缓存的大小,但值太大会带来更多的内存消耗。
  • prog_size 每次写入的字节数,可以比物理写单元大以改善性能,这个数值决定了写缓存的大小,必须是read_size的整数倍,但值太大会带来更多的内存消耗。
  • block_size 每个擦除块的字节数,可以比物理擦除单元大,但此数值应尽可能小因为每个文件至少会占用一个块。必须是prog_size的整数倍。
  • block_count 可以被擦除的块数量,这取决于块设备的容量及擦除块的大小。
  • lookahead 块分配时的预测深度(分配块时每次步进多少个块),这个数值必须为32的整数倍,如1024表示每次预测1024个block。这个值对于内存消耗影响不大,因为它对应的lookahead_buffer 中使用1bit代表一个block。
  • read_buffer 可选参数,用于静态分配读缓存,这个缓存的大小应该等于read_size
  • prog_buffer 可选参数,用于静态分配写缓存,这个缓存的大小应该等于prog_size
  • lookahead_buffer 可选参数,用于静态分配预测缓存,这个缓存的大小应该等于预测深度lookahead/8,因为每个bit表示一个块。
  • file_buffer 可选参数,用于静态分配的文件缓存,这个缓存的大小必须等于prog_size,如果使能了这个参数,则同一时刻只能打开1个文件。

read 接口解读

int (*read)(const struct lfs_config *c, lfs_block_t block,
            lfs_off_t off, void *buffer, lfs_size_t size);

参数

  • block 逻辑块编号,从0开始
  • off 块内偏移,lfs在调用read接口时,传入的off值一定能被read_size整除
  • buffer 读出数据的输出缓冲区
  • size 要读出的数据字节数,lfs在调用read接口时,一定不会存在跨越块的情况。

返回值

  • 0 成功
  • <0 错误码

prog接口解读

int (*prog)(const struct lfs_config *c, lfs_block_t block,
            lfs_off_t off, const void *buffer, lfs_size_t size);

参数

  • block 逻辑块编号,从0开始
  • off 块内偏移,lfs在调用prog接口时,传入的off值一定能被prog_size整除
  • buffer 要写入的数据
  • size 要写入的数据字节数,lfs在调用prog接口时,一定不会存在跨越块的情况。

返回值

  • 0 成功
  • <0 错误码

erase接口解读

int (*erase)(const struct lfs_config *c, lfs_block_t block);

参数

  • block 逻辑块编号,从0开始

返回值

  • 0 成功
  • <0 错误码

关于动态内存

由于嵌入式系统并不一定都支持heap,LittleFS提供了一个编译开关用于不支持heap的系统:

LFS_NO_MALLOC
  • 对于支持HEAP的系统,不需要定义LFS_NO_MALLOC,相关的缓存都是通过malloc来申请的。
  • 对于不支持HEAP的系统,则需要定义LFS_NO_MALLOC,相关的缓存都需要手动指定静态内存空间。

如果系统的HEAP是自己实现的而不是使用标准库的malloc/free实现,则需要自己封装动态内存接口,参考lfs_util.h中的如下代码:

// Allocate memory, only used if buffers are not provided to littlefs
static inline void *lfs_malloc(size_t size) {
#ifndef LFS_NO_MALLOC
    return sys_malloc(size);
#else
    (void)size;
    return NULL;
#endif
}

// Deallocate memory, only used if buffers are not provided to littlefs
static inline void lfs_free(void *p) {
#ifndef LFS_NO_MALLOC
    sys_free(p);
#else
    (void)p;
#endif
}

对于不支持HEAP的系统,除了需要定义LFS_NO_MALLOC外,还必须在lfs_mount前将lfs_config参数中的read_buffer/prog_buffer/lookahead_buffer/file_buffer设置为静态内存。并且存在只能同时打开一个文件的限制(单实例)。

若要支持同时打开多个文件,则动态内存(HEAP)是必须的。

你可能感兴趣的:(LittleFS,移植,ARM嵌入式)