WhyCan Forum(哇酷开发者社区)

我们习惯了"有问题百度一下", 感谢为中文互联网持续输出优质内容的各位老铁们。 QQ: 516333132, 微信: whycan_cn (哇酷网/挖坑网/填坑网) service@whycan.cn

您尚未登录。

#1 2017-12-26 14:01:30

晕哥
管理员
注册时间: 2017-09-06
累计积分: 9,188

LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

LittleFS - 一个高度完整的嵌入式文件系统

拥有小巧灵活的文件系统对许多物联网设备至关重要。使用文件系统并将其与正确的存储技术(如外部闪存或SD卡)配对可能很困难。Mbed操作系统使文件系统的组合变得简单。Mbed OS 5.7既支持FAT文件系统,又引入了高度集成的嵌入式文件系统。这包括对没有自己的磨损平衡控制器的闪存芯片的磨损平衡支持。

LittleFS  - 一个高度完整的嵌入式文件系统

将数据存储在嵌入式设备上非常有用:无论是配置文件,传感器信息批量还是新的固件更新。您可以抓取一些非易失性存储器(如EEPROM或SD卡),并将这些数据写入随机闪存页面,但容易出错。没有关于闪存数据的概述,不保证您不覆盖其他数据使用的页面,并且一遍又一遍地写入同一闪存页面对于耐久性是不利的。所以,你需要一个文件系统(自1965年左右),为您管理这一切。

嵌入式系统和物联网设备的文件系统有一些额外的要求:

  1. 断电恢复能力 - 要求文件系统保持一致,并将数据刷新到底层存储。

  2. 平均磨损 - 通常情况下,存储支持每块数量有限的擦除,因此使用整个存储设备对于可靠性非常重要。

  3. 微小的占地面积 - 物联网设备受到ROM和RAM的限制。占地面积小可以节省资金。

市场上有许多商业和开源嵌入式文件系统,但都没有达到我们的设计代码大小,功能或可靠性要求,这对于成功部署物联网设备至关重要。我们正在发布高完整性嵌入式文件系统,这是一个为嵌入式系统设计的小型故障安全文件系统。它作为Mbed OS 5.7的一部分以早期版本形式提供,并作为非Mbed系统的独立C库提供。它是在从GitHub的允许Apache 2.0许可和可用许可这里。

高完整性的嵌入式文件系统与FAT文件系统

Mbed OS长久以来都支持由安全数字卡或NOR闪存支持的FAT文件系统。FAT文件系统于2010年首次引入,作为外部库,然后作为Mbed OS 5.5核心操作系统的一部分进行集成。由于FAT文件系统对从DOS 6到Mac OS 10.13等其他操作系统的广泛支持和兼容性,FAT文件系统仍然是一个重要的功能。在许多物联网使用案例中,需要具有电源丢失弹性,数据完整性和更长的存储器使用寿命。对于很多物联网设备,与传统的FAT文件系统相比,高完整性嵌入式文件系统是一个更好的选择。

RAM / ROM大小
littlefs1.png

fatfs与littlefs RAM + ROM大小

Littlebed是Mbed OS中高度集成的嵌入式文件系统,可以在有限的RAM和ROM下工作。它避免了递归,将动态内存限制为可配置的缓冲区,并且不需要将整个存储块存储在RAM中。通过专注于一小组多用途数据结构,这个高度集成的嵌入式文件系统使用比FAT少的13K ROM和少于4K的RAM。

失电恢复能力
littlefs3.gif

fatfs引导计数应用程序 littlefs引导计数应用程序

我们为可能有随机电源故障的系统设计了这个文件系统。它具有强大的copy-on-write保证,并且磁盘上的存储总是保持有效状态。在上面的gif中,您可以看到FAT文件系统损坏的速度以及该文件系统的弹性。有关更多详细信息,请参阅我们用于测试的程序。

磨损均衡
littlefs4.gif

fatfs vs littlefs平均分

大多数嵌入式设备使用的存储芯片支持每个扇区有限的一组擦除。如果您没有支持磨损平衡的存储控制器,则嵌入式设备的使用寿命可能会受到影响。嵌入式文件系统提供动态损耗均衡,以在整个闪存的整个区域内跨扇区传播数据。这可以防止应用程序频繁地写入相同的扇区。失败模式是文件系统使用和文件系统中存储的数据量的组合。平均算法并不完美,但具有扩展闪存存储容量的优点,增加了设备的使用寿命。

gif显示了高完整性嵌入式文件系统与FAT文件系统在静态数据3K和动态数据3K之间的比较。要查看不同配置的磨损级别行为,请参阅此交互式仿真。

如何开始使用高完整性嵌入式文件系统?
任何实现BlockDevice接口的设备都可以在Mbed OS中托管文件系统。您可以更改基于BlockDevice的存储驱动程序,而无需更改您的应用程序或库代码。

开始:

1. 选择一个BlockDevice为您的闪存实现API 的驱动程序: DataFlash,SD卡,SFDP SPI Flash或实现您自己的。
2. 初始化并挂载文件系统:

BlockDevice bd =  / *获取块设备* / ;

//第一个参数是挂载点,例如文件将在/ fs /下可用,第二个指向块设备的指针
LittleFileSystem fs(“fs”,&bd);

使用POSIX文件系统调用来读取和写入文件(例如fread,fopen等等)。
你可以在这里找到一个示例程序。如果您的开发板上没有任何存储空间,您可以使用HeapBlockDevice来测试这个高度集成的嵌入式文件系统,HeapBlockDevice将数据存储在内存中(但不会持久存储)。

测试
由于文件系统是设备固件的重要组成部分,因此这个高度集成的嵌入式文件系统带有一套完整的单元和集成测试,Mbed OS测试服务器场每天运行该测试。要运行这些测试,您需要mbed CLI:

功能测试 - mbed test -n 'features-filesystem-littlefs-tests-filesystem-*'。
重新测试 - mbed test -n 'features-filesystem-littlefs-tests-filesystem_retarget-*'。
磨平测试 - mbed test -n 'features-filesystem-littlefs-tests-filesystem_recovery-wear_leveling'。
模拟功率弹性测试mbed test -n 'features-filesystem-littlefs-tests-filesystem_recovery-resilience'。
硬件功率弹性测试mbed test -n 'features-filesystem-littlefs-tests-filesystem_recovery-resilience_functional'。
另外,我们建立了浸泡测试来验证文件系统在真实的硬件上正确处理闪存耗尽失败。

-

Jan Jongboom是Arm的物联网开发人员,他看到太多损坏的SD卡。Chris Haster是Arm的一名软件工程师。


参考链接:LittleFS - 一个高度完整的嵌入式文件系统
arm官方项目地址:armmbed/mbed-littlefs
原作者项目地址:geky/littlefs

离线

#2 2017-12-26 14:20:27

goodgoodstudy
会员
注册时间: 2017-09-15
累计积分: 61

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

看起来不错,去下载学习学习!

离线

#3 2017-12-26 14:57:28

daydayup
会员
注册时间: 2017-10-09
累计积分: 343

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

看了一下还是不会, 求带

离线

#4 2017-12-26 15:08:46

晕哥
管理员
注册时间: 2017-09-06
累计积分: 9,188

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

我试了一下 geky/littlefs-fuse,还挺顺利。

1. 克隆项目: git clone https://github.com/geky/littlefs-fuse.git
2. 安装用户空间文件系统库: sudo apt-get install libfuse-dev
3. libfuse版本不能低于2.6,使用命令查看版本,我的是2.9: fusermount -V
4. 执行make, 生成lfs 的可执行文件。
5. 执行命令:

sudo chmod a+rw /dev/loop0                  # make loop device user accessible
dd if=/dev/zero of=image bs=512 count=2048  # create a 1MB image
losetup /dev/loop0 image                    # attach the loop device

5. 格式化文件系统:

./lfs --format /dev/loop0

6. 挂载文件系统:

mkdir mount
./lfs /dev/loop0 mount

7. 然后现在进入mount目录就像操作硬盘上的文件系统操作lfs文件系统了.

8. 如果想看到调试信息:

./lfs -d /dev/loop0 mount


运行输出:

littlefs@ubuntu:/tmp/littlefs-fuse$ ./lfs -d /dev/loop0 mount
FUSE library version: 2.9.2
nullpath_ok: 0
nopath: 0
utime_omit_ok: 0
unique: 1, opcode: INIT (26), nodeid: 0, insize: 56, pid: 0
INIT: 7.23
flags=0x0003f7fb
max_readahead=0x00020000
   INIT: 7.19
   flags=0x00000039
   max_readahead=0x00020000
   max_write=0x00020000
   max_background=0
   congestion_threshold=0
   unique: 1, success, outsize: 40
unique: 2, opcode: LOOKUP (1), nodeid: 1, insize: 47, pid: 2588
LOOKUP /.Trash
getattr /.Trash
   unique: 2, error: -2 (No such file or directory), outsize: 16
unique: 3, opcode: LOOKUP (1), nodeid: 1, insize: 52, pid: 2588
LOOKUP /.Trash-1000
getattr /.Trash-1000
   unique: 3, error: -2 (No such file or directory), outsize: 16

离线

#5 2017-12-26 15:55:11

daydayup
会员
注册时间: 2017-10-09
累计积分: 343

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

不错, 坐等大神移植到STM32或者ARM9上.

离线

#6 2017-12-28 11:19:43

晕哥
管理员
注册时间: 2017-09-06
累计积分: 9,188

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

QQ20171228111910.png

电脑上用一块缓冲区创建模拟flash,使用littlefs创建文件夹, 读写文件成功!

离线

#7 2018-06-14 13:28:33

beyondabcd
会员
注册时间: 2018-06-12
累计积分: 44

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

有详细说明吗

离线

#8 2018-06-17 21:08:25

达克罗德
会员
注册时间: 2018-04-10
累计积分: 894

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

是针对nor还是nand?

离线

#9 2018-06-17 21:40:56

晕哥
管理员
注册时间: 2017-09-06
累计积分: 9,188

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

达克罗德 说:

是针对nor还是nand?

代码没有oob,应该真对nor.

离线

#10 2018-08-06 22:08:40

达克罗德
会员
注册时间: 2018-04-10
累计积分: 894

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

如何实现标准posix接口?

离线

#11 2018-08-07 08:26:15

超级萌新
会员
注册时间: 2018-05-04
累计积分: 406

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

http://www.gnu.org/software/libc/manual/html_node/I_002fO-on-Streams.html

是指实现这些 fopen, fclose, fwrite, fread 这些接口吧?

离线

#12 2018-08-07 08:31:38

晕哥
管理员
注册时间: 2017-09-06
累计积分: 9,188

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

https://github.com/ARMmbed/mbed-os/blob/master/features/filesystem/littlefs/TESTS/filesystem_retarget/interspersed/main.cpp

已经整合进去了, 但是没看明白如何整合的, 反正就是使用了 libc 一样的接口读写文件.

离线

#13 2018-08-07 08:38:04

达克罗德
会员
注册时间: 2018-04-10
累计积分: 894

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

超级萌新 说:

http://www.gnu.org/software/libc/manual/html_node/I_002fO-on-Streams.html

是指实现这些 fopen, fclose, fwrite, fread 这些接口吧?

是的,mbed似乎是实现了标准接口,但是我没找到实现。littlefs自己的接口只是posix like但不是直接可用

离线

#14 2018-08-07 08:42:24

达克罗德
会员
注册时间: 2018-04-10
累计积分: 894

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

晕哥 说:

https://github.com/ARMmbed/mbed-os/blob/master/features/filesystem/littlefs/TESTS/filesystem_retarget/interspersed/main.cpp

已经整合进去了, 但是没看明白如何整合的, 反正就是使用了 libc 一样的接口读写文件.

是,看了半天都没发现怎么实现的

离线

#15 2018-10-17 09:58:39

伽美什
会员
注册时间: 2018-10-17
累计积分: 2

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

写flash的速度有测试过么,我这边写的好慢但是找不出来原因

离线

#16 2018-10-17 10:30:18

lilo
会员
注册时间: 2017-10-15
累计积分: 214

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

伽美什 说:

写flash的速度有测试过么,我这边写的好慢但是找不出来原因

你用什么SOC, 我用 f1c100s裸奔上 littlefs 没什么问题, 所有没有测速度。

看看你的spi速率.

离线

#17 2018-10-17 11:45:20

伽美什
会员
注册时间: 2018-10-17
累计积分: 2

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

lilo 说:
伽美什 说:

写flash的速度有测试过么,我这边写的好慢但是找不出来原因

你用什么SOC, 我用 f1c100s裸奔上 littlefs 没什么问题, 所有没有测速度。

看看你的spi速率.

华为的liteos,用littlefs写4M要30400ms,驱动用了23776,同样写4Mjffs就只要826ms,调用方式都是一样的O_o

离线

#18 2018-10-17 11:50:44

basicdev
会员
注册时间: 2017-10-02
累计积分: 159

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

伽美什 说:
lilo 说:
伽美什 说:

写flash的速度有测试过么,我这边写的好慢但是找不出来原因

你用什么SOC, 我用 f1c100s裸奔上 littlefs 没什么问题, 所有没有测速度。

看看你的spi速率.

华为的liteos,用littlefs写4M要30400ms,驱动用了23776,同样写4Mjffs就只要826ms,调用方式都是一样的O_o

这个差距有点夸张了。。。

离线

#19 2018-11-20 23:30:39

llhqqwang
会员
注册时间: 2018-11-20
累计积分: 4

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

伽美什 说:
lilo 说:
伽美什 说:

写flash的速度有测试过么,我这边写的好慢但是找不出来原因

你用什么SOC, 我用 f1c100s裸奔上 littlefs 没什么问题, 所有没有测速度。

看看你的spi速率.

华为的liteos,用littlefs写4M要30400ms,驱动用了23776,同样写4Mjffs就只要826ms,调用方式都是一样的O_o


请问您是如何测试读写速度的,我这里测的更慢,打开debug发现有很多擦写,在mbed os上的

离线

#20 2018-12-01 09:05:24

gotofly21
会员
注册时间: 2018-11-12
累计积分: 4

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

为什么我要拷进去1M以上文件就死机,stm32,还有就是不太适合sd,每次写只写512字节,速度太慢太慢

离线

#21 2018-12-03 13:24:37

演技担当黄晓明
会员
注册时间: 2017-10-17
累计积分: 113

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

围观

离线

#22 2018-12-10 23:08:10

llhqqwang
会员
注册时间: 2018-11-20
累计积分: 4

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

gotofly21 说:

为什么我要拷进去1M以上文件就死机,stm32,还有就是不太适合sd,每次写只写512字节,速度太慢太慢

是如何写的?每次写512字节有记过时间吗?会耗时多久,我这些1024字节要150ms左右

离线

#23 2018-12-11 09:58:06

gotofly21
会员
注册时间: 2018-11-12
累计积分: 4

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

sd卡写有个写入时间(很长,ms级),你传多个块(好像个数也有个限制)(一个块512)和一个块,后面等待的写入时间其实差不多(其实数据传输时间并不多)。所以每次写越多块,速度越快。以前拿示波器看过。 搞了一周,放弃了。写几k长的文件还可以,大文件老是死机。不好查。说明也太少,实在搞不懂那个 ahead_buffer是做什么的。

最近编辑记录 gotofly21 (2018-12-11 10:01:47)

离线

#24 2018-12-11 22:33:22

llhqqwang
会员
注册时间: 2018-11-20
累计积分: 4

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

gotofly21 说:

sd卡写有个写入时间(很长,ms级),你传多个块(好像个数也有个限制)(一个块512)和一个块,后面等待的写入时间其实差不多(其实数据传输时间并不多)。所以每次写越多块,速度越快。以前拿示波器看过。 搞了一周,放弃了。写几k长的文件还可以,大文件老是死机。不好查。说明也太少,实在搞不懂那个 ahead_buffer是做什么的。

那个head_buffer用处不太大,只是用来寻找下一个block 的作用,个人理解。 我这写1k字节数据要100-200ms,blocksize是4096,因为写的是spiflash,所以这值最小是4096.读和写的大小是256字节。

离线

#25 2018-12-11 22:34:44

llhqqwang
会员
注册时间: 2018-11-20
累计积分: 4

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

gotofly21 说:

sd卡写有个写入时间(很长,ms级),你传多个块(好像个数也有个限制)(一个块512)和一个块,后面等待的写入时间其实差不多(其实数据传输时间并不多)。所以每次写越多块,速度越快。以前拿示波器看过。 搞了一周,放弃了。写几k长的文件还可以,大文件老是死机。不好查。说明也太少,实在搞不懂那个 ahead_buffer是做什么的。

大文件死机可以查看下是不是溢出了,可以分次进行写操作。

离线

#26 2018-12-13 09:07:49

gotofly21
会员
注册时间: 2018-11-12
累计积分: 4

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

llhqqwang 说:
gotofly21 说:

sd卡写有个写入时间(很长,ms级),你传多个块(好像个数也有个限制)(一个块512)和一个块,后面等待的写入时间其实差不多(其实数据传输时间并不多)。所以每次写越多块,速度越快。以前拿示波器看过。 搞了一周,放弃了。写几k长的文件还可以,大文件老是死机。不好查。说明也太少,实在搞不懂那个 ahead_buffer是做什么的。

大文件死机可以查看下是不是溢出了,可以分次进行写操作。

都不算大,才几十K,几百K。主要死在链表上 static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir,
        const struct lfs_region *regions, int count)


   for (lfs_dir_t *d = lfs->dirs; d; d = d->next),   d->next变野指针了。搞不清楚

离线

#27 2019-01-18 20:28:24

jw__liu
会员
注册时间: 2019-01-18
累计积分: 40

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

mark

离线

#28 2019-01-28 12:49:56

xinyu_khan
会员
注册时间: 2019-01-15
累计积分: 31

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

厉害了

离线

#29 2019-03-09 17:47:40

tink
会员
注册时间: 2019-03-09
累计积分: 32

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

mark

离线

#30 2019-05-05 12:00:24

晕哥
管理员
注册时间: 2017-09-06
累计积分: 9,188

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

这个工具能生成文件系统镜像文件:

https://github.com/whitecatboard/Lua-RTOS-ESP32/blob/master/components/mklfs/src/mklfs.c

./mklfs -c target -b 1024 -r 1024 -p 1024 -s 1048576 -i out.img

离线

#31 2019-05-05 16:30:07

晕哥
管理员
注册时间: 2017-09-06
累计积分: 9,188

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

发现这个不错: https://github.com/earlephilhower/mklittlefs

https://github.com/qq516333132/mklittlefs/blob/master/main.cpp

//
//  main.cpp
//  make_littlefs
//
//  Created by Earle F. Philhower, III on December 15, 2018
//  Derived from mkspiffs:
//  | Created by Ivan Grokhotkov on 13/05/15.
//  | Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
//
#define TCLAP_SETBASE_ZERO 1

#include <iostream>
#include <vector>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cstring>
#include <string>
#include <memory>
#include <cstdlib>
#include "tclap/CmdLine.h"
#include "tclap/UnlabeledValueArg.h"

extern "C" {
#define LFS_NAME_MAX 32
#include "littlefs/lfs.h"
}

#ifndef PATH_MAX
#define PATH_MAX 512
#endif

static std::vector<uint8_t> s_flashmem;

static std::string s_dirName;
static std::string s_imageName;
static uint32_t s_imageSize;
static uint32_t s_pageSize;
static uint32_t s_blockSize;

enum Action { ACTION_NONE, ACTION_PACK, ACTION_UNPACK, ACTION_LIST };
static Action s_action = ACTION_NONE;

static int s_debugLevel = 0;
static bool s_addAllFiles;

// Unless -a flag is given, these files/directories will not be included into the image
static const char* ignored_file_names[] = {
    ".DS_Store",
    ".git",
    ".gitignore",
    ".gitmodules"
};

int lfs_flash_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size)
{
  memcpy(buffer, &s_flashmem[0] + c->block_size * block + off, size);
  return 0;
}

int lfs_flash_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size)
{
  memcpy(&s_flashmem[0] + block * c->block_size + off, buffer, size);
  return 0;
}

int lfs_flash_erase(const struct lfs_config *c, lfs_block_t block)
{
  memset(&s_flashmem[0] + block * c->block_size, 0, c->block_size);
  return 0;
}

int lfs_flash_sync(const struct lfs_config *c) {
  (void) c;
  return 0;
}


// Implementation

static lfs_t s_fs;
static lfs_config s_cfg;
bool s_mounted = false;

void setLfsConfig()
{
  memset(&s_fs, 0, sizeof(s_fs));
  memset(&s_cfg, 0, sizeof(s_cfg));
  s_cfg.read  = lfs_flash_read;
  s_cfg.prog  = lfs_flash_prog;
  s_cfg.erase = lfs_flash_erase;
  s_cfg.sync  = lfs_flash_sync;

  s_cfg.read_size = s_blockSize;
  s_cfg.prog_size = s_blockSize;
  s_cfg.block_size = s_blockSize;
  s_cfg.block_count = s_flashmem.size() / s_blockSize;
  s_cfg.cache_size = s_blockSize;
  s_cfg.lookahead_size = 128;
}

int littlefsTryMount() {
  setLfsConfig();
  int ret = lfs_mount(&s_fs, &s_cfg);
  if (ret) {
    s_mounted = false;
    return -1;
  }
  s_mounted = true;
  return 0;
}

bool littlefsMount(){
  if (s_mounted)
    return true;
  int res = littlefsTryMount();
  return (res == 0);
}

void littlefsUnmount() {
  if (s_mounted) {
    lfs_unmount(&s_fs);
    s_mounted = false;
  }
}

bool littlefsFormat(){
  littlefsUnmount();
  setLfsConfig();
  int formated = lfs_format(&s_fs, &s_cfg);
  if(formated != 0)
    return false;
  return (littlefsTryMount() == 0);
}

int addFile(char* name, const char* path) {
    FILE* src = fopen(path, "rb");
    if (!src) {
        std::cerr << "error: failed to open " << path << " for reading" << std::endl;
        return 1;
    }

    // Make any subdirs required to place this file
    char pathStr[LFS_NAME_MAX+1];
    strcpy(pathStr, name); // Already know path length < LFS_NAME_MAX
    // Make dirs up to the final fnamepart
    char *ptr = strchr(pathStr, '/');
    while (ptr) {
       *ptr = 0;
       lfs_mkdir(&s_fs, pathStr); // Ignore error, we'll catch later if it's fatal
       *ptr = '/';
       ptr = strchr(ptr+1, '/');
    }
    lfs_file_t dst;
    int ret = lfs_file_open(&s_fs, &dst, name, LFS_O_CREAT | LFS_O_TRUNC | LFS_O_WRONLY);
    if (ret < 0) {
        std::cerr << "unable to open '" << name << "." << std::endl;
        return 1;
    }
    // read file size
    fseek(src, 0, SEEK_END);
    size_t size = ftell(src);
    fseek(src, 0, SEEK_SET);

    if (s_debugLevel > 0) {
        std::cout << "file size: " << size << std::endl;
    }

    size_t left = size;
    uint8_t data_byte;
    while (left > 0){
        if (1 != fread(&data_byte, 1, 1, src)) {
            std::cerr << "fread error!" << std::endl;

            fclose(src);
            lfs_file_close(&s_fs, &dst);
            return 1;
        }
        int res = lfs_file_write(&s_fs, &dst, &data_byte, 1);
        if (res < 0) {
            std::cerr << "lfs_write error(" << res << "): ";

            if (res == LFS_ERR_NOSPC) {
                std::cerr << "File system is full." << std::endl;
            } else {
                std::cerr << "unknown";
            }
            std::cerr << std::endl;

            if (s_debugLevel > 0) {
                std::cout << "data left: " << left << std::endl;
            }

            fclose(src);
            lfs_file_close(&s_fs, &dst);
            return 1;
        }
        left -= 1;
    }

    lfs_file_close(&s_fs, &dst);
    fclose(src);

    return 0;
}

int addFiles(const char* dirname, const char* subPath) {
    DIR *dir;
    struct dirent *ent;
    bool error = false;
    std::string dirPath = dirname;
    dirPath += subPath;

    // Open directory
    if ((dir = opendir (dirPath.c_str())) != NULL) {

        // Read files from directory.
        while ((ent = readdir (dir)) != NULL) {

            // Ignore dir itself.
            if ((strcmp(ent->d_name, ".") == 0) || (strcmp(ent->d_name, "..") == 0)) {
                continue;
            }

            if (!s_addAllFiles) {
                bool skip = false;
                size_t ignored_file_names_count = sizeof(ignored_file_names) / sizeof(ignored_file_names[0]);
                for (size_t i = 0; i < ignored_file_names_count; ++i) {
                    if (strcmp(ent->d_name, ignored_file_names[i]) == 0) {
                        std::cerr << "skipping " << ent->d_name << std::endl;
                        skip = true;
                        break;
                    }
                }
                if (skip) {
                    continue;
                }
            }

            std::string fullpath = dirPath;
            fullpath += ent->d_name;
            struct stat path_stat;
            stat (fullpath.c_str(), &path_stat);

            if (!S_ISREG(path_stat.st_mode)) {
                // Check if path is a directory.
                if (S_ISDIR(path_stat.st_mode)) {
                    // Prepare new sub path.
                    std::string newSubPath = subPath;
                    newSubPath += ent->d_name;
                    newSubPath += "/";

                    if (addFiles(dirname, newSubPath.c_str()) != 0)
                    {
                        std::cerr << "Error for adding content from " << ent->d_name << "!" << std::endl;
                    }

                    continue;
                }
                else
                {
                    std::cerr << "skipping " << ent->d_name << std::endl;
                    continue;
                }
            }

            // Filepath with dirname as root folder.
            std::string filepath = subPath;
            filepath += ent->d_name;
            std::cout << filepath << std::endl;

            // Add File to image.
            if (addFile((char*)filepath.c_str(), fullpath.c_str()) != 0) {
                std::cerr << "error adding file!" << std::endl;
                error = true;
                if (s_debugLevel > 0) {
                    std::cout << std::endl;
                }
                break;
            }
        } // end while
        closedir (dir);
    } else {
        std::cerr << "warning: can't read source directory" << std::endl;
        return 1;
    }

    return (error) ? 1 : 0;
}

void listFiles(const char *path) {
    int ret;
    lfs_dir_t dir;
    lfs_info it;

    ret = lfs_dir_open(&s_fs, &dir, path);
    if (ret < 0) {
        std::cerr << "unable to open directory '" << path << "'" << std::endl;
        return;
    }
    while (true) {
        int res = lfs_dir_read(&s_fs, &dir, &it);
        if (res <= 0)
            break;

        // Ignore special dir entries
        if ((strcmp(it.name, ".") == 0) || (strcmp(it.name, "..") == 0)) {
            continue;
        }

        if (it.type == LFS_TYPE_DIR) {
            char newpath[PATH_MAX];
            sprintf(newpath, "%s/%s", path, it.name);
            listFiles(newpath);
        } else {
            std::cout << it.size << '\t' << path << "/" << it.name << std::endl;
        }
    }
    lfs_dir_close(&s_fs, &dir);
}

/**
 * @brief Check if directory exists.
 * @param path Directory path.
 * @return True if exists otherwise false.
 *
 * @author Pascal Gollor (http://www.pgollor.de/cms/)
 */
bool dirExists(const char* path) {
    DIR *d = opendir(path);

    if (d) {
        closedir(d);
        return true;
    }

    return false;
}

/**
 * @brief Create directory if it not exists.
 * @param path Directory path.
 * @return True or false.
 *
 * @author Pascal Gollor (http://www.pgollor.de/cms/)
 */
bool dirCreate(const char* path) {
    // Check if directory also exists.
    if (dirExists(path)) {
	    return false;
    }

    // platform stuff...
#if defined(_WIN32)
    if (mkdir(path) != 0) {
#else
    if (mkdir(path, S_IRWXU | S_IXGRP | S_IRGRP | S_IROTH | S_IXOTH) != 0) {
#endif
	    std::cerr << "Can not create directory!!!" << std::endl;
		return false;
    }

    return true;
}

/**
 * @brief Unpack file from file system.
 * @param littlefsFile SPIFFS dir entry pointer.
 * @param destPath Destination file path path.
 * @return True or false.
 *
 * @author Pascal Gollor (http://www.pgollor.de/cms/)
 */
bool unpackFile(const char *lfsDir, lfs_info *littlefsFile, const char *destPath) {
    uint8_t buffer[littlefsFile->size];
    std::string filename = lfsDir + std::string("/") + littlefsFile->name;

    // Open file from littlefs file system.
    lfs_file_t src;
    int ret = lfs_file_open(&s_fs, &src, (char *)(filename.c_str()), LFS_O_RDONLY);
    if (ret < 0) {
        std::cerr << "unable to open '" << filename.c_str() << "." << std::endl;
        return false;
    }

    // read content into buffer
    lfs_file_read(&s_fs, &src, buffer, littlefsFile->size);

    // Close littlefs file.
    lfs_file_close(&s_fs, &src);

    // Open file.
    FILE* dst = fopen(destPath, "wb");
    if (!dst) return false;

    // Write content into file.
    fwrite(buffer, sizeof(uint8_t), sizeof(buffer), dst);

    // Close file.
    fclose(dst);


    return true;
}

bool unpackLFSDirFiles(std::string sDest, const char *lfsDir) {
    lfs_dir_t dir;
    lfs_info ent;

    // Check if directory exists. If it does not then try to create it with permissions 755.
    if (! dirExists(sDest.c_str())) {
        std::cout << "Directory " << sDest << " does not exists. Try to create it." << std::endl;

        // Try to create directory.
        if (! dirCreate(sDest.c_str())) {
            return false;
        }
    }

    // Open directory.
    lfs_dir_open(&s_fs, &dir, lfsDir);

    // Read content from directory.
    while (lfs_dir_read(&s_fs, &dir, &ent)==1) {
        // Ignore special dir entries
        if ((strcmp(ent.name, ".") == 0) || (strcmp(ent.name, "..") == 0)) {
            continue;
        }

        // Check if content is a file.
        if ((int)(ent.type) == LFS_TYPE_REG) {
            std::string name = (const char*)(ent.name);
            std::string sDestFilePath = sDest + name;

            // Unpack file to destination directory.
            if (! unpackFile(lfsDir, &ent, sDestFilePath.c_str()) ) {
                std::cout << "Can not unpack " << ent.name << "!" << std::endl;
                return false;
            }

            // Output stuff.
            std::cout
                << lfsDir
                << ent.name
                << '\t'
                << " > " << sDestFilePath
                << '\t'
                << "size: " << ent.size << " Bytes"
                << std::endl;
        } else if (ent.type == LFS_TYPE_DIR) {
            char newPath[PATH_MAX];
            if (lfsDir[0]) {
                sprintf(newPath, "%s/%s/", lfsDir, ent.name);
            } else {
                sprintf(newPath, "%s/", ent.name);
            }
            std::string newDest = sDest + ent.name + "/";
            dirCreate(newDest.c_str());
            unpackLFSDirFiles(newDest, newPath);
        }
        // Get next file handle.
    } // end while

    // Close directory.
    lfs_dir_close(&s_fs, &dir);

    return true;
}

/**
 * @brief Unpack files from file system.
 * @param sDest Directory path as std::string.
 * @return True or false.
 *
 * @author Pascal Gollor (http://www.pgollor.de/cms/)
 *
 * todo: Do unpack stuff for directories.
 */
bool unpackFiles(std::string sDest) {
    // Add "./" to path if is not given.
    if (sDest.find("./") == std::string::npos && sDest.find("/") == std::string::npos) {
        sDest = "./" + sDest;
    }
    if (sDest.back() != '/') {
        sDest += "/";
    }

    // Check if directory exists. If it does not then try to create it with permissions 755.
    if (! dirExists(sDest.c_str())) {
        std::cout << "Directory " << sDest << " does not exists. Try to create it." << std::endl;

        // Try to create directory.
        if (! dirCreate(sDest.c_str())) {
            return false;
        }
    }

    return unpackLFSDirFiles(sDest, "");
}

// Actions

int actionPack() {
    s_flashmem.resize(s_imageSize, 0xff);

    FILE* fdres = fopen(s_imageName.c_str(), "wb");
    if (!fdres) {
        std::cerr << "error: failed to open image file" << std::endl;
        return 1;
    }

    littlefsFormat();
    int result = addFiles(s_dirName.c_str(), "/");
    littlefsUnmount();

    fwrite(&s_flashmem[0], 4, s_flashmem.size()/4, fdres);
    fclose(fdres);

    return result;
}

/**
 * @brief Unpack action.
 * @return 0 success, 1 error
 *
 * @author Pascal Gollor (http://www.pgollor.de/cms/)
 */
int actionUnpack(void) {
    int ret = 0;
    s_flashmem.resize(s_imageSize, 0xff);

    // open littlefs image
    FILE* fdsrc = fopen(s_imageName.c_str(), "rb");
    if (!fdsrc) {
        std::cerr << "error: failed to open image file" << std::endl;
        return 1;
    }

    // read content into s_flashmem
    if (s_flashmem.size()/4 != fread(&s_flashmem[0], 4, s_flashmem.size()/4, fdsrc)) {
        std::cerr << "error: couldn't read image file" << std::endl;
        return 1;
    }

    // close fiel handle
    fclose(fdsrc);

    // mount file system
    littlefsMount();

    // unpack files
    if (! unpackFiles(s_dirName)) {
        ret = 1;
    }

    // unmount file system
    littlefsUnmount();

    return ret;
}


int actionList() {
    s_flashmem.resize(s_imageSize, 0xff);

    FILE* fdsrc = fopen(s_imageName.c_str(), "rb");
    if (!fdsrc) {
        std::cerr << "error: failed to open image file" << std::endl;
        return 1;
    }
    if (s_flashmem.size()/4 != fread(&s_flashmem[0], 4, s_flashmem.size()/4, fdsrc)) {
        std::cerr << "error: couldn't read image file" << std::endl;
        return 1;
    }
    fclose(fdsrc);
    littlefsMount();
    listFiles("");
    littlefsUnmount();
    return 0;
}

void processArgs(int argc, const char** argv) {
    TCLAP::CmdLine cmd("", ' ', VERSION);
    TCLAP::ValueArg<std::string> packArg( "c", "create", "create littlefs image from a directory", true, "", "pack_dir");
    TCLAP::ValueArg<std::string> unpackArg( "u", "unpack", "unpack littlefs image to a directory", true, "", "dest_dir");
    TCLAP::SwitchArg listArg( "l", "list", "list files in littlefs image", false);
    TCLAP::UnlabeledValueArg<std::string> outNameArg( "image_file", "littlefs image file", true, "", "image_file"  );
    TCLAP::ValueArg<int> imageSizeArg( "s", "size", "fs image size, in bytes", false, 0x10000, "number" );
    TCLAP::ValueArg<int> pageSizeArg( "p", "page", "fs page size, in bytes", false, 256, "number" );
    TCLAP::ValueArg<int> blockSizeArg( "b", "block", "fs block size, in bytes", false, 4096, "number" );
    TCLAP::SwitchArg addAllFilesArg( "a", "all-files", "when creating an image, include files which are normally ignored; currently only applies to '.DS_Store' files and '.git' directories", false);
    TCLAP::ValueArg<int> debugArg( "d", "debug", "Debug level. 0 means no debug output.", false, 0, "0-5" );

    cmd.add( imageSizeArg );
    cmd.add( pageSizeArg );
    cmd.add( blockSizeArg );
    cmd.add( addAllFilesArg );
    cmd.add( debugArg );
    std::vector<TCLAP::Arg*> args = {&packArg, &unpackArg, &listArg};
    cmd.xorAdd( args );
    cmd.add( outNameArg );
    cmd.parse( argc, argv );

    if (debugArg.getValue() > 0) {
        std::cout << "Debug output enabled" << std::endl;
        s_debugLevel = debugArg.getValue();
    }

    if (packArg.isSet()) {
        s_dirName = packArg.getValue();
        s_action = ACTION_PACK;
    } else if (unpackArg.isSet()) {
        s_dirName = unpackArg.getValue();
        s_action = ACTION_UNPACK;
    } else if (listArg.isSet()) {
        s_action = ACTION_LIST;
    }

    s_imageName = outNameArg.getValue();
    s_imageSize = imageSizeArg.getValue();
    s_pageSize  = pageSizeArg.getValue();
    s_blockSize = blockSizeArg.getValue();
    s_addAllFiles = addAllFilesArg.isSet();
}

int main(int argc, const char * argv[]) {

    try {
        processArgs(argc, argv);
    } catch(...) {
        std::cerr << "Invalid arguments" << std::endl;
        return 1;
    }

    switch (s_action) {
    case ACTION_PACK:
        return actionPack();
        break;
    case ACTION_UNPACK:
    	return actionUnpack();
        break;
    case ACTION_LIST:
        return actionList();
        break;
    default:
        break;
    }

    return 1;
}

可以打包/解包 littlefs文件系统镜像,

mingw 编译在 main.cpp 要加一行:

extern "C" void __assert_func(
    const char *file, int line, const char *, const char *e) {
}

打包 target 目录 到 out.img 文件:

./mklittlefs.exe -c ./target/ -d 0 -b 1024 -p 1024 -s 1048576 out.img

解包 out.img 到 target_out 目录:

./mklittlefs.exe -u ./target_out/ -d 0 -b 1024 -p 1024 -s 1048576 out.img


列目录:

./mklittlefs.exe -l -d 0 -b 1024 -p 1024 -s 1048576 out.img


2019-05-06_165654.png

离线

#32 2019-05-07 14:27:01

晕哥
管理员
注册时间: 2017-09-06
累计积分: 9,188

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

https://github.com/mongoose-os-libs/vfs-fs-lfs/blob/master/tools/mem_lfs.c

/*
 * Copyright (c) 2014-2018 Cesanta Software Limited
 * All rights reserved
 *
 * Licensed under the Apache License, Version 2.0 (the ""License"");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an ""AS IS"" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "mem_lfs.h"

#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#include <lfs.h>

#define STRINGIFY(a) STRINGIFY2(a)
#define STRINGIFY2(a) #a

static lfs_t s_lfs;
static struct lfs_config s_cfg;

char *image; /* in memory flash image */
bool log_reads = false, log_writes = false, log_erases = false;
int opr, opw, ope;
int wfail = -1;

lfs_t *mem_lfs_get(void) {
  return &s_lfs;
}

static int mem_lfs_read(const struct lfs_config *c, lfs_block_t block,
                        lfs_off_t off, void *buffer, lfs_size_t size) {
  if (log_reads) {
    fprintf(stderr, "R   #%04d %d @ %d+%d\n", opr, (int) size, (int) block,
            (int) off);
  }
  memcpy(buffer, image + (block * c->block_size) + off, size);
  opr++;
  return LFS_ERR_OK;
}

static int mem_lfs_prog(const struct lfs_config *c, lfs_block_t block,
                        lfs_off_t off, const void *buffer, lfs_size_t size) {
  if (log_writes) {
    fprintf(stderr, " W  #%04d %d @ %d+%d\n", opw, (int) size, (int) block,
            (int) off);
  }
  memcpy(image + block * c->block_size + off, buffer, size);
  opw++;
  return LFS_ERR_OK;
}

static int mem_lfs_erase(const struct lfs_config *c, lfs_block_t block) {
  if (log_erases) {
    fprintf(stderr, "  E #%04d %d @ %d\n", ope, (int) c->block_size,
            (int) block);
  }
  memset(image + block * c->block_size, 0xff, c->block_size);
  ope++;
  return LFS_ERR_OK;
}

static int mem_lfs_sync(const struct lfs_config *c) {
  (void) c;
  return LFS_ERR_OK;
}

int mem_lfs_mount(int fs_size, int bs) {
  memset(&s_lfs, 0, sizeof(s_lfs));
  memset(&s_cfg, 0, sizeof(s_cfg));

  s_cfg.read = mem_lfs_read;
  s_cfg.prog = mem_lfs_prog;
  s_cfg.erase = mem_lfs_erase;
  s_cfg.sync = mem_lfs_sync;

  s_cfg.read_size = 64;
  s_cfg.prog_size = 64;
  s_cfg.block_size = bs;
  s_cfg.lookahead = 1024;
  s_cfg.block_count = fs_size / bs;

  return lfs_mount(&s_lfs, &s_cfg);
}

int mem_lfs_format(int fs_size, int bs) {
  memset(&s_lfs, 0, sizeof(s_lfs));
  memset(&s_cfg, 0, sizeof(s_cfg));

  s_cfg.read = mem_lfs_read;
  s_cfg.prog = mem_lfs_prog;
  s_cfg.erase = mem_lfs_erase;
  s_cfg.sync = mem_lfs_sync;

  s_cfg.read_size = 64;
  s_cfg.prog_size = 64;
  s_cfg.block_size = bs;
  s_cfg.lookahead = 1024;
  s_cfg.block_count = fs_size / bs;

  image = (char *) malloc(fs_size);
  memset(image, 0xff, fs_size);

  fprintf(stderr, "Formatting LFS (fs %d bs %d)\n", fs_size, bs);
  int r = lfs_format(&s_lfs, &s_cfg);
  if (r != LFS_ERR_OK) return r;
  return lfs_mount(&s_lfs, &s_cfg);
}

int mem_lfs_mount_file(const char *fname, int bs) {
  FILE *in = fopen(fname, "r");
  if (in == NULL) {
    fprintf(stderr, "failed to open %s\n", fname);
    return -1;
  }
  fseek(in, 0, SEEK_END);
  int fs = ftell(in);
  fseek(in, 0, SEEK_SET);
  image = malloc(fs);
  int nr = fread(image, fs, 1, in);
  fclose(in);
  if (nr != 1) {
    fprintf(stderr, "Image %s exists but cannot be read\n", fname);
    return -1;
  }
  fprintf(stderr, "Mounting LFS from %s (fs %d bs %d)\n", fname, fs, bs);
  return mem_lfs_mount(fs, bs);
}

bool mem_lfs_dump(const char *fname) {
  FILE *out = stdout;
  if (fname != NULL) {
    out = fopen(fname, "w");
    if (out == NULL) {
      fprintf(stderr, "failed to open %s for writing\n", fname);
      return false;
    }
  }
  if (fwrite(image, s_lfs.cfg->block_count * s_lfs.cfg->block_size, 1, out) !=
      1) {
    fprintf(stderr, "write failed\n");
    return false;
  }
  if (out != stdout) fclose(out);
  return true;
}

另一个 littlefs demo

离线

#33 2020-04-16 22:29:24

echo
会员
注册时间: 2020-04-16
累计积分: 3

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

已经在产品上用littlefs,目前为止工作良好。

离线

#34 2020-04-17 21:23:18

liuchangyin
会员
注册时间: 2020-03-17
累计积分: 185

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

资源占用挺小的

离线

#35 2020-04-18 11:21:53

elecboy
会员
注册时间: 2020-04-18
累计积分: 1

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

nrf52832 + freertos + spi nor flash用了littelfs,稳定性很好,已批量出货。

离线

#36 2020-04-18 13:48:38

liuchangyin
会员
注册时间: 2020-03-17
累计积分: 185

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

elecboy 说:

nrf52832 + freertos + spi nor flash用了littelfs,稳定性很好,已批量出货。

big_smile:D:D

离线

#37 2020-05-07 23:26:11

lucian621
会员
注册时间: 2020-05-07
累计积分: 2

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

学习了,真是不错的分享

离线

#38 2020-05-16 15:29:58

simon
会员
注册时间: 2019-05-11
累计积分: 5

Re: LittleFS (lfs) -------- 一个高度完整的嵌入式文件系统

想问一下,这个在esp32上怎么用?

离线

页脚

工信部备案:粤ICP备20025096号-1 Powered by FluxBB