ImmortalWrt TF 卡扩容实践:分区扩展与 SquashFS/F2FS Overlay 调整

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 卡只会在末尾保留更多未分配空间,系统不会自动修改镜像中的分区边界。

因此,重复写入同一镜像不会解决容量问题,只会再次写入相同的固定分区表。

1.00

上图里,青色边框代表分区,橙色区域代表文件系统真正能够管理的空间。扩容时必须完成两层动作:

  1. 先让根分区的结束位置向后延伸;
  2. 再让文件系统使用分区新增的空间。

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,因此必须检测实际类型,不能仅根据镜像名称判断。

1.00

因此不能直接对 /dev/sdb2 执行 resize.f2fs:该分区起点是 SquashFS,F2FS 超级块位于分区内部的后续偏移。需要跳过 SquashFS 和对齐间隔,从正确偏移建立 Loop 设备,再对映射出的文件系统执行检查和扩容。

OpenWrt 的SD 卡安装文档给出了相同的布局与偏移计算方法,并指出:镜像第一次启动前 Overlay 尚未创建;启动后,才需要处理已经生成的 Overlay 文件系统。

扩容场景判定

下面四种情况,操作路径完全不同。

场景 处理方式
EXT4 根文件系统 扩大分区,再用 e2fsckresize2fs 扩大 EXT4
SquashFS,刷完后从未启动 只扩大根分区;首次启动通常会用剩余空间创建 Overlay
SquashFS,已经启动并生成 Overlay 扩大分区,再按偏移映射 Overlay,识别为 F2FS 或 EXT4 后扩容
Btrfs、XFS、LVM、加密卷或无法识别 停止操作,按对应存储栈使用专用工具,不套用本文命令

场景判断需要回答三个具体问题:

  1. 哪一块才是 TF 卡?
  2. 要扩大的根分区是哪一个?
  3. 根分区里真正需要扩大的文件系统是什么?

操作环境: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

这些工具的分工很明确:

工具 用途
lsblkblkid 识别设备、分区和文件系统
partedsfdisk 查看或修改分区表
e2fsckresize2fs 检查和扩展 EXT2/3/4
fsck.f2fsresize.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,先关闭文件管理器里打开的目录,再用 lsoffuser 找到占用进程。不要在文件系统仍然挂载时做离线检查。

扩展根分区边界

确认根分区编号为 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 -fblkid 明确显示根分区是 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 仍保持原来的小容量。此时要做三件事:

  1. 算出 SquashFS 在分区中占用的长度;
  2. 按 64 KiB 向上对齐,得到 Overlay 的起始偏移;
  3. 从这个偏移创建 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

正常情况下,容量应该已经接近扩大后的根分区可用空间,并且能看到 upperwork 等 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。应先使用 blkidfile -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;

  • 已用 blkidfile -s 验证实际文件系统;

  • 文件系统检查无致命错误后才执行扩容;

  • 扩容后再次执行文件系统检查;

  • 已只读挂载验证容量和目录;

  • 已卸载并解除 Loop 设备;

  • 已执行 sync 并正常关机后再拔卡;

  • 目标设备启动后已验证容量、挂载关系和重启持久化。

总结

TF 卡镜像扩容涉及磁盘、分区、文件系统、挂载点和 OverlayFS 多个层次。容量受限的直接原因通常是镜像中的固定分区边界,而最终可用空间还取决于文件系统是否完成扩展。

处理同类问题时,应依次确认:

空闲空间在哪一层?
需要调整的是哪个边界?
真正的可写文件系统从哪里开始?
它是什么类型?
如何验证扩容结果和数据持久化?

完成这些判断后,即可将同一方法应用于 ImmortalWrt、OpenWrt 以及其他单板机 Linux 镜像的扩容场景。

参考资料