OpenWRT(20):NAND上squashfs+ubifs的overlay实现rootfs
OpenWrt 使用 squashfs + ubifs 组合作为根文件系统(rootfs)的设计,是针对嵌入式设备(尤其是基于 NAND Flash 的硬件)的一种优化方案。这种设计结合了两种文件系统的优势,解决了嵌入式场景下的关键需求。
1 背景说明
- 嵌入式设备的硬件限制
-
- NAND Flash 特性:嵌入式设备(如路由器、IoT 设备)通常使用 NAND Flash 存储,但其存在以下挑战:
- 坏块管理:NAND Flash 在生命周期中会出现坏块,需动态管理。擦写次数限制:每个存储单元的擦写次数有限(通常约 1~10 万次)。非对齐访问:需避免频繁的小文件写入,延长 Flash 寿命。
资源受限:嵌入式设备存储空间有限,需高压缩率的文件系统。
- NAND Flash 特性:嵌入式设备(如路由器、IoT 设备)通常使用 NAND Flash 存储,但其存在以下挑战:
- OpenWrt 的需求
- 固件可靠性:系统核心部分需保持稳定,避免因用户误操作或意外断电损坏。
- 可写数据隔离:用户配置、日志等需持久化存储,但与只读系统分离。
- 快速恢复:支持通过复位操作恢复出厂设置,同时保留核心系统不变。
2 镜像生成
OpenWrt 将根文件系统分为两部分:
- squashfs:作为只读的根文件系统,存储系统核心文件(内核、预装软件)。
- ubifs(在 rootfs_data 卷中):作为可写分区,存储用户配置、临时文件等,通常挂载为 OverlayFS 的顶层(实现写入覆盖)。
这两个文件系统通过 UBI(Unsorted Block Images) 框架管理,位于同一 MTD 分区中,但分为不同的 UBI 卷(Volume)。
这么实现具有如下优点与收益:
- 可靠性提升
- - 只读的 squashfs:
- - 系统核心文件不可修改,避免因异常断电或错误操作导致系统损坏。
- - 固件升级时只需替换整个 squashfs,操作原子性强,失败风险低。
- - 崩溃恢复:若用户空间数据(ubifs)损坏,可通过删除 rootfs_data 卷快速恢复出厂设置,而无需重刷固件。
- - 只读的 squashfs:
- 存储效率优化
- - 高压缩率:squashfs 支持多种压缩算法(如 LZMA、Zstd),显著减少固件体积,节省 Flash 空间。
- - UBI 的磨损均衡:ubifs 基于 UBI,自动处理坏块管理和磨损均衡,延长 NAND 寿命。
- 读写性能分离
- - 只读操作:squashfs 的只读特性减少对 NAND 的写入压力。
- - 高效写入:ubifs 针对 Flash 优化,支持日志(journaling)和快速随机写入,适合频繁变更的小文件(如配置文件)。
- 灵活的 OverlayFS 机制
- - OverlayFS 叠加:将 ubifs 挂载为 OverlayFS 的可写层,覆盖在 squashfs 之上,用户的所有修改(如安装软件、配置变更)仅影响 ubifs 层,而 squashfs 保持纯净。
- - 空间复用:用户可动态扩展可写层,无需预先分配固定大小。
- 兼容性与维护便利
- - 固件升级简化:升级时只需更新 squashfs 镜像,用户数据(ubifs)独立保留。
- - 调试友好:可通过重置 rootfs_data 快速排除软件配置问题,无需重刷固件。
在《OpenWRT(10):OpenWRT下rootfs的cpio/squashfs/ubifs/ext4生成流程》中介绍了生成rootfs.ubi的工具ubinize-image.sh,其配置文件ubinize.cfg如下:
[rootfs] # 定义rootfs卷的配置。 mode=ubi # 使用UBI文件系统模式。 vol_id=0 # 分区的ID。 vol_type=dynamic # 动态卷,大小可以根据需要增长。 vol_name=rootfs # 卷的名称。 image=root.squashfs # 指定SquashFS镜像文件的路径。 vol_size=21471232 # 卷的大小。 [rootfs_data] # 定义rootfs_data卷的配置。 mode=ubi # 使用UBI文件系统模式。 vol_id=1 # 分区的ID。 vol_type=dynamic # 动态卷,大小可以根据需要增长。 vol_name=rootfs_data # 卷的名称,通常用于存储用户数据。 vol_size=1MiB # 卷的大小,单位是MiB(1MB)。 vol_flags=autoresize # 卷的标志,autoresize表示卷可以自动调整大小。
参考《[OpenWrt Wiki] The OpenWrt Flash Layout》,以root.squashfs作为rootfs volume的输入,创建一个空的rootfs_data volume。输出rootfs.ubi文件:
3 命令行配置
一个bootargs示例如下:
Kernel command line: earlycon=... console=... ubi.mtd=rootfs,2048 ubi.block=0,rootfs root=/dev/ubiblock0_0 rootfstype=squashfs ro rootwait mtdparts=spi0.0:...,64m(rootfs),...
最终形成的squashfs+ubifs在NAND上实例如下:
解读如下:
- 通过mtdparts可以知道:rootfs分区表用于存放rootfs。
- ubi.mtd=rootfs,2048,表示第一个ubi对应rootfs分区。即ubi0。
- ubi.block=0,rootfs,表示在ubi0上的rootfs volume创建ubiblock设备,对应/dev/ubiblock0_0。
- root=/dev/ubiblock0_0表示使用rootfs分区的rootfs volume作为根文件系统。
- rootfstype=squashfs表示rootfs的类型为squashfsh。
4 启动流程
根据以上command line配置,启动流程如下:
- Kernel进行command line中ubi.mtd指定的分区attach。autoresize的volume进行大小调整。
- 对ubi.block配置生成ubiblock设备。
- OpenWRT的preinit调用mount_root在rootfs分区的rootfs_data volume上创建文件系统:
UBIFS (ubi0:1): default file-system created
- mount_root进行剩余的overlay等工作。
启动后mount:
/dev/root on /rom type squashfs (ro,relatime,errors=continue) /dev/ubi0_1 on /overlay type ubifs (rw,noatime,assert=read-only,ubi=0,vol=1) overlayfs:/overlay on / type overlay (rw,noatime,lowerdir=/,upperdir=/overlay/upper,workdir=/overlay/work,uid=on) ...
lowerdir是overlayfs的下层目录,这里指的是rootfs volume的squashfs挂载的根目录/。
upperdir是overlayfs的上层目录,这里指的是rootfs_data volume的ubifs挂载的根目录/overlay/upper。
workdir是overlayfs挂载时的工作目录,用于存储overlayfs操作的元数据和临时文件。workdir对用户是透明的,通常由overlayfs自动创建和管理。
5 内核相关配置
为了使能squashfs+ubifs的rootfs方案,需要如下配置:
Device Drivers
Memory Technology Device (MTD) support
Enable UBI - Unsorted block images--支持UBI。
Read-only block devices on top of UBI volumes--支持基于UBI的block设备。
File systems
Overlay filesystem support--支持overlayfs。
Miscellaneous filesystems
UBIFS file system support--支持ubifs。
SquashFS 4.0 - Squashed file system suport--支持squashfs。
6 其他问题
6.1 为什么ubifs volume可以用/dev/ubiX_Y,而squashfs不行?
在UBI卷上挂载UBIFS(Unsorted Block Image File System)时,可以直接通过UBI字符设备(如/dev/ubiX_Y)进行挂载,这与SquashFS需要通过UBI块设备(/dev/ubiblockX_Y)挂载的行为不同。这种差异源于UBIFS与UBI的紧密集成设计以及文件系统与存储管理的分工不同。以下是具体原因:
1.UBIFS是专为UBI设计的文件系统
UBIFS是专门针对UBI(Unsorted Block Images)卷设计的文件系统,其核心设计目标是直接利用UBI的特性(如坏块管理、磨损均衡、逻辑擦除块映射等)。因此:
- 直接操作UBI卷:UBIFS不需要通过块设备抽象层(如ubiblock),而是直接与UBI子系统交互,通过UBI字符设备(/dev/ubiX_Y)访问物理闪存。
- 感知UBI元数据:UBIFS能够理解UBI卷的布局(例如逻辑擦除块LEB与物理擦除块PEB的映射),从而直接管理存储空间,而无需依赖块设备的中间层。
2.UBI字符设备提供了UBIFS所需的功能
UBI字符设备(/dev/ubiX_Y)虽然是字符设备接口,但它隐含了UBI的管理逻辑,包括:
- 逻辑擦除块(LEB)的访问:UBIFS通过UBI字符设备直接读写逻辑擦除块,UBI子系统会自动处理坏块替换和磨损均衡。
- 原子写入支持:UBI提供了原子操作(如UBI_IOCVOLUP),UBIFS利用这些特性实现崩溃安全性(例如通过UBIFS的日志机制)。
- 卷更新接口:UBIFS在挂载时会通过UBI字符设备直接验证卷的元数据(如超级块),而无需块设备的文件系统格式解析。
3.UBIFS与块设备抽象(ubiblock)的无关性
UBIFS的设计目标是直接管理UBI卷,而不是依赖传统的块设备抽象:
- 无需块设备缓存:UBIFS自身实现了高效的缓存机制(如Page Cache和日志缓存),直接通过UBI字符设备访问闪存。
- 直接处理物理特性:UBIFS需要感知闪存的擦除块大小、最小I/O单元等物理特性,这些特性由UBI子系统通过字符设备接口传递,而块设备会隐藏这些细节(导致UBIFS无法通过块设备正确操作)。
4.对比:SquashFS为何需要块设备
与UBIFS不同,SquashFS是一个通用只读文件系统,其设计不针对UBI或闪存:
- 依赖块设备接口:SquashFS需要通过标准的块设备接口(如/dev/ubiblockX_Y)访问数据,因为它无法直接处理UBI的逻辑擦除块映射或坏块管理。
- 无UBI感知能力:SquashFS假设底层存储是连续的、无坏块的块设备,而UBI字符设备无法满足这一假设(需要通过ubiblock将其转换为“虚拟”的可靠块设备)。
5.技术实现差异的总结
特性 | UBIFS | SquashFS |
设计目标 | 专为UBI/闪存优化 | 通用的只读压缩文件系统 |
底层接口 | 直接使用UBI字符设备(/dev/ubiX_Y) | 依赖UBI块设备(/dev/ubiblockX_Y) |
存储管理 | 直接利用UBI的坏块管理和磨损均衡 | 假设底层块设备是可靠的(需ubiblock保障) |
缓存与I/O调度 | 自行实现缓存,直接操作UBI | 依赖内核块层缓存和调度 |
原子操作与崩溃安全 | 通过UBI原子操作实现 | 无此需求(只读) |
UBIFS可以直接通过UBI字符设备挂载,是因为它专为UBI设计,能够直接利用UBI的存储管理功能(坏块处理、磨损均衡),无需块设备抽象。而SquashFS必须通过UBI块设备挂载,因为它是一个通用文件系统,依赖块设备接口的可靠性和标准性。这种差异体现了文件系统与底层存储管理层的不同分工。
由于squashfs在UBI volume中,所以无法直接使用/dev/mtdx或者/dev/mtdblockx设备。
所以rootfs volume使用块设备/dev/ubiblockX_Y挂载;rootfs_data volume使用字符设备/dev/ubiX_Y挂载。