littlefs的使用技巧和性能分析

1 配置分析

参数

说明

Read_size

从flash中读取数据的最小size,并且起始地址需要按照这个size对齐

prog_size

往flash中写入数据的最小size,并且起始地址需要按照这个size对齐。

block_size

文件系统中物理block的size。

block_count

文件系统中物理block的个数。

block_cycles

元数据块擦写均衡的次数,当block完成block_cycles次擦写操作之后,将重新申请新的block,源数据导入到新的block,继续擦写操作。

cache_size

文件系统的读写时,数据在内存中的缓存大小,文件系统初始化的时候在内存中申请了一个写cache和读cache,open文件的时候还申请了一个文件cache,他们都是cache_size大小。

lookahead_size

空闲块滑窗的大小,littlefs用一个滑窗来管理所有的空闲块,并且用一个bit是否为0来代表对应的物理block是否空闲,当空闲块不够的时候,会进行滑动操作,一次滑动lookahead_size,并且遍历整个文件系统,判断物理block是否被使用,从而给滑窗的bit状态置位正确的值。

2 性能分析

2.1 环境信息

2.1.1硬件信息

通过使用rthread的fal bench命令,对手头的板子(cpu主频160MHz、SPI时钟主频20MHz)进行flash的性能测试,得出的结论如下:

操作类型

大小(字节)

时间(ms)

擦除

4096

22

4096

20

4096

4

2.1.2 littlefs配置信息

参数

大小

Read_size

256

prog_size

256

block_size

4096

block_count

1792

block_cycles

10

cache_size

4096

lookahead_size

128

2.1.3 测试用例说明

  • 使用rtthread操作系统加载littlefs
  • 操作系统的systick配置为5ms
  • 一次写操作的大小为128字节
  • 顺序写的文件大小为32kByte
  • 覆盖写的文件大小为8192Byte
  • 操作的文件是根目录下的文件,且仅有此文件
  • 操作之前,先格式化一把文件系统,减少历史碎片的干扰

2.2 顺序写性能测试

2.2.1 写性能分析

 

从测试可以看出来,顺序写分为两个阶段:

  1. 写入到元数据内部,时间开销比较小;
  2. 写入到专门的物理block里面,时间开销主要在于write file的erase flash操作以及close file的write flash操作。

2.2.2 flash操作分析

从上面的截图可以看到,每隔16个包,就会有一个比较大的时间开销,这个时候其实是文件系统进行了一次compact操作,此时的性能很差,我们把flash的相关日志打印出来,分析性能消耗在哪里:

 

从测试可以看出来,compact操作的时间开销主要在于close的时候,其执行了erase flash一次、write flash一个block size,read flash 近50k数据,时间开销差不多分别为22ms、20ms、51ms,接近90ms。

2.3 覆盖写性能分析

2.3.1 写性能分析

 

从测试可以看出来:

  1. write的时候,读取一个块的数据,擦一个块的数据,写当前块offset大小的数据到对应的pcache里面,write的时间在变大,因为offset在变大,每次在write的时候,需要读取的数据变大了;
  2. close的时候,擦两个块的数据,读剩下的完整的数据,写剩下的完整的数据,close的时间在变小,因为write过程中读取的数据变多了,那么close的时候要读取的数据就变少了。

2.3.2 flash操作分析

覆盖写的性能,异常的差,把flash的操作打印打开(因为耗时最长的地方肯定是对flash的操作),深入分析一次完整的写入流程,对flash有哪些操作:

 

close的时间达到了170ms,和顺序写相比,高了很多倍。其主要的耗时在:2次erase操作,3次write 4096字节的操作, 1次write 256字节的操作,多次read操作,读取了近30k的数据。时间计算为: 22*2 + 20*3 + 20/16 + 8 *4 = 137ms,线程切换以及其他的运算操作有接近30ms了?

 

2.4 性能总结

2.4.1写抖动

不论和顺序写还是覆盖写,存在抖动,抖动的时间开销主要在于:

  1. 元数据块的压缩过程,读取flash的次数太多;
  2. 压缩不了后,进行分片,有多余的擦除操作和压缩操作;
  3. block_cycles达到上限之后,会申请新的元数据对,并有压缩的过程

2.4.2 覆盖写性能差

覆盖写的时候,文件越大,写的位置越前面,性能越差。

3 使用注意事项

3.1 配置注意事项

  1. read_size和prog_size不要太大了,不然会频繁的触发压缩过程。
  2. 在保证内存的情况下,lookahead_size越大越好,因为lookahead窗口的建立过程消耗的时间,不受lookahead_size的影响。lookahead窗口用1bit代表1个block size,其实对内存的开销也还好。

(3)block_cycles越小,擦写均衡的效果越好,但是写入抖动的频率会越高。

(4)cache_size一般和block size对齐就好了。

3.2 实际使用注意事项

  1. 不要同时open太多文件,每open一个文件,没有close的情况下,会增加cache_size大小的内存开销。
  2. write完数据要尽快的close,因为只有close操作完成了,异常掉电后,write的数据才能被文件系统正确检索。
  3. 一个目录下面不要放太多的文件,不然压缩的过程(write的抖动)时间会比较大。
  4. 文件的大小尽可能小于1个block size,如果大于1个block size,那么尽可能按照ctz特性来设计文件大小,不然可能性能比想象的差,例如文件大小为8192(占据三个ctz block),比文件大小为8000(占据两个ctz block),要差很多。

(5)对写时延要求比较高的文件,尽量放到一级目录,写时间会短一些。

历史存档

08-08 01:50:15 D/lfs_test lfs_test: -----start open----

读取两个pair blockresv,选择大的那个,结果选择了1199,然后把tag全部读取出来

08-08 01:50:15 I/NO_TAG lfs_srv: read 1198 256

08-08 01:50:15 I/NO_TAG lfs_srv: read 1199 256

08-08 01:50:15 I/NO_TAG lfs_srv: read 1199 3840

08-08 01:50:15 D/lfs_test lfs_test: -----start lseek----

lseek啥都没干

08-08 01:50:15 D/lfs_test lfs_test: -----start write----

找到偏移对应的ctz block,擦除一个新的块,读取偏移大小的数据,写入到新的块对应的pcache里面

08-08 01:50:15 I/NO_TAG lfs_srv: read 1396 256

08-08 01:50:15 I/NO_TAG lfs_srv: erase 1397

08-08 01:50:15 I/NO_TAG lfs_srv: read 1394 1280

08-08 01:50:15 D/lfs_test lfs_test: -----start close----

再次找到偏移对应的ctz block,把剩余的2816字节读取了,写入到1397对应的block,写完之后,再全部读取出来,校验一把

08-08 01:50:15 I/NO_TAG lfs_srv: read 1396 256

08-08 01:50:15 I/NO_TAG lfs_srv: read 1394 2816

08-08 01:50:15 I/NO_TAG lfs_srv: write 1397 0

08-08 01:50:15 I/NO_TAG lfs_srv: read 1397 4096

pos偏移到下一个block了,找到pos对应的ctz block,把整个block全部读取出来,擦除一个新的块1398,然后把数据写入1398,再全部读取出来,校验一把

08-08 01:50:15 I/NO_TAG lfs_srv: read 1396 256

08-08 01:50:15 I/NO_TAG lfs_srv: read 1395 4096

08-08 01:50:15 I/NO_TAG lfs_srv: erase 1398

08-08 01:50:15 I/NO_TAG lfs_srv: write 1398 0

08-08 01:50:15 I/NO_TAG lfs_srv: read 1398 4096

最后一个块的位置(ctz的链表头部),本来就确定了的,在open的时候就有了,把整个block全部读取出来,擦除一个新的块1399,然后把数据写入1399,再全部读取出来,校验一把(校验只校验了256个字节,因为实际关注的只有这么多数据)

08-08 01:50:15 I/NO_TAG lfs_srv: read 1396 4096

08-08 01:50:15 I/NO_TAG lfs_srv: erase 1399

08-08 01:50:15 I/NO_TAG lfs_srv: read 1398 256

08-08 01:50:15 I/NO_TAG lfs_srv: read 1396 4096

08-08 01:50:15 I/NO_TAG lfs_srv: write 1399 0

08-08 01:50:15 I/NO_TAG lfs_srv: read 1399 256

提交新的ctz tag信息到元数据块,并且写完之后再校验一把,本次写入了256字节

08-08 01:50:15 I/NO_TAG lfs_srv: start commit

08-08 01:50:15 I/NO_TAG lfs_srv: read 1199 256

08-08 01:50:15 I/NO_TAG lfs_srv: start sync

08-08 01:50:15 I/NO_TAG lfs_srv: write 1199 1536

08-08 01:50:15 I/NO_TAG lfs_srv: read 1199 256

08-08 01:50:15 D/lfs_test lfs_test: file_system_info: cover_write offset 1280 cost 10 0 35 195 ms

close的时间达到了170ms,和顺序写相比,高了很多倍。其主要的耗时在:2次erase操作,3次write 4096字节的操作, 1次write 256字节的操作,多次read操作,读取了近30k的数据。时间计算为: 22*2 + 20*3 + 20/16 + 8 *4 = 137ms,线程切换以及其他的运算操作有接近30ms了?

  • 6
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
littlefs 是一个用于嵌入式系统的轻量级文件系统,功能简单但高效。下面我将对其源码进行分析。 首先,littlefs 的源码结构清晰,主要包含了文件系统的核心逻辑、存储管理、文件操作等模块。在核心逻辑部分,源码实现了文件系统的初始化、格式化、挂载等功能,同时提供了基本的文件操作接口,如创建、打开、读取、写入和删除文件等。它还包含了一些实用的功能,比如目录操作、文件描述符的管理、簇(cluster)和扇区(sector)的管理等。 在存储管理方面,源码使用了位图(bitmap)来管理簇的分配情况,位图的每一位对应着簇的状态。这样,当需要分配或释放簇时,只需修改相应的位图即可。此外,源码还实现了一个缓存区(buffer)来加速读写操作,可以提高文件的访问速度。 在文件操作方面,源码使用了块(block)作为文件的基本单位,块的大小通过宏定义进行设置。它使用了文件控制块(file control block)来管理文件的元数据,包括文件名、长度、属性等。通过文件控制块,可以方便地对文件进行操作。 对于源码的性能方面,littlefs 采用了写前日志(write-ahead log)的方式来提高写入操作的性能和可靠性。在写入文件时,会先写入日志,然后再写入实际的数据块。这样即使发生意外断电等情况,也能保证数据的完整性。 总的来说,littlefs 的源码分析表明它是一个功能简单但高效的嵌入式文件系统。其清晰的结构和良好的性能使其成为嵌入式系统中的一个理想选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值