
littlefs 这个文件系统最早是 ARM 那边为微控制器场景设计的主打掉电安全、磨损均衡和极小的 RAM/ROM 占用。很多人第一次接触它是因为 NOR Flash 上跑 FAT 太脆弱一次意外断电就可能把整个目录表写坏。但真正把 littlefs 用到量产项目里你会发现一个绕不开的问题同一套代码放在 NOR Flash 上跑得好好的换到 NAND Flash 上就开始出各种幺蛾子——挂载失败、写入变慢、寿命骤降甚至数据静默损坏。这篇内容就围绕 littlefs 在 NOR 和 NAND 两类 Flash 上的适配差异与优化手段展开把我在实际项目里踩过的坑和验证过的配置方案摊开讲清楚。不管你是刚接手存储模块的新人还是正在做产品选型的老手应该都能从中找到可以直接抄作业的部分。1. 先搞清楚 NOR 和 NAND 到底差在哪很多人做适配时习惯性地把两类 Flash 当成都是块设备来处理结果配置一塌糊涂。要理解 littlefs 的适配逻辑得先把这两类介质的物理特性差异摆到台面上。1.1 擦写粒度与访问方式的本质区别NOR Flash 的典型结构是扇区Sector4KB、块Block64KB 或 128KB支持字节级随机读取写入前必须先擦除整个扇区。它的读取速度很快可以像内存一样直接寻址执行代码XIP这也是为什么很多 MCU 直接把固件放在 NOR 上跑。NAND Flash 则完全不同。它的最小可读写单位是页Page常见 2KB 或 4KB最小擦除单位是块Block通常是 128KB 或 256KB一个块包含 64 到 128 个页。NAND 不支持随机字节读取必须整页读取而且存在坏块Bad Block和位翻转Bit Flip问题需要 ECC 纠错配合。这个差异直接决定了 littlefs 的配置参数。littlefs 的block_size必须和底层介质的擦除单位对齐read_size和prog_size则要和最小读写单位匹配。在 NOR 上prog_size可以设成 1 字节在 NAND 上prog_size必须等于页大小否则写入会失败或者效率极低。我见过一个项目工程师把 NOR 的配置直接搬到 NAND 上prog_size设成 16 字节结果每次写入 littlefs 都要做一次读-改-写整个页的操作写入放大严重寿命直接砍半。这个坑后面还会细说。1.2 坏块管理与 ECC 的介入方式NOR Flash 基本可以认为是无坏块的出厂时极少有寿命内也罕见所以 littlefs 在 NOR 上不需要额外的坏块管理逻辑。但 NAND 不一样出厂就可能带坏块使用过程中还会新增坏块。littlefs 本身不直接管理坏块它依赖底层驱动或 MTD 层来做坏块跳过和 ECC 校验。这里有个关键点littlefs 的元数据metadata是成对写入的它假设写入是原子的。如果底层 NAND 驱动没有正确处理坏块导致某个元数据块写入失败littlefs 的一致性就会出问题。所以 NAND 场景下底层驱动必须实现坏块标记和跳过逻辑并且 ECC 要能纠正至少 4 位错误对于 SLC NAND 的典型要求。1.3 寿命与磨损均衡的压力差异NOR 的擦写寿命通常在 10 万次左右NAND 则因类型而异SLC 约 10 万次MLC 约 3000 到 1 万次TLC 更低。littlefs 自带磨损均衡但它的均衡策略是基于块的动态均衡不是全局静态均衡。在 NAND 上如果文件系统分区较小而写入频繁某些块会被反复擦写寿命消耗很快。实际项目中我建议在 NAND 上给 littlefs 预留足够大的分区至少保证有 20% 以上的空闲块用于均衡。如果分区太小littlefs 的均衡效果会大打折扣。2. littlefs 配置参数在两类 Flash 上的映射关系配置参数是适配的核心配错一个参数轻则性能下降重则直接挂载失败。这一章把关键参数逐个拆开讲。2.1 block_size、prog_size、read_size 的取值逻辑littlefs 的配置结构体lfs_config里有几个核心字段read_size最小读取单位NOR 可以设 1NAND 必须等于页大小。prog_size最小写入单位NOR 可以设 1NAND 必须等于页大小。block_size擦除单位必须等于底层 Flash 的扇区或块擦除大小。block_count分区总块数。cache_size读写缓存大小影响性能。lookahead_size用于磨损均衡的预读位图大小。在 NOR 上典型配置是read_size1、prog_size1、block_size4096。这样 littlefs 可以精细地写入不需要读-改-写。在 NAND 上典型配置是read_size2048、prog_size2048、block_size131072假设页 2KB、块 128KB。注意block_size必须是prog_size的整数倍且block_size最好是prog_size的 2 的幂次倍否则 littlefs 会报错。这里有个容易忽略的点cache_size至少要等于prog_size否则 littlefs 无法正常工作。在 NAND 上cache_size建议设为prog_size的 2 到 4 倍以减少读-改-写次数。2.2 lookahead_size 与磨损均衡的实际影响lookahead_size决定了 littlefs 在分配新块时能看多远。它是以位为单位每个位对应一个块。如果lookahead_size太小磨损均衡只能在很小的范围内进行导致热点块集中擦写。在 NOR 上因为块数通常不多比如 4MB 分区、4KB 块共 1024 块lookahead_size设 64 或 128 字节就够用。在 NAND 上块数可能只有几十到几百比如 16MB 分区、128KB 块共 128 块lookahead_size设 16 或 32 字节即可覆盖全部块。但要注意lookahead_size必须是 8 的倍数且不能超过block_count。我一般会把它设为能覆盖全部块的最小值这样均衡效果最好。2.3 参数配置对照表下面这张表是我在实际项目中总结的典型配置可以直接参考参数NOR Flash 典型值NAND Flash 典型值说明read_size12048NAND 必须等于页大小prog_size12048NAND 必须等于页大小block_size4096131072必须等于擦除单位block_count分区大小/block_size分区大小/block_size根据实际分区计算cache_size64~2564096~8192至少等于 prog_sizelookahead_size64~12816~32覆盖全部块为佳block_cycles100~500100~500磨损均衡阈值注意NAND 的block_size如果和驱动层的擦除命令不匹配会导致擦除不干净进而引发写入失败。务必确认底层驱动的擦除粒度。3. NAND 适配中最容易翻车的三个环节NAND 的适配复杂度远高于 NOR下面这三个环节是我见过出问题最多的地方。3.1 坏块表与 littlefs 块编号的冲突littlefs 在格式化时会扫描整个分区把可用块编号成 0 到block_count-1。但 NAND 的坏块是物理存在的底层驱动通常会跳过坏块导致逻辑块号和物理块号不一致。如果底层驱动没有把坏块信息透传给 littlefslittlefs 可能会把数据写到坏块上导致写入失败或数据丢失。正确的做法是在lfs_config的block_device回调里底层驱动负责把逻辑块号映射到物理块号并跳过坏块。littlefs 本身不感知坏块它只认逻辑块号。我建议在驱动层维护一张坏块表格式化时扫描一次之后每次擦写都查表。如果坏块数量超过分区总块数的 2%建议直接更换 Flash 或缩小分区。3.2 ECC 校验失败后的重试策略NAND 的位翻转是常态ECC 纠错是必须的。但 ECC 纠错失败后怎么办很多驱动直接返回错误littlefs 收到错误后会认为该块不可用尝试重写。如果重写还是失败littlefs 会标记该块为坏块。这里的问题是如果 ECC 失败是偶发的比如读取时电压不稳直接标记坏块会浪费存储空间。我通常会在驱动层加一个重试机制ECC 失败后先尝试重新读取 2 到 3 次如果还是失败再尝试用更强的 ECC 算法如果硬件支持最后才返回错误。另外littlefs 的元数据块写入时建议开启 ECC 校验。如果元数据块 ECC 失败整个文件系统可能无法挂载。所以元数据块的 ECC 强度要比数据块更高。3.3 页内子页写入的陷阱NAND 的页写入有个特性同一页可以分多次写入但只能从前往后写不能回头覆盖已写区域。这叫子页写入Sub-page Program。littlefs 在 NAND 上如果prog_size设得比页小就会触发子页写入。子页写入的问题是如果第一次写入后断电第二次写入前需要先读取整页、修改、再写入。这个读-改-写过程如果被打断数据就会损坏。所以 littlefs 在 NAND 上必须把prog_size设为页大小避免子页写入。我见过一个项目为了节省空间把prog_size设成 512 字节结果每次写入都要读-改-写不仅慢而且掉电后数据损坏率极高。后来改成 2048 字节问题立刻消失。4. 性能优化从挂载速度到写入吞吐适配只是第一步真正让产品好用还得做性能优化。这一章讲几个实测有效的优化手段。4.1 挂载加速减少扫描块的数量littlefs 挂载时会扫描所有块重建元数据。在 NOR 上因为读取快扫描 1024 个块可能只要几十毫秒。但在 NAND 上读取慢扫描 128 个块可能要几百毫秒甚至更久。优化手段有两个一是减小lookahead_size让扫描范围缩小二是利用 littlefs 的lfs_mount返回值如果挂载失败尝试lfs_format后重新挂载。但格式化会丢失数据所以只适合首次上电或数据可丢失的场景。更实用的做法是在驱动层加缓存把元数据块缓存在 RAM 里挂载时直接读缓存。但要注意掉电一致性缓存必须定期同步到 Flash。4.2 写入吞吐cache_size 与 prog_size 的配合写入吞吐主要受cache_size和prog_size影响。在 NAND 上如果cache_size等于prog_size每次写入都要触发一次 Flash 编程效率低。如果cache_size是prog_size的 4 倍littlefs 可以攒够 4 页再一起写减少编程次数。但cache_size太大会占用 RAM。我一般会在 RAM 允许的范围内把cache_size设为prog_size的 2 到 4 倍。实测下来写入吞吐能提升 30% 到 50%。另外littlefs 的写入是追加式的不是原地覆盖。这意味着写入新数据时旧数据块会被标记为无效直到垃圾回收时才擦除。如果写入频繁垃圾回收会成为瓶颈。建议在应用层做批量写入减少小文件频繁写。4.3 磨损均衡block_cycles 的调优block_cycles是 littlefs 的磨损均衡阈值默认是 500。它的含义是当一个块被擦写 500 次后littlefs 会尝试把它迁移到其他块。这个值越小均衡越积极但迁移开销越大值越大均衡越懒但热点块寿命消耗快。在 NOR 上因为寿命长10 万次block_cycles可以设大一点比如 1000。在 NAND 上尤其是 MLC/TLC寿命短block_cycles要设小一点比如 100 到 200。我实测过在 SLC NAND 上block_cycles100时热点块的擦写次数比block_cycles500时低 40% 左右。但迁移开销增加了约 15%。所以这个值要根据实际写入模式来调。5. 掉电安全两类 Flash 上的一致性验证littlefs 的核心卖点就是掉电安全但掉电安全不是自动的需要底层驱动和配置配合。5.1 元数据成对写入的原子性保证littlefs 的元数据是成对写入的先写一个块再写另一个块两个块互为备份。如果写入第一个块时掉电第二个块还是完整的挂载时可以用第二个块恢复。但这个机制的前提是底层写入是原子的。在 NOR 上因为可以字节级写入原子性容易保证。在 NAND 上页写入是原子的但块擦除不是。如果擦除过程中掉电整个块可能变成无效状态。所以 NAND 上必须保证擦除操作要么完成要么不开始。这需要底层驱动支持擦除中断保护或者在擦除前先备份数据。5.2 掉电测试的实操方法掉电测试不能只靠手动拔电那样复现率太低。我通常用两种方法一是用可编程电源在写入过程中随机切断电源重复 1000 次以上统计挂载失败率和数据丢失率。二是用软件模拟在lfs_config的prog和erase回调里随机返回错误模拟掉电。实测下来如果配置正确littlefs 在 NOR 上的掉电挂载成功率可以做到 99.9% 以上。在 NAND 上如果坏块管理和 ECC 到位也能做到 99% 以上。但如果prog_size配错掉电后挂载失败率会飙升到 10% 以上。5.3 掉电后的恢复策略即使 littlefs 挂载成功也可能有部分数据丢失。我建议在应用层加一个恢复逻辑挂载后检查关键文件是否存在如果不存在从备份分区恢复。或者用 littlefs 的lfs_file_truncate把损坏的文件截断到最后一个有效长度。另外littlefs 的lfs_mount如果返回LFS_ERR_CORRUPT不要直接格式化先尝试用lfs_mount的第二个参数如果支持或者手动修复。格式化是最后手段。6. 实战案例从 NOR 迁移到 NAND 的完整过程去年我接手一个项目原来用 NOR Flash 跑 littlefs后来因为成本原因换成 NAND。迁移过程中遇到了不少问题这里把关键步骤和踩坑记录整理出来。6.1 迁移前的评估与分区规划首先确认 NAND 的型号和参数页大小 2KB块大小 128KB总容量 128MB。规划给 littlefs 的分区是 16MB即 128 个块。预留 20% 空闲块实际可用约 102 个块。然后确认底层驱动是否支持坏块管理和 ECC。原 NOR 驱动没有这些功能需要重写。我用了 MTD 层的通用 NAND 驱动配置好 ECC 强度和坏块表。6.2 配置参数的调整与验证把lfs_config从 NOR 配置改成 NAND 配置// NOR 配置 const struct lfs_config nor_cfg { .read_size 1, .prog_size 1, .block_size 4096, .block_count 1024, .cache_size 64, .lookahead_size 64, .block_cycles 500, }; // NAND 配置 const struct lfs_config nand_cfg { .read_size 2048, .prog_size 2048, .block_size 131072, .block_count 128, .cache_size 8192, .lookahead_size 16, .block_cycles 150, };改完后先做格式化测试确认lfs_format成功。然后做读写测试写入 1MB 数据读取校验。最后做掉电测试重复 500 次。6.3 迁移后暴露的问题与修复迁移后第一个问题是挂载时间变长从 50ms 变成 400ms。原因是 NAND 读取慢扫描 128 个块耗时增加。优化方法是减小lookahead_size到 16挂载时间降到 250ms。还可以在驱动层加元数据缓存进一步降到 100ms 以内。第二个问题是写入吞吐下降从 2MB/s 降到 800KB/s。原因是cache_size设小了。把cache_size从 2048 改成 8192 后吞吐恢复到 1.5MB/s。第三个问题是坏块导致挂载失败。原因是驱动层的坏块表没有正确初始化。修复方法是格式化前先扫描坏块建立坏块表并在block_device回调里跳过坏块。7. 一些容易被忽略的细节与经验最后分享几个在实际项目中总结的细节这些在官方文档里通常不会写但很实用。7.1 分区对齐与擦除边界littlefs 的分区起始地址必须和 Flash 的擦除边界对齐。如果分区起始地址不是block_size的整数倍擦除时会影响到相邻分区。我见过一个项目因为分区起始地址偏移了 4KB导致擦除时把 bootloader 擦掉了直接变砖。所以规划分区时务必确认起始地址和大小都是block_size的整数倍。在 NAND 上还要确认起始地址不是坏块。7.2 文件系统大小与 RAM 占用的平衡littlefs 的 RAM 占用主要来自cache_size和lookahead_size。在资源紧张的 MCU 上这两个值不能设太大。但设太小又影响性能。我的经验是如果 RAM 只有几十 KBcache_size设 512 到 1024lookahead_size设 8 到 16。如果 RAM 有几百 KBcache_size可以设 4096 到 8192lookahead_size设 32 到 64。另外littlefs 的文件句柄也会占用 RAM每个打开的文件大约占用cache_size大小的 RAM。所以不要同时打开太多文件。7.3 与 RTOS 的配合注意事项如果项目用了 RTOSlittlefs 的读写操作要注意线程安全。littlefs 本身不是线程安全的多个线程同时读写同一个文件系统会导致元数据损坏。我通常的做法是加一个互斥锁所有 littlefs 操作都先获取锁。或者把 littlefs 操作放在一个单独的任务里其他任务通过消息队列请求。另外littlefs 的擦除操作可能耗时较长NAND 上可能几十毫秒如果在中断里调用会阻塞系统。建议把擦除操作放在低优先级任务里或者用异步擦除。7.4 调试手段与日志输出littlefs 提供了lfs_trace和lfs_debug宏可以输出详细的调试信息。在适配阶段建议打开这些宏观察挂载、读写、擦除的每一步。但要注意打开调试宏会增加代码体积和运行开销量产时要关掉。我通常会在开发阶段打开量产时通过编译开关关闭。另外可以用lfs_fs_stat和lfs_fs_traverse来检查文件系统状态比如已用块数、空闲块数、坏块数。这些信息对调优很有帮助。7.5 版本兼容性与升级策略littlefs 的版本更新比较频繁不同版本的磁盘格式可能不兼容。升级 littlefs 版本时要先确认磁盘格式是否变化。如果变化需要做数据迁移。我建议在项目初期就锁定 littlefs 版本不要频繁升级。如果必须升级先在测试环境验证确认挂载和读写正常后再上量产。另外littlefs 的配置结构体在不同版本间可能有变化升级时要同步更新配置代码。8. 关于选型的一点个人看法说了这么多适配和优化的细节最后聊聊选型。如果你的项目是 NOR Flashlittlefs 几乎是默认选择配置简单性能好掉电安全。如果是 NAND Flashlittlefs 也能用但需要额外的坏块管理和 ECC 支持适配成本高一些。如果 NAND 容量很大比如 1GB 以上littlefs 可能不是最优选择因为它的磨损均衡是块级的大容量下均衡效果有限。这时候可以考虑其他文件系统比如 SPIFFS 的改进版或者厂商提供的专有文件系统。但如果你的 NAND 容量在 16MB 到 128MB 之间littlefs 完全够用而且掉电安全是它的强项。我在多个量产项目里用 littlefs 跑 NAND只要配置正确稳定性没问题。最后提醒一句不管用哪种 Flash适配完成后一定要做充分的掉电测试和寿命测试。我见过太多项目因为省了这一步量产后退货率飙升。存储这块稳字当头。