1. 从一个真实的翻车现场说起
手里有一块嵌入式板子,eMMC 标称 8GB,系统跑起来之后df -h一看,根分区只有 3.2GB,剩下几个 G 不知道去哪了。这种场景做嵌入式 Linux 的人基本都遇到过——镜像烧录时分区表写死了,或者出厂镜像本身就没把分区扩满。你可能会想,直接fdisk删了重建不就行了?但根分区正在使用中,删了系统就崩了。
这就是Linux 设备存储分区问题处理的典型场景。它涉及的核心链路是:块设备识别(mmcblk0)→ 分区表解析 → 分区扩容 → 文件系统在线扩展(resize2fs)。每一步都有坑,每一步踩错都可能导致数据丢失或者设备变砖。
这篇文章适合谁看?如果你在做嵌入式 Linux 开发、运维国产化设备、或者手里有一台存储空间“缩水”的 Linux 机器想把它救回来,那接下来的内容基本能覆盖你 90% 的需求。我会从分区表的底层逻辑讲起,把 mmcblk0 这类设备的命名规则、分区表类型的选择、resize2fs 的在线扩容原理,以及实操中那些文档里不会写的坑,全部拆开讲清楚。
先给一个全局认知:Linux 下存储设备的分区处理,本质上是三层的协作——块设备层(内核识别硬件)→ 分区表层(MBR/GPT 描述布局)→ 文件系统层(ext4/xfs 管理数据)。很多人出问题就出在只动了其中一层,另外两层没同步,结果就是分区表改了但内核没重读,或者分区扩了但文件系统没跟着扩。下面按这个逻辑逐层拆解。
2. 块设备与分区表:先把底层逻辑搞清楚
2.1 mmcblk0 到底是什么,为什么不是 sda
Linux 下块设备的命名不是随便起的,它直接反映了设备的总线类型和连接方式。你看到sda、nvme0n1、mmcblk0这些名字,其实是在告诉你这个盘是怎么接到系统上的。
sda:SATA/SCSI/USB 存储设备,走的是 SCSI 子系统nvme0n1:NVMe 固态硬盘,走 PCIe 直连mmcblk0:MMC/SD/eMMC 设备,走 MMC 子系统
mmcblk0这个命名拆开看:mmc是 MultiMediaCard 子系统的缩写,blk表示块设备,0是设备序号。如果是第二块 eMMC,就是mmcblk1。而这块设备上的分区,命名规则是mmcblk0p1、mmcblk0p2、mmcblk0p3……注意那个p,它是 partition 的分隔符。
这里有个容易搞混的点:整盘设备是mmcblk0,分区是mmcblk0p1。你在fdisk里操作的是mmcblk0,但挂载和扩容针对的是mmcblk0p1这种分区设备。我见过有人对着mmcblk0直接跑resize2fs,报错说“Bad magic number in super-block”,就是因为整盘上没有文件系统,文件系统在分区里。
提示:用
lsblk可以一眼看清设备树结构,比fdisk -l更直观。lsblk -f还能同时显示文件系统类型和 UUID。
2.2 分区表类型:MBR 和 GPT 的选择逻辑
分区表是存放在磁盘最前面的一小块区域,用来描述“这块盘被分成了几个区、每个区从哪个扇区到哪个扇区”。Linux 下主流就两种:MBR(DOS)和GPT。
| 对比项 | MBR | GPT |
|---|---|---|
| 最大支持磁盘容量 | 2TB | 约 9.4ZB |
| 最大分区数 | 4 个主分区(或 3 主+1 扩展) | 128 个(Windows 限制,Linux 更多) |
| 分区表备份 | 无 | 有,头部和尾部各一份 |
| 兼容性 | 极好,老设备通吃 | 需要 UEFI 或较新内核 |
| 嵌入式常见度 | 非常高 | 逐渐增多 |
嵌入式设备里 MBR 依然占大头,原因很简单:BootROM 和 U-Boot 的解析代码简单。很多 SoC 的启动流程里,ROM Code 只认 MBR 那 512 字节的分区表,你换成 GPT 它直接不启动。所以你在 mmcblk0 上看到 MBR 分区表,不要觉得“落后”,这是启动链路的硬约束。
但 MBR 有个致命限制:主分区最多 4 个。如果你需要更多分区,就得用扩展分区+逻辑分区的方案。扩展分区占一个主分区槽位,里面可以再切多个逻辑分区。mmcblk0p1到mmcblk0p4是主分区,mmcblk0p5开始就是逻辑分区了。这个编号规则要记住,后面扩容时如果搞错了分区号,操作就会落到错误的分区上。
2.3 分区表的三份“账本”必须对齐
这是很多人踩坑的根源。分区信息其实存在三个地方,它们必须保持一致:
- 磁盘上的分区表:实际写在 mmcblk0 开头扇区里的数据
- 内核的分区视图:内核启动时读取分区表后,在内存里维护的设备列表(/proc/partitions)
- 文件系统的超级块:每个分区内部记录的文件系统大小
你改了磁盘分区表,内核不会自动知道,需要partprobe或partx通知它重读。你扩了分区,文件系统超级块里记录的大小还是旧的,需要resize2fs去更新。三层全部对齐,扩容才算真正完成。只做其中一步,就会出现“分区显示变大了但 df 还是老容量”或者“df 变了但重启后又回去了”这类诡异现象。
3. 分区扩容实操:从识别到生效的完整链路
3.1 操作前的信息采集与风险评估
动手之前,先把现状摸清楚。这一步花五分钟,能省掉后面五小时的救砖时间。
# 查看块设备树和挂载点 lsblk -f # 查看分区表详情 fdisk -l /dev/mmcblk0 # 查看当前文件系统使用情况 df -hT # 查看内核识别的分区 cat /proc/partitions重点看几个信息:根分区是哪个设备(比如/dev/mmcblk0p2)、文件系统类型(ext4 还是 xfs)、分区起始扇区号、以及分区后面是否还有未分配空间。
注意:如果根分区后面紧跟着另一个分区,那你就不能直接扩根分区,得先处理后面那个分区。这种情况在嵌入式镜像里很常见——
p1是 boot,p2是 rootfs,p3是 data,三个区把盘占满了。要扩p2,就得先删p3、扩p2、再重建p3,顺序错了数据就没了。
风险评估的核心问题就一个:你要动的分区里有没有不能丢的数据?如果是量产设备,建议先dd备份分区表:
dd if=/dev/mmcblk0 of=/tmp/mmcblk0_mbr_backup.img bs=512 count=1这 512 字节就是 MBR 分区表,出问题了直接写回去就能恢复分区布局。GPT 的话要备份 34 个扇区(count=34)。
3.2 用 fdisk 调整分区表
假设场景是:/dev/mmcblk0有 8GB,p1是 boot(64MB),p2是 rootfs(3GB),后面有约 4.9GB 未分配。目标是把p2扩到占满剩余空间。
fdisk /dev/mmcblk0进入交互界面后,操作序列如下:
- 输入
p打印当前分区表,记下p2的 Start 扇区号(比如 133120) - 输入
d,然后输入2,删除分区 2 - 输入
n,选择p(主分区),分区号输入2 - First sector 输入刚才记下的起始扇区号(133120),必须完全一致
- Last sector 直接回车,使用默认值(即磁盘末尾)
- 提示是否移除 ext4 签名时,选
N(不移除) - 输入
p再次确认分区表,检查p2的 Start 没变、End 变大了 - 输入
w写入并退出
这里最关键的是第 4 步的起始扇区必须和原来一模一样。因为文件系统的超级块就在分区起始位置,起始扇区变了,文件系统就找不到了。删除分区再重建,本质上只是改了分区表里的结束扇区号,分区内的数据一个字节都没动。
提示:
fdisk删除分区时只是抹掉了分区表条目,不会擦除分区内的数据。所以“删了再建”这个操作在起始扇区不变的前提下是安全的。但如果你手抖把起始扇区改了,那就真的找不回来了。
3.3 通知内核重读分区表
写完分区表后,fdisk可能会提示“The kernel still uses the old table”。这时候需要手动通知内核:
partprobe /dev/mmcblk0或者:
partx -u /dev/mmcblk0如果这两个命令都报“device is busy”(根分区正在使用中,无法重读),那就需要重启。这是根分区扩容绕不过去的一步——内核没法在根分区挂载状态下重新扫描它的分区表。重启后lsblk应该能看到p2的 SIZE 变大了。
有些发行版支持partx -u对非根分区热更新,但根分区基本都得重启。我试过用blockdev --rereadpt,对根分区同样无效。所以实操流程就是:改分区表 → 重启 → 扩文件系统。
3.4 resize2fs 在线扩容文件系统
重启之后,分区变大了,但df -h看根分区还是 3GB。因为 ext4 文件系统的超级块里记录的大小没变。这时候resize2fs上场:
resize2fs /dev/mmcblk0p2不带大小参数时,resize2fs会自动把文件系统扩展到分区设备的当前大小。执行过程会输出一系列 block 数变化,几秒到几十秒不等,取决于分区大小。
执行完再df -h,容量应该就对了。
如果是 xfs 文件系统,命令不一样:
xfs_growfs /xfs 只能扩不能缩,而且必须挂载状态下操作,参数是挂载点而不是设备名。这一点和 ext4 的resize2fs区别很大,别搞混了。
3.5 参数计算:resize2fs 到底改了什么
理解resize2fs的原理,能帮你在出问题时快速定位。ext4 文件系统的布局大致是:
- 超级块(Superblock):记录文件系统总块数、块大小、inode 数量等
- 块组描述符(Group Descriptors):每个块组的元信息
- inode 表:文件元数据
- 数据块:实际文件内容
resize2fs做的事就是:读取分区设备的当前大小 → 计算能容纳多少块 → 更新超级块里的总块数 → 为新增加的块组初始化描述符和位图。
块大小的计算:假设块大小 4KB,分区从 3GB 扩到 8GB,新增 5GB 空间,对应 5×1024×1024÷4 = 1310720 个新块。resize2fs会把这些块纳入管理,并更新空闲块计数。
注意:
resize2fs扩容前会做一次文件系统检查(fsck)的预检,如果文件系统有错误会拒绝执行。这时候先跑e2fsck -f /dev/mmcblk0p2修复,再 resize。根分区的话,e2fsck需要在单用户模式或者从其他介质启动后执行。
3.6 完整操作流程速查
把上面的步骤串起来,一个标准的根分区扩容流程是:
# 1. 备份分区表 dd if=/dev/mmcblk0 of=/tmp/mbr_backup.img bs=512 count=1 # 2. 查看现状 lsblk -f fdisk -l /dev/mmcblk0 # 3. 调整分区表(删p2、重建p2、起始扇区不变、结束扇区到盘尾) fdisk /dev/mmcblk0 # 4. 重启(根分区必须重启才能重读分区表) reboot # 5. 重启后确认分区已扩大 lsblk # 6. 扩展文件系统 resize2fs /dev/mmcblk0p2 # 7. 验证 df -hT整个流程里,第 3 步的起始扇区核对和第 4 步的重启是两个关键节点。其他步骤出错了还能补救,这两步错了基本就是数据丢失。
4. 常见故障与排查技巧实录
4.1 分区表改了但重启后没生效
这种情况通常是分区表写入失败,或者写到了错误的设备上。排查思路:
fdisk -l /dev/mmcblk0确认分区表确实变了- 检查是不是有多个块设备,操作错了盘(比如系统实际从
mmcblk1启动,你改了mmcblk0) - 某些 eMMC 有 boot0/boot1 分区,
mmcblk0boot0和mmcblk0是不同设备,别搞混
还有一种可能是 U-Boot 在启动时覆盖了分区表。有些嵌入式方案里,U-Boot 会根据环境变量重新写分区表,你手动改的会被覆盖。这种情况要改 U-Boot 的启动脚本或者环境变量。
4.2 resize2fs 报 “Filesystem does not support online resizing”
ext4 支持在线扩容,但前提是内核编译时开启了CONFIG_EXT4_FS的 resize 支持。嵌入式内核为了精简体积,有时会裁掉这个功能。确认方法:
grep CONFIG_EXT4_FS_RESIZE /boot/config-$(uname -r)如果没有这个配置项,或者值为n,那就只能离线扩容——从其他介质启动,或者把分区卸载后操作。根分区的话,用 Live USB 或者网络启动进入救援模式。
4.3 扩容后 df 显示容量没变
resize2fs执行成功但df没变化,通常是文件系统挂载时缓存了旧的超级块信息。尝试:
mount -o remount /或者直接重启。如果重启后还是老容量,检查resize2fs的输出里有没有报错,以及dumpe2fs /dev/mmcblk0p2 | grep "Block count"看超级块里的块数是否真的变了。
4.4 分区号错乱导致挂载失败
MBR 的逻辑分区编号从 5 开始,如果你删除了扩展分区里的某个逻辑分区,后面的分区号可能会变。比如原来p5、p6、p7,删了p5之后,原来的p6可能变成p5。这会导致/etc/fstab里的挂载项失效。
解决办法:用 UUID 或 LABEL 挂载,不要用设备名。blkid可以查看分区的 UUID,/etc/fstab里写成UUID=xxxx-xxxx / ext4 defaults 0 1的形式,这样分区号变了也不影响挂载。
4.5 常见问题速查表
| 现象 | 可能原因 | 排查命令 | 解决方式 |
|---|---|---|---|
| 分区表改了不生效 | 内核未重读 | cat /proc/partitions | partprobe或重启 |
| resize2fs 报错 | 文件系统有错误 | e2fsck -f /dev/mmcblk0pX | 先修复再 resize |
| df 容量不变 | 超级块未更新 | dumpe2fs -h /dev/mmcblk0pX | 重新 resize 或 remount |
| 挂载失败 | 分区号变了 | blkid | 改用 UUID 挂载 |
| 设备不识别 | 驱动未加载 | `dmesg | grep mmc` |
| 扩容后数据丢失 | 起始扇区改错 | 无法直接排查 | 从备份恢复分区表 |
4.6 几个保命技巧
第一,操作前一定备份分区表。512 字节的 MBR 备份成本几乎为零,但出问题时能救命。GPT 备份 34 个扇区,也就 17KB。
第二,起始扇区用笔抄下来。不要依赖记忆,fdisk里p命令打印出来后,把 Start 列的数字记在纸上或者另一个终端里。删分区重建时对照着输入。
第三,根分区扩容必须重启。不要试图用partx热更新根分区,内核不会允许。重启是最稳妥的路径。
第四,resize2fs 之前先 e2fsck。虽然 resize2fs 自己会做预检,但显式跑一次e2fsck -f能提前发现文件系统问题,避免 resize 到一半失败。
第五,嵌入式设备注意 U-Boot 的分区表覆盖。如果重启后分区表恢复原样,检查 U-Boot 环境变量里有没有partition相关的脚本。
4.7 一个真实的踩坑记录
之前处理过一块全志方案的板子,eMMC 16GB,出厂镜像只用了 4GB。我按标准流程fdisk删了p2重建,起始扇区核对无误,w写入,重启。结果重启后系统直接起不来了,串口打印停在 “Waiting for root device”。
排查了半天发现,这块板子的 U-Boot 里写死了 root 分区的结束扇区号,用来计算 rootfs 的校验和。我扩了分区,校验和对不上,U-Boot 拒绝启动。解决办法是进 U-Boot 命令行,更新环境变量里的分区参数,或者重新烧录一个不校验的 U-Boot。
这个坑的教训是:嵌入式设备的分区表可能被多个组件引用——内核、U-Boot、甚至 BootROM。改之前先确认启动链路上有没有硬编码的分区信息。通用 Linux 发行版没这个问题,但嵌入式方案里很常见。
4.8 国产化设备上的注意事项
国产 Linux 设备(比如基于飞腾、龙芯、兆芯的平台)在存储分区处理上,底层逻辑和通用 Linux 一致,但有几个差异点:
- 分区工具版本可能较老:部分国产发行版自带的
fdisk不支持 GPT,或者resize2fs版本偏低。建议先fdisk --version和resize2fs -V确认版本。 - 文件系统可能是 ext4 的定制版:某些国产系统对 ext4 做了修改,
resize2fs需要用配套版本,用通用版的可能报错。 - 启动介质可能是 UFS 而非 eMMC:UFS 设备在 Linux 下命名可能是
sd开头(走 SCSI 子系统),不是mmcblk。用lsblk确认实际设备名。
这些差异不影响操作流程,但工具选型和设备名确认上要多留个心眼。
5. 文件系统选型与长期维护建议
5.1 ext4 和 xfs 在扩容上的差异
嵌入式设备上 ext4 是绝对主流,但 xfs 在部分国产服务器和工作站上也有使用。两者在扩容操作上的核心差异:
| 操作 | ext4 | xfs |
|---|---|---|
| 扩容命令 | resize2fs | xfs_growfs |
| 参数 | 设备名(如 /dev/mmcblk0p2) | 挂载点(如 /) |
| 在线扩容 | 支持 | 支持 |
| 缩小 | 支持(离线) | 不支持 |
| 前置检查 | e2fsck | xfs_repair(离线) |
xfs 不支持缩小这一点很关键。如果你不确定未来会不会需要缩分区,选 ext4 更灵活。但 xfs 在大文件和高并发场景下性能更好,服务器场景可以考虑。
5.2 分区布局的长期规划建议
与其每次存储不够了再扩容,不如一开始就把分区布局规划好。几个原则:
- 根分区不要占满整盘:留 10%-20% 的未分配空间,后续扩容不用动其他分区
- 数据分区独立:把
/var、/home、/data单独分区,根分区扩容不影响数据 - boot 分区给足:内核更新频繁的话,
/boot给 512MB 以上,避免内核装不下 - 用 LVM 做弹性管理:如果设备支持,LVM 可以在线扩缩逻辑卷,比直接操作分区灵活得多
LVM 在嵌入式设备上用得少,因为增加了一层抽象,启动链路变复杂。但在国产化服务器和工作站上,LVM 是标配。lvextend+resize2fs的组合比fdisk删分区重建安全得多,因为不涉及分区表改动。
5.3 监控与预警
存储分区问题最好的处理方式是提前发现。几个实用的监控点:
# 分区使用率超过 80% 告警 df -h | awk 'NR>1 && int($5)>80 {print $0}' # inode 使用率检查(小文件多时容易耗尽) df -i | awk 'NR>1 && int($5)>80 {print $0}' # 查看块设备错误 dmesg | grep -i "mmc.*error\|I/O error"嵌入式设备上,eMMC 的寿命有限,频繁写入会导致坏块。如果dmesg里出现大量 MMC 错误,说明 eMMC 可能快挂了,这时候扩容也没意义,得换硬件。
5.4 关于 resize2fs 的一个冷知识
resize2fs其实支持指定目标大小,比如resize2fs /dev/mmcblk0p2 6G。这个用法在分区比文件系统大、但你不想全用的时候有用。不过大多数场景下不带参数直接扩到分区满就行。
另外,resize2fs在扩容时会保留原有的 inode 数量。如果你的场景是小文件极多,扩容后 inode 可能不够用。这时候需要在 resize 时用-N参数调整 inode 数量,但操作更复杂,建议直接重建文件系统。
5.5 最后分享一个实用脚本
把根分区扩容的检查逻辑写成一个脚本,每次拿到新设备跑一遍,能快速判断是否需要扩容:
#!/bin/bash ROOT_DEV=$(findmnt -n -o SOURCE /) ROOT_DISK=$(lsblk -n -o PKNAME "$ROOT_DEV") DISK_SIZE=$(lsblk -n -o SIZE "/dev/$ROOT_DISK" | head -1) PART_SIZE=$(lsblk -n -o SIZE "$ROOT_DEV") FS_SIZE=$(df -h / | awk 'NR==2 {print $2}') echo "磁盘总容量: $DISK_SIZE" echo "根分区容量: $PART_SIZE" echo "文件系统容量: $FS_SIZE" # 检查分区后是否有未分配空间 LAST_PART_END=$(fdisk -l "/dev/$ROOT_DISK" | grep "^/dev" | tail -1 | awk '{print $3}') DISK_SECTORS=$(fdisk -l "/dev/$ROOT_DISK" | grep "sectors" | awk '{print $7}') if [ "$LAST_PART_END" -lt "$DISK_SECTORS" ]; then echo "警告: 分区后有未分配空间,可执行扩容" else echo "分区已占满磁盘,无需扩容" fi这个脚本在fdisk -l输出格式不同的系统上可能需要微调,但思路是通用的:对比最后一个分区的结束扇区和磁盘总扇区数,有差值就说明有未分配空间。
存储分区处理这件事,说到底就是三层对齐——分区表、内核视图、文件系统超级块,三者一致了,容量就对了。操作本身不复杂,复杂的是嵌入式设备上各种非标准的启动约束和硬件差异。把底层逻辑吃透,遇到再奇怪的现象也能顺着链路排查下去。