前言
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)是必须的。