
ImmortalWrt TF 卡扩容实践:分区扩展与 SquashFS/F2FS Overlay 调整
基于 ImmortalWrt TF 卡扩容实践,说明分区边界、文件系统容量与 SquashFS/F2FS Overlay 的关系,并给出 EXT4、首次启动前 SquashFS 和已生成 Overlay 三类场景的离线扩容与验证流程。
将 ImmortalWrt 镜像写入一张 16 GB TF 卡后,系统报告的可用空间只有数百 MB。这通常源于镜像携带的固定分区表没有覆盖卡尾空间,与存储介质的实际容量无关。
仅扩展第二分区后,df -h 显示的容量仍可能保持不变;对于 SquashFS 镜像,直接对 /dev/sdb2 执行 resize.f2fs 还会得到文件系统类型不匹配的错误。原因是分区边界、文件系统容量以及可写 Overlay 分属不同层次,需要分别识别和调整。
本文基于一次已经启动并生成 F2FS Overlay 的 ImmortalWrt TF 卡扩容实践,说明存储设备、分区、文件系统和 Overlay 之间的关系,并给出可复用的判断与操作流程。
操作覆盖 EXT4 根文件系统、首次启动前的 SquashFS 镜像,以及已经生成 Overlay 的 SquashFS 镜像。文中的 /dev/sdb 和 /dev/sdb2 仅为示例,执行前必须替换为实际设备。
处理原则:
-
TF 卡剩余容量通常没有丢,只是位于镜像分区表之外,处于未分配状态。
-
扩大分区,只是移动分区的结束边界;文件系统还要用自己的工具继续扩大。
-
EXT4 使用
resize2fs,F2FS 使用resize.f2fs,两者不能混用。 -
SquashFS 镜像已经启动过时,可写 Overlay 可能藏在同一个根分区内部,需要先按偏移映射成 Loop 设备。
-
修改前必须确认设备名、卸载相关分区并备份分区表。不要直接复用示例中的
/dev/sdb,并确保目标不是计算机系统盘。
镜像写入后容量受限的原因
写镜像会把镜像内的分区表和系统文件一起完整复刻到卡上。
将 ImmortalWrt 的 .img 写入 TF 卡时,写入内容不仅包括系统文件,还包括镜像预先定义的分区表。假设镜像按下面的容量构建:
-
一个十几 MB 的启动分区;
-
一个几百 MB 的根分区;
-
根分区之后没有任何已定义分区。
无论目标介质是 4 GB、16 GB 还是 64 GB,写入后的初始分区结构都由镜像决定。容量更大的 TF 卡只会在末尾保留更多未分配空间,系统不会自动修改镜像中的分区边界。
因此,重复写入同一镜像不会解决容量问题,只会再次写入相同的固定分区表。

上图里,青色边框代表分区,橙色区域代表文件系统真正能够管理的空间。扩容时必须完成两层动作:
- 先让根分区的结束位置向后延伸;
- 再让文件系统使用分区新增的空间。
GNU Parted 的手册明确说明,resizepart 只移动分区结束位置,并不会修改分区中的文件系统;resize2fs 手册也反过来强调,它只调整 EXT2/3/4 文件系统,不负责改变分区大小。
Linux 存储层次:设备、分区、文件系统与挂载点
在 Linux 中,这几层具有明确的包含关系:
TF/SD 卡设备
└── 分区表
├── 启动分区
└── 根分区
└── 文件系统
└── 目录和文件
对应到设备名:
| 层次 | 常见名字 | 它表示什么 |
|---|---|---|
| 整块存储设备 | /dev/sdb、/dev/mmcblk0 |
整张 TF 卡,包括分区表和未分配空间 |
| 某个分区 | /dev/sdb2、/dev/mmcblk0p2 |
分区表定义的一段连续扇区范围 |
| 文件系统 | EXT4、F2FS、SquashFS | 组织文件、目录、权限和空闲块的格式 |
| 挂载点 | /、/rom、/overlay |
文件系统进入 Linux 目录树的位置 |
最关键的一句话是:分区是容器,文件系统是容器里的内容。
容器变大,里面的内容不会自动铺满;而文件系统也不可能越过容器边界。扩容的正确顺序因此总是:
确认磁盘 → 备份 → 卸载 → 扩大分区 → 识别文件系统 → 检查 → 扩大文件系统 → 再检查 → 验证
ImmortalWrt 的 SquashFS 与 OverlayFS 布局
对于 EXT4 镜像,上述模型已经足够。本文实践使用的是 SquashFS 镜像,其根目录由只读基础层和可写 Overlay 共同组成。
SquashFS 是压缩、只读的系统底座,通常挂载到 /rom;安装软件、修改配置和写入文件产生的变化,会放进可写的 /overlay。OverlayFS 再把两者合并,对外呈现一个看起来可写的 /。
只读 SquashFS(/rom)
+
可写 rootfs_data(/overlay)
↓
用户看到的统一根目录(/)
OpenWrt 的文件系统说明和Extroot 文档都描述了这种只读底座与可写 Overlay 合并的设计。它有两个很实用的结果:系统镜像可以压缩得很小,恢复出厂设置时也不需要改动只读底座。
在部分 SD 卡 SquashFS 镜像里,rootfs_data 不作为独立分区写入分区表,而是紧跟在 SquashFS 之后、位于同一个根分区内部。较新的镜像常用 F2FS,也可能出现 EXT4,因此必须检测实际类型,不能仅根据镜像名称判断。

因此不能直接对 /dev/sdb2 执行 resize.f2fs:该分区起点是 SquashFS,F2FS 超级块位于分区内部的后续偏移。需要跳过 SquashFS 和对齐间隔,从正确偏移建立 Loop 设备,再对映射出的文件系统执行检查和扩容。
OpenWrt 的SD 卡安装文档给出了相同的布局与偏移计算方法,并指出:镜像第一次启动前 Overlay 尚未创建;启动后,才需要处理已经生成的 Overlay 文件系统。
扩容场景判定
下面四种情况,操作路径完全不同。
| 场景 | 处理方式 |
|---|---|
| EXT4 根文件系统 | 扩大分区,再用 e2fsck 和 resize2fs 扩大 EXT4 |
| SquashFS,刷完后从未启动 | 只扩大根分区;首次启动通常会用剩余空间创建 Overlay |
| SquashFS,已经启动并生成 Overlay | 扩大分区,再按偏移映射 Overlay,识别为 F2FS 或 EXT4 后扩容 |
| Btrfs、XFS、LVM、加密卷或无法识别 | 停止操作,按对应存储栈使用专用工具,不套用本文命令 |
场景判断需要回答三个具体问题:
- 哪一块才是 TF 卡?
- 要扩大的根分区是哪一个?
- 根分区里真正需要扩大的文件系统是什么?
操作环境:Ubuntu Live
OpenWrt 提供了根分区与文件系统在线扩容脚本。为避免操作正在使用的根文件系统,并便于独立检查分区和 Overlay,本文使用 Ubuntu Live:从 U 盘进入“试用 Ubuntu”,不安装到计算机硬盘。
Ubuntu 官方文档说明了 Live 试用环境的启动方式。进入桌面后,先安装本文需要的工具:
sudo apt update
sudo apt install -y parted gdisk e2fsprogs f2fs-tools squashfs-tools util-linux
这些工具的分工很明确:
| 工具 | 用途 |
|---|---|
lsblk、blkid |
识别设备、分区和文件系统 |
parted、sfdisk |
查看或修改分区表 |
e2fsck、resize2fs |
检查和扩展 EXT2/3/4 |
fsck.f2fs、resize.f2fs |
检查和扩展 F2FS |
unsquashfs |
读取 SquashFS 元数据,获得文件系统长度 |
losetup |
从分区内部某个偏移创建块设备视图 |
Live 系统关机后,保存在它临时目录中的文件会消失。分区表备份要复制到电脑硬盘、另一块 U 盘或网络存储,不要只放在 Live 桌面上。
识别目标设备与分区
建议先移除无关的移动硬盘和 U 盘,再插入 TF 卡并执行:
lsblk -p -o NAME,SIZE,MODEL,SERIAL,TRAN,TYPE,FSTYPE,LABEL,MOUNTPOINTS
本文示例中的 TF 卡为 /dev/sdb,根分区为 /dev/sdb2。实际设备也可能是 /dev/sdc,或者读卡器暴露的 /dev/mmcblk0;后者的第二分区写作 /dev/mmcblk0p2。
继续通过分区表和设备信息交叉确认:
sudo parted /dev/sdb unit MiB print free
sudo fdisk -l /dev/sdb
重点看四件事:
-
设备总容量是否接近 TF 卡标称容量;
-
型号、传输方式和插拔前后出现的设备是否一致;
-
第二分区结束位置之后是否存在大量
Free Space; -
电脑系统盘是否另有其盘,例如常见的
/dev/nvme0n1。
如果仍无法确定目标设备,应立即停止操作。设备名选择错误会直接修改其他磁盘的数据。
备份分区表并卸载分区
确认设备后,先导出分区表。下面的备份路径应位于另一块持久存储中:
sudo sfdisk -d /dev/sdb | sudo tee /path/to/backup/immortalwrt-tf-card.sfdisk
同时记录扩容前的分区起点:
sudo parted /dev/sdb unit s print
使用扇区作为单位,是为了准确记录最重要的安全约束:只改变第二分区的结束位置,不移动其起始扇区。 起点变化会改变原文件系统的数据偏移,可能导致系统无法启动。resize2fs 手册也特别警告,重建或调整底层分区时必须保持原起点。
Ubuntu 可能会自动挂载 TF 卡上的分区。先查看:
lsblk -p -o NAME,FSTYPE,SIZE,MOUNTPOINTS /dev/sdb
再按实际挂载结果卸载对应分区:
sudo umount /dev/sdb1
sudo umount /dev/sdb2
如果提示目标没有挂载,可以忽略;如果提示 busy,先关闭文件管理器里打开的目录,再用 lsof 或 fuser 找到占用进程。不要在文件系统仍然挂载时做离线检查。
扩展根分区边界
确认根分区编号为 2 后,执行:
sudo parted -s /dev/sdb resizepart 2 100%
sudo partprobe /dev/sdb
sudo udevadm settle
然后立即复查:
sudo parted /dev/sdb unit MiB print free
sudo parted /dev/sdb unit s print
理想结果是:
-
第二分区的结束位置接近整张卡的末尾;
-
第二分区起始扇区和备份前完全一致;
-
尾部的大块未分配空间消失;
-
第一分区以及其他分区没有变化。
如果内核仍显示旧大小,不要继续扩文件系统。安全退出并重启 Live 环境,重新确认设备状态后再做下一步。
到这里,只完成了分区层扩容。接下来必须根据文件系统进入不同路径。
路径 A:根文件系统是 EXT4
如果 lsblk -f 或 blkid 明确显示根分区是 ext4,在确保它未挂载后执行:
sudo e2fsck -f /dev/sdb2
sudo resize2fs /dev/sdb2
sudo e2fsck -f /dev/sdb2
e2fsck 是 EXT2/3/4 的一致性检查工具,官方手册不建议对已挂载的文件系统执行检查。resize2fs 不给目标大小时,会把文件系统扩展到当前底层设备允许的最大范围。
完成后可以只读挂载验证:
sudo mkdir -p /mnt/immortalwrt-root
sudo mount -o ro /dev/sdb2 /mnt/immortalwrt-root
df -Th /mnt/immortalwrt-root
sudo umount /mnt/immortalwrt-root
如果 blkid 显示 F2FS,则不能使用 resize2fs;该工具只支持 EXT2/3/4。
路径 B:首次启动前的 SquashFS 镜像
OpenWrt 的 SD 卡文档说明,SquashFS 镜像第一次启动前,隐藏的 Overlay 还没有生成。只要先把根分区扩大到卡尾,首次启动时系统通常就会在 SquashFS 后面的可用区域创建 Overlay。
也就是说,这条路径完成前面的 resizepart 并复查后,就可以安全关机、把卡插回设备,然后让 ImmortalWrt 完成第一次启动:
sync
sudo poweroff
不要预先格式化空白区域。不同镜像的初始化逻辑可能不同,额外创建文件系统可能破坏首次启动流程。
路径 C:已经生成 Overlay 的 SquashFS 镜像
本次实践属于该场景:根分区已经扩大,但首次启动时创建的 Overlay 仍保持原始容量。
根分区虽然已经被扩大,但第一次启动时创建的 Overlay 仍保持原来的小容量。此时要做三件事:
- 算出 SquashFS 在分区中占用的长度;
- 按 64 KiB 向上对齐,得到 Overlay 的起始偏移;
- 从这个偏移创建 Loop 设备,再识别并扩展其中的 F2FS 或 EXT4。
第 1 步:设置变量并再次确认。
PARTITION=/dev/sdb2
lsblk -p -o NAME,SIZE,FSTYPE,LABEL,MOUNTPOINTS "$PARTITION"
sudo blkid "$PARTITION"
这里 blkid /dev/sdb2 很可能只报告 squashfs,因为它读取的是分区起点。这不代表后面不存在 Overlay。
第 2 步:读取 SquashFS 大小并计算偏移。
FS_SIZE="$(sudo env LC_ALL=C unsquashfs -s "$PARTITION" \
| grep -o 'Filesystem size [0-9]* bytes' \
| grep -o '[0-9][0-9]*')"
test -n "$FS_SIZE" && test "$FS_SIZE" -gt 0 || {
echo "无法读取 SquashFS 大小,停止操作"
exit 1
}
FS_OFFSET=$(( (FS_SIZE + 65535) / 65536 * 65536 ))
printf 'SquashFS size: %s bytes\nOverlay offset: %s bytes\n' \
"$FS_SIZE" "$FS_OFFSET"
偏移计算等价于:
向上对齐后的偏移 = ceil(SquashFS 字节数 / 65536) × 65536
如果 SquashFS 末尾没有刚好落在 64 KiB 边界,就向后补到下一个边界。OpenWrt 文档指出,这个逻辑来自 libfstools/rootdisk.c 的根盘处理方式。
第 3 步:创建 Loop 设备。
不要预设空闲设备为 /dev/loop0。由系统分配 Loop 设备,并保存返回值:
LOOP_DEVICE="$(sudo losetup --find --show --nooverlap \
--offset "$FS_OFFSET" "$PARTITION")"
echo "$LOOP_DEVICE"
sudo losetup -l -O NAME,BACK-FILE,OFFSET,SIZELIMIT "$LOOP_DEVICE"
Loop 设备会给一段已有数据建立新的块设备视图,并不复制数据。--offset 让这个视图从分区内部指定位置开始;从此,文件系统工具看到的“设备起点”就是 Overlay 的起点。losetup 手册也建议在自动寻找 Loop 设备时避免为同一后端创建重叠映射,因此这里增加了 --nooverlap。
第 4 步:先识别文件系统,暂时不要扩容。
sudo blkid "$LOOP_DEVICE"
sudo file -s "$LOOP_DEVICE"
本次实践识别到的文件系统为 F2FS,标签通常显示为 rootfs_data。只有当输出明确包含 TYPE="f2fs" 或 F2FS filesystem 时,才执行 F2FS 扩容步骤。
如果显示 EXT4,应使用 EXT4 工具链;如果仍然无法识别,说明偏移、镜像布局或设备选择可能存在错误,应立即停止并解除映射:
sudo losetup -d "$LOOP_DEVICE"
第 5 步:检查、扩展 F2FS、再检查。
sudo fsck.f2fs -f "$LOOP_DEVICE"
sudo resize.f2fs "$LOOP_DEVICE"
sudo fsck.f2fs -f "$LOOP_DEVICE"
Linux 内核的 F2FS 文档说明,resize.f2fs 用于在保留现有文件和目录的情况下调整 F2FS 镜像大小。这里不传目标大小,让它使用 Loop 设备当前可见的全部空间。
如果 Loop 设备识别为 EXT4,则使用:
sudo e2fsck -f "$LOOP_DEVICE"
sudo resize2fs "$LOOP_DEVICE"
sudo e2fsck -f "$LOOP_DEVICE"
文件系统工具必须与格式匹配。EXT4 和 F2FS 的元数据结构不同,选错工具只会报错,强行继续还有破坏数据的风险。
第 6 步:只读挂载 Overlay 做最后检查。
sudo mkdir -p /mnt/immortalwrt-overlay
sudo mount -o ro "$LOOP_DEVICE" /mnt/immortalwrt-overlay
df -Th /mnt/immortalwrt-overlay
sudo find /mnt/immortalwrt-overlay -maxdepth 1 \
-type d -printf '%f\n' | sort
正常情况下,容量应该已经接近扩大后的根分区可用空间,并且能看到 upper、work 等 Overlay 相关目录。检查完成后按顺序清理:
sudo umount /mnt/immortalwrt-overlay
sync
sudo losetup -d "$LOOP_DEVICE"
sudo poweroff
等电脑完全关机后再拔卡,避免缓存尚未落盘。
目标设备启动后的验证
扩容工具成功退出后,仍需在目标设备上验证最终状态。将 TF 卡插回单板机并启动 ImmortalWrt,首先检查挂载关系和容量:
df -Th
cat /proc/partitions
mount | grep -E 'overlay|/rom|/overlay'
/dev/root 挂载到 /rom 后显示 100% 使用率通常属于正常现象。SquashFS 是固定大小的只读基础层,验证重点应放在 /overlay 以及合并后的 / 是否获得新增容量。
随后执行写入持久化测试:
date > /root/resize-persistence-test.txt
sync
reboot
重启后:
cat /root/resize-persistence-test.txt
rm /root/resize-persistence-test.txt
文件仍在,说明写入确实落在可写 Overlay 中,而不是只活在内存临时层。最后再进入 LuCI 查看存储空间,确认界面数据和命令行结果一致。
常见故障与原因
分区已经变大,df 为什么没变?
因为只完成了分区层,文件系统仍记录着旧的块数量。继续执行对应文件系统的扩容工具,而不是重复拖动分区边界。
对 /dev/sdb2 执行 resize.f2fs,提示不是 F2FS。
在 SquashFS + 隐藏 Overlay 布局中,分区起点是 SquashFS。先计算偏移并创建 Loop 设备,再对识别为 F2FS 的 Loop 设备操作。
resize2fs 报 bad magic number 或 bad superblock。
目标文件系统可能不是 EXT2/3/4。应先使用 blkid 和 file -s 识别,而不是通过 -f 参数强制执行。OpenWrt 的 SD 卡文档也指出,该错误可能意味着实际使用的是 F2FS,此时应改用 resize.f2fs。
把 /dev/sdb 和 /dev/sdb2 混用。
parted 修改的是整盘上的分区表,所以传整盘 /dev/sdb;文件系统检查和扩容操作针对具体分区或 Loop 设备。对象层次不同,参数也不同。
写死 /dev/loop0。
桌面系统、容器或 Snap 都可能已经占用 Loop 设备。使用 losetup --find --show 获取实际设备名,并在结束时只解除自己创建的那个映射。
移动了分区起点。
移动起点会改变文件系统所有数据相对设备起点的位置。扩容只移动结束边界。操作前后都用扇区单位记录并核对起点。
Windows 弹出“需要格式化”。
不要点格式化。Windows 不认识 Linux 文件系统时会给出这个提示,它不代表 TF 卡已经损坏。
在正在运行的目标系统里直接照搬离线命令。
本文的 fsck 流程假设文件系统未挂载。在线根分区扩容涉及重挂载、重启和镜像差异,不应把 Live 环境的命令原样贴进运行中的路由器。
相关 Linux 存储知识
1. 块设备只是字节范围,不等于文件系统。
/dev/sdb2 本质上是一段连续扇区。系统把它解释成什么,取决于其中的元数据。Loop 设备更能说明这一点:同一个分区从不同偏移观察,可以暴露出不同的文件系统视图。
2. “挂载”是在目录树中接入文件系统。
Linux 不使用 Windows 那样固定的盘符。设备只有挂载到某个目录后,里面的文件才进入统一目录树。OpenWrt 把 SquashFS 挂到 /rom、可写层挂到 /overlay,再把合并视图作为 /,正是这一模型的典型应用。
3. 分区表和文件系统各自维护边界。
分区表记录“这段存储从哪里开始、到哪里结束”;文件系统超级块记录块大小、块数量、空闲空间和其他元数据。两者都要更新,所以扩容天然是两阶段操作。
4. 文件系统工具不能跨类型通用。
EXT4 使用 e2fsprogs 工具链,F2FS 使用 f2fs-tools;Btrfs、XFS、LVM 和 LUKS 则具有各自的层次与管理命令。因此,文件系统识别必须先于扩容操作。
5. 只读底座 + 可写层是一种工程取舍。
SquashFS 提供高压缩率和稳定底座,Overlay 保存变化。它适合空间有限、需要恢复能力的嵌入式系统,但也让外部维护多出一层偏移与映射。理解这个取舍后,/rom 100% 和隐藏 rootfs_data 就不再反直觉。
扩容操作检查清单
执行前应逐项确认:
-
已拔掉无关移动存储,避免选错盘;
-
已用型号、容量、接口和插拔变化确认整盘设备;
-
已确认目标根分区编号;
-
已将分区表备份到持久存储;
-
已记录根分区起始扇区;
-
已卸载 TF 卡上的相关分区;
-
parted print free明确显示卡尾有未分配空间; -
扩大分区后,起点未变、终点已后移;
-
已判断是 EXT4、未启动的 SquashFS,还是已生成 Overlay 的 SquashFS;
-
已用
blkid和file -s验证实际文件系统; -
文件系统检查无致命错误后才执行扩容;
-
扩容后再次执行文件系统检查;
-
已只读挂载验证容量和目录;
-
已卸载并解除 Loop 设备;
-
已执行
sync并正常关机后再拔卡; -
目标设备启动后已验证容量、挂载关系和重启持久化。
总结
TF 卡镜像扩容涉及磁盘、分区、文件系统、挂载点和 OverlayFS 多个层次。容量受限的直接原因通常是镜像中的固定分区边界,而最终可用空间还取决于文件系统是否完成扩展。
处理同类问题时,应依次确认:
空闲空间在哪一层?
需要调整的是哪个边界?
真正的可写文件系统从哪里开始?
它是什么类型?
如何验证扩容结果和数据持久化?
完成这些判断后,即可将同一方法应用于 ImmortalWrt、OpenWrt 以及其他单板机 Linux 镜像的扩容场景。