只读分享解决方案文档原理方案设计📅 2026-10-05🔖 Rev 1.4⏳ 26天有效(至 2026-11-05)
🔧解决方案&应用市场分析 -> 原理方案设计 -> 嵌入式文件系统开发指南

嵌入式文件系统开发指南

生成时间 2026-10-06 15:32:42 有效期至 2026-11-05 15:37:58(26天有效(至 2026-11-05))
同系列 · 原理方案设计

嵌入式文件系统开发指南

编制日期 2026-10-05 | 版本号 Rev 1.4

面向 NOR / NAND / eMMC 与 MCU、MPU 平台的存储软件栈 · 覆盖分区挂载、FTL、FAT 与日志式文件系统、LittleFS 与 KV 持久化、掉电一致性、寿命预算、移植与验收 · 内核态与用户态双视角

一句话结论:嵌入式文件系统的难点从来不是「把文件存进去」,而是在介质物理特性受限的前提下,保证掉电后数据仍可恢复、把擦写次数花在真正需要的地方。NOR 的扇区大、擦除慢、寿命十万次量级,NAND 的页小、擦除快、需要坏块与 ECC,eMMC 有内部 FTL 与磨损均衡——三套介质特性直接决定了三套不同的文件系统设计。选错抽象层,写放大能从 1 倍放大到 10 倍以上;漏掉掉电原子性,一次掉电就是一批配置全丢。

20
章 + 4 附录
从全景到验收
5
层抽象
应用 / FS / FTL / 块 / 介质
68
张图示
全部内联 SVG
9
道交付检查门
第 20 章
10
个必记数字
寿命与超时

这份文档解决什么

本文是文件系统层的专项指南:从介质物理特性出发,讲清分区挂载、FTL 映射与垃圾回收、FAT 族结构、日志式一致性模型、LittleFS 与 SPIFFS 的取舍、键值存储布局、磨损均衡与寿命预算、掉电原子性、加密存储,最后给出一套可执行的移植流程与验收规范。 存储器件的电气设计(时序、电压、封装、信号完整性)由各器件的电路设计指南承接;内核设备树、内核态驱动开发由《Linux 内核开发指南(BSP Kernel)》承接;具体 Flash 芯片的驱动实现由《SPI NOR / SPI NAND / PPI NAND / eMMC 驱动开发指南》系列承接。本文站在它们之上,只讲文件系统。

路线 1 · 从零移植一个文件系统
手里有 NOR / NAND,想跑文件系统
第 2 章介质特性 → 第 3 章分区挂载 → 第 7 章 LittleFS 移植 → 第 12 章移植接口 → 附录 A 流程十步。
重点:擦除粒度、三个回调、元数据对、掉电原子性
路线 2 · 掉电后数据丢了
现场偶发丢配置
第 10 章一致性模型 → 第 8 章 KV 布局 → 第 18 章故障手册 → 第 19 章掉电测试。
重点:原子重命名、fsync 语义、恢复路径
路线 3 · 容量 / 寿命要多久
算能撑几年
第 9 章寿命预算 → 第 11 章缓存与性能 → 第 13 章容量规划 → 第 17 章优化手段。
重点:写放大、擦除次数、温度降额
路线 4 · 选型与对比
FAT / LittleFS / ext4 / 加密
第 1 章全景选型 → 第 5 章 FAT → 第 6 章日志式 → 第 7 章 NOR → 第 14 章加密 → 附录 B 对比表。
重点:抽象层、成本、兼容边界
路线 5 · 验收与移交
项目要交付了
第 19 章测试规范 → 第 20 章九道门 → 附录 C 十二项验收。
重点:掉电测试、寿命测试、判据

读者路径

路线 1(移植一个新器件):第 2.5 节从介质参数反推设计 → 第 3.1 节分区 → 第 7.3 节三个回调 → 第 12.2 节移植前十个问题 → 附录 A.1 流程十步。

路线 2(排查线上问题):第 18 章故障手册 → 第 10.3 节原子重命名 → 第 8.3 节 NVS 布局 → 第 16.2 节调试命令。

路线 3(做容量与寿命规划):第 2.3 节寿命与写放大 → 第 9.1 节寿命预算算法 → 第 11.3 节小 IO 差异 → 第 13.3 节预算平衡。

路线 4(做选型):第 1.2 节五层抽象 → 第 5.6 节 FAT 固有缺陷 → 第 6.1 节日志思想 → 第 7.2 节 LittleFS 取舍 → 附录 B.2 对比表。

版本基线与引用约定

  • 文件系统以 Linux 6.1 / 5.15 LTS 与 LittleFS v2.9 / v3.x、SPIFFS 3.0 为基线;
  • 介质参数以各厂商数据手册为唯一依据,本文数值仅为示例量级,实际设计必须回到手册;
  • 命令与路径在 MCU 上为 /mnt、/data 等挂载点,在 Linux 上为 /dev/mmcblk0p1 等块设备,两套都给出;
  • 寄存器级时序参数见《SPI NOR / eMMC 电路设计指南》,本文只讲它们对文件系统软件栈的影响。

1 · 文件系统全景与选型

文件系统是软件栈里最容易被「当成系统自带」的一层,却是嵌入式产品里最容易出问题的一层。它的复杂度不来自代码量,而来自它要对物理世界的三条硬约束负责。

应用与框架层业务代码、数据库、配置服务、日志采集只关心「打开一个文件、读出内容」,完全不知道下面是什么文件系统层FAT32、ext4、LittleFS、SPIFFS、NVS、FFS负责目录、文件、配额、分配策略与掉电一致性——本文主体FTL 闪存转换层把「块设备的逻辑地址」翻译成「闪存的物理页」负责地址映射、垃圾回收、坏块替换——eMMC 内部自带,用户态看不到块设备层MTD、mmcblk、nvme、ublock;统一「按 512 字节扇区读写」接口统一到 sector 语义,上层不必知道是 NOR 还是 eMMC物理介质NOR Flash / SLC NAND / TLC NAND / eMMC / SD NAND扇区大小、擦除粒度、寿命、掉电特性在此确定,不可更改本文聚焦这一层的上边界:文件系统与 FTL 的职责划分
图 1 · 嵌入式文件系统的五层抽象

1.1 为什么嵌入式文件系统是独立课题

PC 上的文件系统几乎不需要工程师操心,因为三个前提天然成立:介质块大(4 KB 扇区起步)、可靠性高(SSD 有自己的 FTL 与磨损均衡)、应用层只管调 API。嵌入式把这三个前提全部拿掉了。

一颗 64 Mb SPI NOR 的擦除粒度可能是 4 KB,一个页写 200 ms;一次改 16 字节的配置文件,底层要读出整块、修改、擦除、写回——写放大 256 倍。一块 1 Gb 的 NAND 页只有 2 KB、使用中还会长出坏块,需要自己的坏块表与 ECC 策略。这些约束直接进文件系统设计,不是加个驱动就完事。

一个反面案例

某智能终端产品用 FAT32 存配置:一个小文件 100 字节,占 1 个簇。日志每 5 分钟追加一条。每条日志触发「读簇 — 改 — 擦簇 — 写簇」。这颗 NOR 只有 10 万次擦除寿命、簇 4 KB,于是每天约 3 万次擦除,三周就把那一簇写坏,表现为设备运行一段时间后配置读取异常。正确做法是把日志改成环形缓冲 + 批量落盘,或至少做到「攒够 64 KB 再落盘」——写放大立刻降到 1 附近。

1.2 五层抽象:应用、FS、FTL、块设备、介质

NOR FlashNANDeMMC擦除粒度64 KB ~ 256 KB16 KB ~ 256 KB编程粒度1 ~ 8 字节512 B ~ 4 KB 页擦除时间100 ~ 1500 ms1 ~ 10 ms擦写寿命10 万 ~ 100 万次1 千 ~ 10 万次读延迟数十 us数十 us随机读能力强(XIP 可行)弱(需 ECC)是否需要 FTL不必(可直写)必须典型容量1 ~ 512 Mb128 Mb ~ 16 Gb坏块出厂即有,极少增长出厂 + 使用中增长掉电粒度整扇区可能只写一半页 / 多页结论:三类介质不能套同一个文件系统NOR 用 LittleFS 类、NAND 用 FTL + 块层语义、eMMC 直接 ext4/FAT —— 抽象层选错,成本差一个量级
图 2 · 三类存储介质的特性对照

上图是选型的起点。下面逐层说明每层负责什么、边界在哪里。

第 1 层:应用与框架

业务代码、日志服务、配置中心、数据库。这一层只应该看到 POSIX 接口(open/read/write/lseek/fsync)。如果应用需要知道自己在 NOR 上还是在 eMMC 上,说明抽象已经漏了——那是最需要警惕的设计信号。

第 2 层:文件系统

本文的主体。它负责五件事:

  • 空间分配:哪些块归哪个文件、哪些块空闲;
  • 目录组织:文件名到数据块的映射;
  • 元数据管理:超级块、位图、目录项、inode 的可靠性;
  • 一致性:保证掉电后能恢复到某个一致状态;
  • 寿命与配额:把有限的擦写次数分配给无限的写入请求。

第 3 层:FTL(闪存转换层)

把「逻辑块地址」翻译成「物理页地址」,并在逻辑页被改写时做垃圾回收。eMMC 与 SD NAND 芯片内部自带 FTL,用户态看到的是一个干净的块设备;SPI NOR 与裸 NAND 则通常需要在主机侧自己实现。第 4 章专门讲。

第 4 层:块设备层

提供统一的扇区语义:request(sector, count, buffer)。Linux 上是 blkdev / mtdblk / mmcblk;MCU 上由应用自己实现这个函数指针。关键约定是:单位是 512 字节还是页大小,必须在设计时定死,混用是移植里最常见的错误之一。

第 5 层:物理介质

NOR:可原地改写,但只能 1 变 0擦除一次恢复整块为全 1,因此「擦除 + 写」是改写的前置条件1 1 1 1初始1 1 1 1擦除后0 0 1 1写入 00 0 0 0再次写入想把 0 改回 1?只能擦除整块这就是「写放大」的根源NAND:只能顺序写入到空白页已编程的页不可复写,必须换新页,这就是 FTL 存在的原因页 0已用页 1已用页 2当前页 3空白页 4空白FTL 的活:把逻辑页 2 映射到物理页 3,并记住这个映射映射关系存在 OOB 或映射表中,掉电时必须能重建三条不可违反的物理规则· 擦除只能整块:不能擦半个扇区,这是所有「先读出—改—整块写回」逻辑的根源· 编程有顺序性:NAND 必须编程到空白页;NOR 可原地写但需先擦除· 断电即最终:写操作中断,介质可能处于「一半新一半旧」,这叫撕裂由此推出文件系统的两条设计底线① 任何「看起来只改了 1 字节」的操作,底层可能触发「读整块 — 改 — 擦整块 — 写整块」,写放大由此而来。② 任何可能掉电的操作,都必须能靠介质上已有的信息恢复到「擦除前」或「修改后」的一致状态。这一章的结论文件系统的每一个设计决策——是否用日志、写放大多少、要不要预留空间、元数据放哪——都能追溯到上面这三条物理规则。反过来,看到一个文件系统设计就能推出它假设的介质特性。
图 3 · 擦除与编程的物理过程

这三条规则是整篇文档的地基。请记住它们的实际后果:

  • 「擦除只能整块」→ 文件系统的最小分配单元不能小于擦除粒度,否则每次写都要擦一个大盘;
  • 「编程有顺序性」→ NAND 上必须有 FTL,NOR 上可以不写 FTL 但要用日志式结构;
  • 「断电即最终」→ 一切跨块操作都需要事务性设计。

1.3 块设备与字符设备的分界

这个分界决定了「能否用文件系统」,实践中要先问清楚。

维度块设备字符设备对文件系统的影响
访问单位固定大小扇区(512 B / 4 KB)字节流块设备可挂文件系统;字符设备不能
随机寻址支持,可跳着读写不支持,只能顺序FAT 依赖随机寻址
典型设备mmcblk、mtdblk、nvme、ublock串口、GPIO、传感器MTD 原始设备是字符设备,需 mtdblock 转块设备
MTD 分区MTD 上仍以字符设备暴露(mtdblockN)mtd0 直接是字符设备NOR 上常用 mtdblock 挂 LittleFS
能否直接改 1 字节不能(需读改写整块)能决定写放大量级
典型文件系统ext4、FAT32、LittleFS、NTFS无—

实践中最容易踩的一条:在 SPI NOR 上用 mtdblock 挂文件系统,写放大会大到不可接受。因为 MTD 的写是「按最小可写单位(NAND 上是页、NOR 上是 1 字节)」,而擦除是「按擦除单位(4 KB ~ 256 KB)」。文件系统以为自己在改 1 KB,实际触发了一次 64 KB 的擦除。正确路径是让文件系统直接跑在 MTD 之上(LittleFS 就是这么设计的),由它把「改了多少」与「擦多少」的关系算清楚。

1.4 本文覆盖范围与前置约定

层次本文讲什么本文不讲什么
文件系统FAT、日志式、LittleFS、SPIFFS、KV 存储的原理与选型—
FTL映射策略、GC 算法、坏块替换的设计取舍芯片内部 FTL 的实现细节
块设备接口约定与移植要点块设备驱动的寄存器级实现
介质物理特性对上层的影响、参数表电气时序、电压、封装(见电路设计指南)
系统挂载顺序、只读根、OTA 空间规划内核调度、进程管理(见内核 BSP 与系统篇)
阅读约定:本文的路径以 Linux 为主线(/dev/mtdblock0、/dev/mmcblk0p1、mount),MCU 平台在每处给出对应写法。命令示例中的参数以常见默认值示意,实际必须按手册与实测调整。

2 · 存储介质与硬件基础

这一章看起来像是「硬件内容」,但它是文件系统设计的输入。跳过这一章直接抄一个文件系统跑起来,是绝大多数移植失败的起点。

2.1 NOR、NAND 与 eMMC 的本质差异

介质标称寿命10 万次擦除(数据手册值)实际单块可用次数受保留块、温度与均衡质量影响总可用擦除次数单块次数 ×可用块数每次逻辑写消耗写放大系数(通常 2 ~ 10)可支撑总写入量总擦除次数 ×擦除粒度 / 写放大换算成寿命总写入量 ÷日均写入量算例:256 Mb SPI NOR,分成 4 KB 簇的 LittleFS 区域标称寿命 10 万次/块 · 保留 4 个块做磨损均衡与元数据 · 擦除粒度 4 KB · 典型写放大 2.5每块可安全擦 10 万次,容量 32 MB 相当于 8192 个 4 KB 块,扣除保留块后约 8188 块步骤计算式结果总可用擦除次数8188 块 × 10 万次≈ 8.19 亿次块擦除折算总擦除字节数8.19 亿次 × 4 KB≈ 33.5 TB扣除写放大 2.533.5 TB ÷ 2.5≈ 13.4 TB 逻辑写入日均写入 8 MB 时13.4 TB ÷ 8 MB≈ 4.9 年日均写入 80 MB 时13.4 TB ÷ 80 MB≈ 5 个月这张表要说明的事:寿命不是介质的固定属性,而是「介质 × 文件系统 × 使用方式」三者的乘积。把写放大从 8 降到 2,等于把寿命翻两番——这通常比换一颗更大容量的芯片便宜得多。
图 4 · 从介质参数到寿命的计算链路

三类介质的差异可以归到三条根上:能否原地改写、擦除粒度多大、会不会长坏块。

特性NOR FlashNAND FlasheMMC
可否 XIP 就地执行可以(但速度远低于 SRAM)不可以不可以
单元字节数1 字节(可位级改写)页(512 B ~ 4 KB)内部已封装
擦除单位扇区 64 ~ 256 KB块 16 ~ 256 KB内部已封装
编程可否原地需先擦除同块不可,只能写空白页内部已封装
读延迟几十 us几十 usus 级
坏块出厂即有,极少增长出厂有,擦写中还会新增内部管理并重映射
ECC不需要必须(Hamming / BCH / LDPC)内部处理
OOB 空间无每页有(通常 1/8 ~ 1/16)不可见
标称寿命10 万 ~ 100 万次擦除1 千 ~ 10 万次擦除按容量摊薄,TBW 指标
典型容量512 KB ~ 512 Mb128 Mb ~ 32 Gb2 ~ 512 GB
常见形态SPI NOR / QSPI / 并行 NORSPI NAND / PPI NANDeMMC 封装
主机侧是否要 FTL通常不要,但要日志式 FS必须不要

三点最容易被忽略:

  • NOR 也有坏块,只是出厂时就标好了、增长极慢。但 NOR 的「坏块」一般不是整块不可用,而是某些位在擦除后无法归 1。厂家会在出厂时扫描并标记,文件系统要尊重这个标记,不能随意擦除被标记的块。
  • NAND 的 OOB 空间是生命线。它不只放 ECC,还放坏块标记与 FTL 映射信息。写 OOB 时必须严格遵守「只允许把 1 写成 0」的规则,否则映射表会静默损坏。
  • eMMC 的 TBW 指标才是真正该看的参数。它直接给出「在标称负载下能写多少 TB」,比「擦除次数 × 容量」更贴近实际。但要注意 TBW 是按厂商自己的负载模型测的,若你的负载是高频小写入,寿命会明显短于 TBW 标称。

2.2 擦除粒度与编程粒度

这两个参数决定文件系统「最小分配单元」的下限,也决定了一致性原语的可用粒度。

参数定义对文件系统的影响
擦除粒度 / erase_size一次擦除能清空的最小字节数文件系统块大小不宜远小于它,否则写放大失控
编程粒度 / write_size一次编程能写入的最大字节数决定跨页写入时需要多少条命令、是否需要读改写
页大小 / page_sizeNAND 上一次读写的最小单位块设备扇区大小通常与页对齐
OOB 大小每页附带的元数据区ECC 容量决定可纠错位数;FTL 映射表放这里
擦除超时厂商保证的最大擦除时间所有忙等都要以此为上限,超时应报错而非卡死
编程超时厂商保证的最大编程时间同上;FRAM 等器件此值为 0(写入即完成)
最高频的移植事故:把时间参数单位搞错

Zephyr 的 flash_parameters.erase_size 与 write_size 单位是 bit,填 4 KB 要写 12 而不是 4096。Linux 的 SPI-NOR binding 里则是字节。填错的典型表现是:设备初始化时算出扇区数与实际容量差 8 倍(4096 被当成 4096 bit = 512 byte),文件系统挂载时报「设备太小」或读到大量全 0xFF 扇区。

另一个常见坑是「擦除时间取典型值而非最大值」。典型值 200 ms 的器件,最大值可能 2 s。忙等按典型值设超时,遇到坏块(擦除慢)就误判失败;按最大值设,正常时只是多等几轮,不影响正确性。超时一律按数据手册的最大值设定。

2.3 寿命、写入放大与磨损均衡

介质标称寿命10 万次擦除(数据手册值)实际单块可用次数受保留块、温度与均衡质量影响总可用擦除次数单块次数 ×可用块数每次逻辑写消耗写放大系数(通常 2 ~ 10)可支撑总写入量总擦除次数 ×擦除粒度 / 写放大换算成寿命总写入量 ÷日均写入量算例:256 Mb SPI NOR,分成 4 KB 簇的 LittleFS 区域标称寿命 10 万次/块 · 保留 4 个块做磨损均衡与元数据 · 擦除粒度 4 KB · 典型写放大 2.5每块可安全擦 10 万次,容量 32 MB 相当于 8192 个 4 KB 块,扣除保留块后约 8188 块步骤计算式结果总可用擦除次数8188 块 × 10 万次≈ 8.19 亿次块擦除折算总擦除字节数8.19 亿次 × 4 KB≈ 33.5 TB扣除写放大 2.533.5 TB ÷ 2.5≈ 13.4 TB 逻辑写入日均写入 8 MB 时13.4 TB ÷ 8 MB≈ 4.9 年日均写入 80 MB 时13.4 TB ÷ 80 MB≈ 5 个月这张表要说明的事:寿命不是介质的固定属性,而是「介质 × 文件系统 × 使用方式」三者的乘积。把写放大从 8 降到 2,等于把寿命翻两番——这通常比换一颗更大容量的芯片便宜得多。
图 5 · 从介质寿命到整机寿命的计算链路与算例

图 4 给出的算例说明:寿命是三者相乘的结果。介质寿命是分子,写放大是分母,使用方式是系数的来源。工程上真正能动的只有两个:降低写放大、改善使用方式。

概念定义典型量级
写放大 (WAW)写进介质的数据量 ÷ 应用写入的数据量FAT32 小文件 100 ~ 500;日志式 2 ~ 5;良好设计 1 ~ 2
擦除放大一次逻辑写引发的擦除次数理想 1;日志式最坏可达 100
磨损不均某些块被擦得比其他块多得多良好均衡下不超过均值 ±20%
TBW厂商标称可承受的总写入量eMMC 常见 300 TBW / 600 TBW / 1200 TBW
P/E 循环单块的编程擦除次数NOR 10 万次以上;消费级 TLC 约 1 千次
TLC 的「SLC 缓存耗尽陷阱」

消费级 TLC eMMC/NAND 有 SLC 缓存(缓存满了以 SLC 速度写一小块区域),缓存写满后写入速度会掉到原来的十分之一甚至更低。更严重的是:持续写入超过缓存容量后,厂商会在后台做隐式垃圾回收,这段时间内掉电可能导致数据损坏。所以 eMMC 做「大量顺序写」比「频繁随机写」更安全;关键数据(配置、凭据)不要依赖 eMMC 的隐式 GC,要有自己的双副本或日志保护。

2.4 掉电行为的物理本质

形态 1 · 整块写完成介质上是完整的旧数据或完整的新数据新数据旧数据最理想,但只在大块顺序写时出现形态 2 · 块内部分页写入前半是新数据,后半是旧数据,读出来是垃圾新数据旧数据NAND 页编程时的典型形态形态 3 · 元数据已改、数据未改文件大小变了但内容是旧的,或反之新数据旧数据最危险:文件系统自认为一致,实际不一致为什么必须由文件系统来处理· 介质只保证「块内编程是原子的」,不保证「跨块事务是原子的」——它不知道你要做什么;· FTL 能保证自己的一致性,但那只覆盖「已分配页的映射」,覆盖不了「文件系统语义」上的事务;· 只有文件系统知道「这次操作要改哪几个块、顺序是什么、哪个状态是合法的」,所以一致性责任必然落在它身上。工程结论:fsync 不是「更保险一点」,而是「保证跨块原子性」的唯一手段高频写日志的场景不 fsync,掉电后文件系统结构损坏的概率随写入次数线性上升;这个坑要到现场掉电才暴露,实验室常温常压下测不出来。
图 6 · 掉电撕裂的三种形态与责任归属

图 5 的三种形态里,第三种最危险:文件系统的元数据说「文件有 4 KB」,实际内容却是旧的或半新半旧的。程序读到「长度合法但内容错误」的数据,不会报错、静默往下走,最终以诡异的方式崩溃。

工程上对掉电的处理遵循一条原则:介质只保证「单次编程的原子性」,跨块原子性必须由文件系统用「日志」或「双写元数据」自己造出来。第 6 章与第 10 章分别讲机制与工程落地。

2.5 从介质参数反推文件系统设计

介质手册里的每一行,都对应文件系统的某个设计选择做移植前先把参数抄进参数表,再逐条对照右列——漏掉任何一条,后期都会以「莫名其妙的问题」形式暴露擦除粒度 256 KB文件块不宜过小FAT 簇 ≥ 128 KB,或改用大簇擦除时间 1.5 s阻塞式擦除不可接受配擦写队列,OS 要能睡等寿命 10 万次预留块比例上调LittleFS 保留块设为块数的 2% ~ 5%有 ECC读错误可纠正NAND 上的日志式 FS 可用无 XIP 支持代码不能就地执行XIP 分区只放数据或改用加密掉电粒度 = 块原子性可做在块级提交标记 + 元数据双写足够手册参数设计约束落地做法反过来也成立:看到文件系统的行为,可以反推它假设的介质FAT32 假设块设备能提供原子扇区写;ext4 假设块设备有可靠的中断与请求合并;LittleFS 假设擦除粒度大、擦除慢但次数多、可以整块擦;SPIFFS 假设 NOR 可位级改写且 RAM 紧张。
图 7 · 从介质参数反推文件系统设计决策

这张图建议在移植前填成一张表,逐行确认。顺序上先做介质参数表(照抄手册),再逐条确定文件系统侧的后果,两者对齐后再动手写配置。反过来——先抄一份 CONFIG_LITTLEFS=y 再去想为什么挂了——是嵌入式里最常见的顺序错误。

参数表要抄哪些项(可直接当模板)
  • 容量、页大小、擦除粒度、编程粒度、OOB 大小
  • 擦除时间典型值 / 最大值、编程时间典型值 / 最大值
  • 擦写寿命(P/E 次数)、数据保持年限(如 10 年 @ 85 °C)
  • 出厂坏块数、允许的增长上限
  • 工作电压范围、待机电流(决定低功耗策略)
  • 顺序读 / 随机读 / 页编程 / 块擦除 四类操作的实测时间

最后一项最好自己实测一遍:手册值是典型设备的表现,具体到你的板子(走线长度、去耦、时钟)会有偏差,而文件系统层的超时设置需要用实测的最大值。

3 · 分区、挂载与命名空间

分区解决「一块介质怎么切成多块用」,挂载解决「多块怎么变成一棵目录树」。这一章的内容在 PC 上很少被关心,但在嵌入式上每一处都会影响寿命与可靠性。

3.1 分区表:MBR、GPT 与固定分区

一块 8 Gb eMMC 上的典型分区布局分区不是「切块」,而是给每类数据不同的可靠性与寿命契约MBR / GPT16 KB分区表与元数据,只写一次boot8 MB内核 + initramfs,只读,可靠性最高rootfs A48 MB根文件系统,ext4 或 squashfsrootfs B48 MBOTA 的目标槽位data512 MB用户数据,可读写,写放大可接受log32 MB运行日志,环形覆盖,允许丢失cache64 MB可随时丢弃,不计入寿命预算剩下未分配留作扩容或数据分区分区划分的真正目的:让「可以丢的数据」不要占用「不能丢的数据」的寿命预算日志与 cache 每天写入几十 MB,若与 rootfs 共享 FTL 与寿命预算,会把整机寿命拖垮;分区之后,它们可以独立挂载、独立格式化、甚至独立加密与丢弃。
图 8 · 整块存储上的典型分区布局

三种分区方式并存,选哪种取决于介质类型与平台能力。

方式原理容量上限表项数上限适用场景
MBR主引导记录在 0 扇区,含 4 个主分区 + 扩展分区链表2 TB(实际约 2 GB 因 32 位扇区数)4 个主分区eMMC、小容量 SD;PC 兼容性最好
GPT保护 MBR + 全局唯一标识 + 分区表项数组(默认 128 项)9.4 PB(64 位扇区号)128 项大容量 eMMC;需 UEFI 或 hybrid 模式
固定分区在 DTS / 设备树或 bootloader 里硬编码偏移与长度无自定义SPI NOR + MCU;无 GPT 支持时的唯一选择
MTD 分区由 mtdparts 分区表定义,解析后注册为多个 MTD 设备受介质限制无硬限制NOR / NAND;Zephyr 与 U-Boot 都支持

固定分区:MCU 上的默认选择

MCU 平台没有 GPT 工具链,常见做法是把分区写进设备树:

&flash0 {
    partitions {
        compatible = "fixed-partitions";
        #address-cells = <1>;
        #size-cells = <1>;

        boot_partition: partition@0 {
            label = "mcuboot";
            reg = <0x00000000 0x0000C000>;   /* 48 KB 引导 */
        };
        storage_partition: partition@c000 {
            label = "storage";
            reg = <0x0000C000 0x00314000>;   /* 3.1 MB 文件系统 */
        };
    };
};
分区表的三个常见错误
  • 偏移未按擦除粒度对齐:分区起点不对齐到 4 KB 或 64 KB,会导致该分区首个块无法使用,或读写耗时暴涨。
  • 大小未留对齐余量:reg 的 size 必须是擦除粒度的整数倍,否则最后一个块跨界,访问时行为未定义。
  • 改了分区表但没动应用:应用里写死的偏移量与新分区表不一致,读到的是错误位置的数据。这类问题在 OTA 升级后集中爆发。

3.2 挂载点与挂载选项

第 1 层 · 设备节点/dev/mmcblk0p2 · 或 /dev/mtdblock1内核提供的设备入口第 2 层 · 超级块ext4 magic 0xEF53 · FAT BPB 签名文件系统在该块上的签名与几何参数第 3 层 · 挂载表/etc/fstab · 或 mount 命令「哪个设备挂到哪个目录」第 4 层 · 挂载点/data · 目录必须存在应用看到的路径排查挂载失败时的四个问题① 设备节点存在吗?② 超级块签名对吗?③ 分区号与分区表一致吗?④ 文件系统类型匹配吗?⑤ 挂载点目录存在吗?⑥ 内核编译了这个 FS 吗?常见失败与原因对照wrong fs type→ 用错 fs 名或未挂载bad superblock→ 分区偏移错或未格式化device busy→ 有进程占用,需先 umountno such file→ 挂载点目录不存在
图 9 · 挂载的四层解析链路与排查顺序

图 9 右侧的六个问题按顺序排查,九成以上的挂载失败在前四个就能定位。

挂载选项作用适用场景代价
ro只读挂载根文件系统、boot 分区无法写日志
noatime不更新访问时间SD 卡 / eMMC 常开失去 atime 追踪
relatime相对时间戳通用默认精度略低
discard删除时立即丢弃块SSD / eMMC增加 GC 压力
data=ordered日志式默认ext4 通用写延迟略高
data=writeback放宽写序追求吞吐掉电风险上升
sync每次写入立即落盘关键配置分区性能显著下降
noatime,commit=600批量提交日志分区最多丢 10 分钟日志
noatime 不是可选项

默认情况下每次 read() 都会更新文件的 atime(访问时间),这意味着「只读」也会触发写操作。在 64 MB 的 NOR 分区上,一个每小时读 1 MB 配置文件、每次读都触发 atime 更新的系统,会在几个月内把那个文件所在的块擦到寿命上限。

所有嵌入式挂载都必须加 noatime。这是嵌入式 Linux 最容易被遗漏、代价又最直接的一个挂载选项。

3.3 挂载顺序与依赖关系

挂载顺序错了会表现为「偶发启动失败」——因为根文件系统所在的分区还没挂上,init 就去读它配置。

顺序挂载点依赖缺失时的后果
1/dev(devtmpfs)无内核无法创建设备节点,应用报 no such file
2/proc无ps / top / 大量工具不可用
3/sys无udev 无法枚举设备,动态库加载可能失败
4/boot(只读)根设备可用内核启动参数缺失
5/(根,只读)boot 分区挂好init 找不到 /sbin/init
6/data(可写)根已挂载应用无法写配置,表现为只读错误
7/tmp(tmpfs)根已挂载大量程序无法启动
8/run /var(tmpfs)根已挂载日志写入失败

在 systemd 体系下,挂载顺序由 fstab 的依赖与 unit 的 After= 声明保证;在 BusyBox initramfs 里则要手工按上表顺序写 mount。一条实用的自检规则:把上面这张表做成脚本,逐项检查挂载点是否存在且挂载类型正确,作为开机自检的第一项。

3.4 只读挂载与 rootfs 切换

1. OverlayFS内核联合文件系统只读 lowerdir + 可写 upperdir + 工作目录 workdir。写操作只落在 upperdir,升级时只换 lowerdir。适用:最常用;需要内核 CONFIG_OVERLAY_FS;MCU 上通常没有。2. 只读挂载 + tmpfsmount -o remount,ro根以只读挂载,/tmp、/var、/run 挂 tmpfs。所有写入只落在内存,重启即丢。适用:最简单;需要足够 RAM;日志要外发到非易失区。3. 快照 / COW只读底层 + 写时复制底层是只读镜像,写时复制到另一分区。本质是 OverlayFS 的手工实现。适用:适合无 OverlayFS 但有 eMMC 的场景。为什么嵌入式特别需要只读根① 根分区只读 = 系统文件不可能被应用写坏,坏掉了重启就好;② 写放大只落在小块可写区;③ 升级只需替换只读镜像,配合 A/B 分区实现原子回滚;④ 安全启动校验的对象是只读镜像,不受运行时污染。
图 10 · 只读根与可写层的三种实现

只读根的四条收益在第 6 章还会再展开:

  • 系统文件不可被应用写坏——写坏了重启即可,不需要重刷;
  • 写放大只落在小块可写区——boot 与 rootfs 变成一次性写入;
  • 升级 = 替换只读镜像——配合 A/B 分区实现原子回滚;
  • 安全启动校验对象不被运行时污染——校验的是只读镜像,攻击者改不动。

3.5 tmpfs、ramfs 与内存文件系统

特性tmpfsramfs说明
大小可限制(size=)不限制,吃满即 OOM生产环境一律用 tmpfs 并限制大小
是否占 Swap可写入 swap不占tmpfs 内容可换出到 swap
支持 O_DIRECT不支持不支持需要直接 I/O 时应改用块设备直写
是否计入内存计入页缓存占连续物理内存ramfs 的连续分配容易碎片化
掉电丢失丢失关键数据绝不能只放这里
典型用途/tmp /run /var/run早期 initramfs现代系统基本只用 tmpfs
tmpfs 用满的表现不是报错,而是诡异故障

tmpfs 写满时,写调用返回 ENOSPC,但更麻烦的是:某些进程(如 systemd 的 journald、udev、dbus)会因写不进去而反复重试并吃掉 CPU。典型现象是「系统越来越慢,重启就好,但过几天又复现」。

规避方法:给每个 tmpfs 明确 size= 上限;给 /var/log 单独挂 tmpfs 并设 size=16M;journald 设 SystemMaxUse=16M。这些上限之和必须小于可用 RAM 的 20%。

4 · FTL 与闪存转换层

FTL 是「块设备」与「闪存物理特性」之间的翻译器。它决定了文件系统看到的块设备有多「干净」,也决定了寿命被消耗得有多快。

4.1 FTL 要解决的核心问题

一句话:把「可随机覆盖的块」伪装成「只能顺序写的页」。具体要解决四件事。

问题现象FTL 的解法
不能原地覆写写过的页不能重写映射到新页,旧页标记为无效
不能局部擦除擦除单位远大于写单位把「逻辑块」映射到「空闲的擦除块」
有坏块部分块永久不可用坏块池 + 备用块替换 + 标记管理
断电会丢映射映射表在 DRAM 里就全丢映射表持久化(OOB 或双份表 + CRC)

4.2 逻辑块与物理页的映射结构

逻辑页号(文件系统看到的)连续的逻辑地址,被翻译成物理上分散的页L0L1L2L3L4L5L6L7P5P2P9P1P12P7P3P15物理页号(介质上真实的)逻辑上连续的 8 个页,在物理上散布于整个芯片——这正是「顺序写」的物理基础FTL 必须维护的三张表映射表 L2P逻辑页 → 物理页 · 每次读都要查反向表 P2L物理页 → 逻辑页 · FTL 内部使用状态表每块已用多少页 · GC 时需要映射表存在哪里,直接决定掉电会不会丢文件· 存 OOB:与数据同页掉电,不一致风险最低,但 OOB 空间紧张、不能放太多信息;· 存独立表区域:容量大,但该区域自身也要可靠性保护(通常双份 + CRC);· 存 DRAM:最快,但掉电即全丢,除非有电池或 supercap 支撑。实践建议:MCU 平台优先选 OOB 或双份表;Linux 上交给内核 MTD 层处理,不要自己造。
图 11 · FTL 的逻辑页到物理页映射与三张必备表

图 11 的上半部分是映射本身:逻辑上连续的页,在物理上可以散布在整个芯片。这正是「顺序写」能高效的前提——GC 只需要搬数据,不需要原地改。

下半部分是三张必备表。其中映射表的存放位置是掉电安全的核心,图中的三种方案各有取舍:

方案优点缺点适用
OOB 区与数据同页掉电,不一致窗口最小空间紧张,通常只能存少量映射主机侧 FTL、SPI NAND
独立表区域容量大,可存更多元数据该区自身需双份 + CRC + 原子更新大容量 NAND 量产方案
DRAM速度最快掉电全丢,必须有后备(电池 / supercap)工业级高可靠方案
eMMC 上你看不到 FTL,但它的影响一样存在

使用 eMMC 时,FTL 由芯片内部实现,应用看到的是干净的块设备。但它仍然会:做垃圾回收、消耗寿命、在 SLC 缓存耗尽时掉速、在后台 GC 期间掉电丢数据。所以 eMMC 上的开发仍然要做寿命预算(第 9 章)与关键数据保护(第 13 章),只是不能也不需要自己写 FTL。

4.3 垃圾回收的三种策略

1. 贪心 / 立即回收写到一个已用页就立即找新页并回收旧页权衡:写延迟最稳定;回收频繁;空闲块始终充足空闲块2. 惰性 / 按需回收直到空闲块低于阈值才开始 GC权衡:写延迟有尖峰;平时几乎零开销;空闲块少时集中回收空闲块3. 预留块 / 冷热分离留出专用块,热数据不与冷数据混在同一块权衡:磨损最均衡;需要额外空间;实现最复杂空闲块三种策略的共同前提:擦除只能整块,且块内数据必须全部有效才能回收· 块内有效页比例低 → 回收收益差,这就是碎片化的代价;· 回收时要读出有效页、写到新块、校验、再擦旧块 —— 一次回收的写放大可能是数据量的数倍;· 回收过程本身也可能掉电中断,因此 FTL 必须保证「回收未完成」与「回收已完成」两种状态都可恢复。
图 12 · 三种垃圾回收策略与共同前提

三种策略的差别本质上是「写延迟稳定性」与「平均效率」之间的取舍。对嵌入式产品,评价标准通常不是平均吞吐,而是最坏情况下的写延迟——因为一次超长的 GC 停顿可能直接触发看门狗或用户可感知的卡顿。

策略写延迟平均效率空闲块需求适合
贪心 / 立即回收最稳定低(回收频繁)高硬实时要求、eMMC 常用
惰性 / 按需回收有尖峰中中通用嵌入式,默认选择
冷热分离较稳定高高(需额外空间)长期运行、日志与数据混存

4.4 坏块管理与备用块池

1. 出厂坏块厂商在出厂时扫描并标记文件系统必须跳过,不得尝试擦除(擦除会加剧失效甚至扩成大坏块)2. 增长坏块使用中擦除失败或校验出错标记为坏块,从可用池中移除记录到坏块表(FTL 或 MTD 层)3. 备用块池预留若干块专门用于替换坏块出现时用备用顶上延迟介质失效的时间点三条必须遵守的规则① 出厂标记的坏块永远不要擦——手册会明写「do not erase」,擦它可能把局部失效扩大成整块失效;② 坏块表本身要可靠:每个记录加 CRC,掉电后能识别损坏;Linux 上用 MTD 的 bad_blocks 机制;③ 坏块增长要有告警:NAND 从 0 增长到几十个是正常的,突然上百说明存储条件(温度、电压、磨损)有问题。
图 13 · 坏块的三种处理方式与三条铁律

坏块处理的核心原则是「宁可少一块,不能用坏块」。一条坏块的错误处理如果不当(比如反复重试到超时才标记),会浪费大量时间并可能扩大损伤。

4.5 FTL 的一致性保证

FTL 层面的掉电一致性问题与文件系统正交但相关。掉电可能发生在 GC 的任意一步:

/* GC 的四个阶段与掉电后的可恢复性 */
1. 选择牺牲块 V(有效页最少)
2. 把 V 中有效页复制到空闲页 P        <- 掉电:V 与 P 同时存在,重复占用空间但可恢复
3. 更新映射表,把逻辑页指向 P       <- 掉电:映射表未更新,数据仍在 V,可重做
4. 标记 V 无效,加入空闲块池       <- 掉电:V 未标记,下次 GC 会重做,不丢数据

关键:第 3 步的映射表更新必须是「原子提交」,
     否则会出现「映射指向 P,但 P 里数据没写完」的错误指针。
FTL 一致性的设计原则
  • 宁可重复,不可丢失:掉电后允许同一份数据占两个物理块,这只浪费空间;反过来「丢失」无法恢复。
  • 映射表提交要原子:用「双份表 + 序号 + CRC」或「写 OOB + ECC 保护」实现。
  • GC 全程可重入:每个阶段开始时都能判断「上次做到哪」,重做而不是从头推断。
扫码打开本页
微信扫码 · 展会可扫

再分享给同事

同事可直接打开这份资料《嵌入式文件系统开发指南》,在线阅读;完整章节请下载芯参谋。