初学littlefs文件系统
概述
一个为微控制器设计的小故障安全文件系统。
掉电恢复能力: 设计用于处理随机电源故障。所有文件操作都有很强的写时拷贝保证,如果断电,文件系统将恢复到上一次已知的良好状态。 动态磨损均衡: 设计考虑到闪存,并提供动态块磨损均衡。此外,littlefs可以检测坏块并在它们周围工作。 有限RAM/ROM: 被设计为使用少量内存。RAM的使用是严格限制的,这意味着RAM的使用不会随着文件系统的增长而改变。文件系统不包含无界递归,动态内存仅限于可静态提供的可配置缓冲区。
在lfs.h的评论中可以找到详细的文档(或者至少是目前可用的尽可能多的细节)。 littlefs采用了一种配置结构,定义了文件系统的运行方式。配置结构为文件系统提供了块设备操作和维度、可调整的参数(用于在性能上权衡内存使用情况)以及可选的静态缓冲区(如果用户希望避免动态内存)。 littlefs的状态存储在lfs\t类型中,由用户分配,允许同时使用多个文件系统。使用lfs\u t和configuration struct,用户可以格式化块设备或挂载文件系统。 挂载后,littlefs提供了一整套类似POSIX的文件和目录功能,但文件系统结构的分配必须由用户提供。 所有POSIX操作,比如remove和rename,都是原子的,即使在断电的情况下也是如此。此外,在对文件调用sync或close之前,文件更新实际上不会提交到文件系统。
littlefs文件系统下载地址
Tags · littlefs-project/littlefs · GitHub
下载最新的即可。
源码下载下来后,只需要其中的4个文件:lfs.c、lfs.h、lfs_util.c、lfs_util.h,其他文件都是用于测试的Demo例程,调试过程中需要参考的话可以了解一下。
移植步骤
1、将lfs.c、lfs.h、lfs_util.c、lfs_util.h这4个文件放到一个新建的文件夹,并将其添加到工程中;sos_lfs.c这个文件是我对lfs.c接口二次封装生成的文件,这个文件根据你自己喜好命名,适配即可。
2、适配lfs_util.h
lfs_util.h中需要修改申请内存的方式,如果你代码中对malloc有修改过,就需要修改为你动态申请内存的方式
原版
我的系统移植了FreeRTOS系统,动态申请内存是在FreeRTOS的总内存中申请,所以我需要修改此内存申请的方式
3、适配lfs_config结构体的内容
lfs_config结构体
结构体成员变量 说明void *context用于给底层块设备传参,比如:要写入的文件信息、位置、大小、是否有坏块等。该参数的数据结构由底层块设备驱动来定义。如果不需要传参的话可以不赋值int (*read)块设备读取函数指针int (*prog)块设备写入函数指针,写入前必须保证该写入块已经被擦除过int (*erase)块设备擦除函数指针int (*sync)块设备同步函数指针,若块设备不需要同步操作,可以直接返回成功lfs_size_t read_size最小读取字节数,所有的读取操作字节数必须是它的整数倍lfs_size_t prog_size最小写入字节数,所有的写入操作字节数必须是它的整数倍lfs_size_t block_size擦除块操作的字节数,该选项不影响 RAM 消耗,可以比物理擦除尺寸大,但是每个文件至少占用一个块,必须是读取和写入操作字节数的整数倍lfs_size_t block_count设备上可擦除块的数量,block_size x block_count = 块设备容量int32_t block_cycleslittlefs 系统删除元数据日志并将元数据移动到另一个块之前的擦除周期数。建议取值范围为 100 ~ 1000,较大数值有较好的性能但是会导致磨损分布不一致,若取值 -1 的话,即为禁用块级磨损均衡lfs_size_t cache_size块缓存大小,每个缓存都会在 RAM 中缓冲一部分块数据,littlefs 系统需要一个读取缓存、一个写入缓存,每个文件还需要一个额外的缓存。更大的缓存可以通过存储更多的数据并降低磁盘访问数量等手段来提高性能lfs_size_t lookahead_size先行缓冲大小,更大的先行缓冲可以提高分配操作中可被发现的块数量。即分配块时每次分配多少个块,16就表示每次分配16个块。先行缓冲以 bit 位形式来存储,故 RAM 中的一个字节对应8个块。该值必须是8的整数倍int (*lock)块设备加锁函数指针,需定义 LFS_THREADSAFE 宏才可用,若不需要多线程操作可不赋值int (*unlock)块设备解锁函数指针,需定义 LFS_THREADSAFE 宏才可用,若不需要多线程操作可不赋值void *read_buffer可选参数。读取静态缓冲区指针。若目标MCU不支持动态分配堆内存的话,就需要定义这个参数,用于在栈内分配静态变量void *prog_buffer可选参数。写入静态缓冲区指针。若目标MCU不支持动态分配堆内存的话,就需要定义这个参数,用于在栈内分配静态变量void *lookahead_buffer可选参数。先行分配静态缓冲区指针,需要32bit对齐。若目标MCU不支持动态分配堆内存的话,就需要定义这个参数,用于在栈内分配静态变量
下面是我的lfs_config配置表
3.1、block_count我用了宏定义,这个大小根据你分配内存大小给定,我分配的littlefs的内存大小是2600K,所以INTERNALFLASH_LFS_MAXSIZE是650,其中cache_size的大小和block_size、read_size的大小成比例,否则运行lfs的时候就会出现错误的问题
3.2、在单线程的情况下lock和unlock可以不适配,多线程的时候,建议适配,因为会出现抢占资源,导致不必要的错误。
3.3、.read、.prog、.erase 、.sync 、.lock 、.unlock 适配
注:因为flash中有存放了你的程序,不是整块芯片都用作littlefs文件系统,所以INTERNALFLASH_STARTADDR是我内存已经规划好相应的地址,需要偏移了指定大小的地址之后才开始被littlefs使用的;
.read
.prog
.erase
.sync
.lock
.unlock
4、测试接口是否适配OK?
4.1、lfs初始化
4.2、测试函数接口
/*
* @brief : 测试little fs系统接口用例
* @param :
* @date : 2022.07.07
* @author:
* @remark:
*/
void lfs_test(void)
{
int err;
/*内部文件系统测试*/
printf("*内部文件系统测试*\r\n");
// mount the filesystem
err = lfs_mount(&lfs_sosInternalFlash, &intCfg);
// reformat if we can't mount the filesystem
// this should only happen on the first boot
if(err){
printf("err2=%d\r\n", err);
lfs_format(&lfs_sosInternalFlash, &intCfg);
lfs_mount(&lfs_sosInternalFlash, &intCfg);
}
// read current count
uint32_t boot_count1 = 0;
lfs_file_open(&lfs_sosInternalFlash, &lfs_file_internalFlash, "boot_count1", LFS_O_RDWR | LFS_O_CREAT);
lfs_file_read(&lfs_sosInternalFlash, &lfs_file_internalFlash, &boot_count1, sizeof(boot_count1));
printf("ID:%d\r\n", lfs_file_internalFlash.id);
// update boot count
boot_count1 += 1;
lfs_file_rewind(&lfs_sosInternalFlash, &lfs_file_internalFlash);
lfs_file_write(&lfs_sosInternalFlash, &lfs_file_internalFlash, &boot_count1, sizeof(boot_count1));
// remember the storage is not updated until the file is closed successfully
lfs_file_close(&lfs_sosInternalFlash, &lfs_file_internalFlash);
uint32_t txt1 = 0;
lfs_file_open(&lfs_sosInternalFlash, &lfs_file_internalFlash, "txt1", LFS_O_RDWR | LFS_O_CREAT);
lfs_file_read(&lfs_sosInternalFlash, &lfs_file_internalFlash, &txt1, sizeof(txt1));
printf("ID:%d\r\n", lfs_file_internalFlash.id);
// update boot count
txt1 += 1;
lfs_file_rewind(&lfs_sosInternalFlash, &lfs_file_internalFlash);
lfs_file_write(&lfs_sosInternalFlash, &lfs_file_internalFlash, &txt1, sizeof(txt1));
// remember the storage is not updated until the file is closed successfully
lfs_file_close(&lfs_sosInternalFlash, &lfs_file_internalFlash);
// release any resources we were using
lfs_unmount(&lfs_sosInternalFlash);
// print the boot count
printf("boot_count1: %d\r\n", boot_count1);
printf("txt1: %d\r\n", txt1);
printf("\r\n");
/*外部文件系统测试*/
printf("*外部文件系统测试*\r\n");
// mount the filesystem
err = lfs_mount(&lfs_sosExternalFlash, &extCfg);
// reformat if we can't mount the filesystem
// this should only happen on the first boot
if(err){
printf("err1=%d\r\n", err);
lfs_format(&lfs_sosExternalFlash, &extCfg);
lfs_mount(&lfs_sosExternalFlash, &extCfg);
}
// read current count
uint32_t boot_count2 = 0;
lfs_file_open(&lfs_sosExternalFlash, &lfs_file_externalFlash, "boot_count2", LFS_O_RDWR | LFS_O_CREAT);
lfs_file_read(&lfs_sosExternalFlash, &lfs_file_externalFlash, &boot_count2, sizeof(boot_count2));
printf("ID:%d\r\n", lfs_file_externalFlash.id);
// update boot count
boot_count2 += 1;
lfs_file_rewind(&lfs_sosExternalFlash, &lfs_file_externalFlash);
lfs_file_write(&lfs_sosExternalFlash, &lfs_file_externalFlash, &boot_count2, sizeof(boot_count2));
// remember the storage is not updated until the file is closed successfully
lfs_file_close(&lfs_sosExternalFlash, &lfs_file_externalFlash);
uint32_t txt2 = 0;
lfs_file_open(&lfs_sosExternalFlash, &lfs_file_externalFlash, "txt2", LFS_O_RDWR | LFS_O_CREAT);
lfs_file_read(&lfs_sosExternalFlash, &lfs_file_externalFlash, &txt2, sizeof(txt2));
printf("ID:%d\r\n", lfs_file_externalFlash.id);
// update boot count
txt2 += 1;
lfs_file_rewind(&lfs_sosExternalFlash, &lfs_file_externalFlash);
lfs_file_write(&lfs_sosExternalFlash, &lfs_file_externalFlash, &txt2, sizeof(txt2));
// remember the storage is not updated until the file is closed successfully
lfs_file_close(&lfs_sosExternalFlash, &lfs_file_externalFlash);
// release any resources we were using
lfs_unmount(&lfs_sosExternalFlash);
// print the boot count
printf("boot_count2: %d\r\n", boot_count2);
printf("txt2: %d\r\n", txt2);
printf("\r\n");
}
测试通过,读写和擦除都正常,littlefs算移植完成
5、注意事项
5.1、在创建文件的时候,如果该文件已经创建过了,你再次创建同样的名字,没有加LFS_O_EXCL属性,文件同样会创建成功,但是不会返回文件已存在的错误码,创建的次数越多,后面文件系统内存会出现泄漏,最后导致写入文件都会报内存不足的问题,需要注意
5.2、内外文件系统需要自己注意函数的调用,不要混乱使用。
5.3、lfs_config配置表看自己的需要的大小适配,不过如果配置不对,很容易报错,推荐用的我的配置表。
5.4、挂载文件系统时候,如果没有实现read、erase和write的操作,会出现错误码:-84;
5.5、没有挂载成功,调用卸载函数会导致代码卡死。
6、总结
这篇文件断断续续写的,有不足的地方希望一起探讨,后续有新的发现和注意事项,我会持续更新。