本文记录一次真实的 Ubuntu 扩容过程。
我的电脑是 Windows 11 + Ubuntu 双系统,Ubuntu 最初只分配了约 108GB。随着 Zephyr SDK、STM32 工具链、Conda、VS Code、机器人开发环境等不断增加,Ubuntu 根分区最终只剩不到 1GB。

考虑到直接移动根分区风险较高,我最后采用了更稳妥的方案:

从 Windows 压缩出 300GB → 新建 ext4 分区 → 将 /home 迁移到新分区。

最终效果:

/dev/nvme0n1p5  →  /       106GB,剩余约 59GB
/dev/nvme0n1p6  →  /home   295GB,剩余约 226GB

这篇文章既记录具体操作,也重点总结其中几个容易踩坑的地方,适合有相同需求的同学或公司同事参考。


一、我的磁盘为什么会不够用

一开始执行:

df -h

得到:

文件系统          大小  已用  可用 已用% 挂载点
/dev/nvme0n1p5   106G  100G  约1G  100% /

而 Windows 分区有 845GB,实际只使用了约 127GB。

Ubuntu 里的主要空间占用包括:

/home       约 60GB
/opt        约 14GB
/var        约 7GB
/usr        约 10GB

进一步查看 /home

du -hd1 ~ 2>/dev/null | sort -h

其中比较大的目录包括:

11G  ~/zephyr-sdk-1.0.1
9G   ~/zephyrproject
5G   ~/.cache
5G   ~/.zephyr_ide
4.8G ~/miniconda3
2.2G ~/STM32Cube
...

这类开发环境大部分都位于 /home。因此,与其直接移动整个 Ubuntu 根分区,不如给 /home 单独分配一个大分区。


二、最终选择:不直接扩根分区,而是独立 /home

我的原始分区布局大致如下:

[EFI][MSR][Windows p3][Ubuntu p5][Windows Recovery p4]

其中:

/dev/nvme0n1p1  EFI
/dev/nvme0n1p2  Microsoft Reserved
/dev/nvme0n1p3  Windows NTFS
/dev/nvme0n1p5  Ubuntu ext4,挂载为 /
/dev/nvme0n1p4  Windows Recovery

理论上,可以从 Windows 尾部压缩空间,再将 Ubuntu 根分区向左扩展。

但由于未分配空间位于 Ubuntu 分区左侧,直接扩根分区需要移动 ext4 分区的起始位置。这意味着要搬移大量数据,操作时间长,断电或异常中断的风险也更高。

所以我选择:

原布局:

[Windows p3][Ubuntu p5 /][Recovery p4]


调整后:

[Windows p3][新 ext4 p6 /home][Ubuntu p5 /][Recovery p4]

这个方案的优点是:

  1. 不移动原来的 Ubuntu 根分区;

  2. 不修改 Ubuntu 分区起始位置;

  3. 不碰 EFI 和 Windows 恢复分区;

  4. 即使迁移失败,旧 /home 数据仍然保留;

  5. 后续重装系统时,也可以考虑继续复用独立 /home


三、操作前先确认真实分区结构

df -h 只能查看已经挂载的文件系统,不能完整展示分区布局。

建议先执行:

lsblk -o NAME,SIZE,FSTYPE,FSAVAIL,MOUNTPOINTS,PARTLABEL

以及:

sudo parted -l

我的原始结果类似:

编号  起始点  结束点  大小    文件系统
1     ...      ...    273MB   fat32
2     ...      ...    16.8MB
3     ...      ...    907GB   ntfs
5     ...      ...    116GB   ext4
4     ...      ...    1035MB  ntfs

这里还出现了:

分区表记录没有按磁盘顺序

这通常只是因为 GPT 分区编号和实际物理顺序不一致。例如 p4 可能物理上位于 p5 后面,并不代表磁盘损坏。


四、先备份重要文件

在进行分区操作前,我先将重要代码和文档备份到 Windows 分区。

例如:

rsync -aH --info=progress2 \
  ~/JforceGlove \
  ~/STM32_Proj \
  ~/SEA_test \
  ~/my_zephyr_apps \
  ~/0602_NewGalbot_test \
  ~/文档 \
  /media/galbot/Windows/Ubuntu_Backup/

不过这里有一个非常重要的注意事项:

1. 备份前必须确认 Windows 分区真的已经挂载

不要只看目录是否存在。

因为:

/media/galbot/Windows

即使 Windows 分区没有挂载,也可能只是根分区中的一个普通空目录。

备份前应执行:

findmnt -T /media/galbot/Windows/Ubuntu_Backup

以及:

df -hT /media/galbot/Windows/Ubuntu_Backup

必须明确看到目标来源是:

/dev/nvme0n1p3

文件系统类型为:

ntfs3

否则数据可能会被复制到 Ubuntu 根分区,反而进一步占满磁盘。

可以增加一道检查:

mountpoint -q /media/galbot/Windows \
  && echo "Windows 分区已挂载,可以备份" \
  || echo "Windows 分区未挂载,请停止操作"

2. NTFS 备份不能完整保留 Linux 元数据

将普通代码、文档、图片备份到 NTFS 没问题,但 NTFS 不一定能完整保留:

  • Linux UID、GID;

  • ACL;

  • 扩展属性;

  • 特殊权限;

  • 部分软链接;

  • Unix socket 和特殊文件。

因此,Windows 分区上的备份更适合保存:

代码
文档
工程文件
普通数据集

而不是作为完整、可直接恢复的 Linux /home 镜像。


五、公司环境下的一个特殊踩坑:飞连 EDLP 占满根分区

在备份过程中,我发现根分区空间突然从约 8GB 降到了不足 500MB。

重新统计:

sudo du -xhd1 / 2>/dev/null | sort -h

发现:

/opt 由约 14GB 增长到了 22GB

进一步定位:

sudo du -xhd3 /opt/apps/com.volcengine.feilian/files 2>/dev/null \
  | sort -h | tail -50

主要空间集中在:

6.4G /opt/apps/com.volcengine.feilian/files/data/edlp/back_paks
1.7G /opt/apps/com.volcengine.feilian/files/data/edlp/back_files

也就是说,在公司终端安全环境中,大量文件复制可能会触发 DLP 文件审计、存证或备份机制。

这种情况不建议直接手动删除安全客户端目录,因为可能:

  • 触发重新生成;

  • 造成客户端异常;

  • 影响企业网络准入;

  • 产生审计告警。

应优先联系公司 IT 或安全管理员处理。

此外,我当时还清理了 ~/.cache。由于部分程序仍持有已经删除的文件,出现了:

du 统计较小
df 仍然显示空间被占用

重启后,相关进程关闭,空间从约 500MB 恢复到了约 12GB。

这属于 Linux 中典型的:

文件已经删除,但仍被进程打开

可以通过下面的命令检查:

sudo lsof +L1 2>/dev/null

六、在 Windows 中压缩 C 盘

1. 检查 BitLocker

管理员 CMD 中执行:

manage-bde -status C:

我的结果是:

BitLocker 版本:    无
转换状态:          完全解密
已加密百分比:      0.0%
保护状态:          保护关闭
密钥保护器:        找不到

说明没有启用 BitLocker,因此不需要备份恢复密钥,也不需要暂停保护。

如果显示 BitLocker 已启用,应先保存恢复密钥并暂停保护。

不要把下面两个操作混淆:

manage-bde -protectors -disable C:

这是暂停保护。

而:

manage-bde -off C:

会开始解密整个 C 盘,不是这次操作需要的。

2. 关闭休眠和快速启动

管理员 CMD 中执行:

powercfg /h off

这样可以避免 Windows 休眠或快速启动导致 NTFS 分区处于未完全关闭状态。

3. 压缩 Windows 分区

按:

Win + R

输入:

diskmgmt.msc

找到 C 盘,右键选择:

压缩卷

我准备划出 300GB,因此输入:

307200 MB

压缩完成后得到:

300GB 未分配空间

这里不要:

  • 新建简单卷;

  • 格式化为 NTFS;

  • 分配盘符。

保持“未分配”状态即可。

完成后彻底关机:

shutdown /s /t 0

七、在 Ubuntu 中创建新的 ext4 分区

通常更推荐使用 Ubuntu Live USB 操作。

但我的公司不允许插入私人 U 盘,因此采用了:

在当前 Ubuntu 中,只对未分配空间新建分区,不移动、不调整现有根分区。

因为这次操作只是:

未分配空间 → 新建分区

不会修改已经挂载的 /dev/nvme0n1p5

打开 GParted:

sudo gparted

确认磁盘为:

/dev/nvme0n1

当时看到的布局为:

/dev/nvme0n1p3  Windows
unallocated     300GiB
/dev/nvme0n1p5  Ubuntu /
/dev/nvme0n1p4  Windows Recovery

在 300GiB 未分配空间上右键选择:

New

设置:

创建为:主分区
文件系统:ext4
卷标:ubuntu_home
对齐:MiB

然后应用操作。

完成后执行:

lsblk -f

得到:

nvme0n1p5 ext4                  UUID=...  挂载到 /
nvme0n1p6 ext4 ubuntu_home      UUID=...  尚未挂载

这里出现两个 ext4 完全正常:

p5:原来的 Ubuntu 根分区
p6:新建的 /home 分区

八、进入 TTY,尽量减少 /home 的读写

在桌面环境中,很多程序会持续写入 /home,例如:

Chrome
VS Code
微信
飞书
Conda
GNOME 配置
PipeWire
各种后台服务

因此不建议直接在桌面环境中复制。

按下:

Ctrl + Alt + F3

进入 TTY。

登录后,建议先切换到不位于 /home 的目录:

cd /

停止 GNOME 图形登录管理器:

sudo systemctl stop gdm3

执行后屏幕可能会黑掉,只剩左上角光标闪烁。这通常只是图形界面关闭后的显示切换,不代表系统损坏。

重新按:

Ctrl + Alt + F3

即可回到 TTY。

检查状态:

systemctl status gdm3 --no-pager

正常应看到:

inactive (dead)

还可以检查 /home 是否仍有进程使用:

sudo lsof +D /home 2>/dev/null | head -30

少量当前 shell 或系统服务记录不一定有问题,但应尽量关闭:

Chrome
Code
微信
飞书
Python 任务
编译任务
下载程序

九、将新分区临时挂载到 /mnt/newhome

创建临时挂载目录:

sudo mkdir -p /mnt/newhome

挂载新分区:

sudo mount /dev/nvme0n1p6 /mnt/newhome

检查:

df -h /mnt/newhome

我的结果类似:

文件系统          大小  已用  可用  挂载点
/dev/nvme0n1p6   295G   28K  280G  /mnt/newhome

查看内容:

ls -la /mnt/newhome

新建的 ext4 分区通常只有:

.
..
lost+found

这里的 /mnt/newhome 只是临时挂载目录,最终分区会挂载到 /home


十、使用 rsync 迁移 /home

执行:

sudo rsync -aAXH \
  --numeric-ids \
  --info=progress2 \
  /home/ \
  /mnt/newhome/

注意源目录和目标目录最后的 /

/home/
/mnt/newhome/

这样复制后的结构是:

/mnt/newhome/galbot

而不是:

/mnt/newhome/home/galbot

参数含义:

-a  归档模式,保留大部分权限、时间、软链接等
-A  保留 ACL
-X  保留扩展属性
-H  保留硬链接
--numeric-ids  按数值保存 UID/GID

复制完成后检查:

sudo du -sh /home
sudo du -sh /mnt/newhome

我的结果两边都约为:

54G

再执行:

sync

确保缓存数据写入磁盘。

如果 /home 特别大,可以先在桌面环境中进行一次预复制,关闭图形界面后再执行第二次 rsync,只同步变化部分,以缩短维护时间。


十一、配置 /etc/fstab

查看新分区 UUID:

sudo blkid /dev/nvme0n1p6

我的结果为:

/dev/nvme0n1p6:
UUID="6fffcf0a-7941-4cab-8532-a1f21ca5c654"
TYPE="ext4"

先备份:

sudo cp /etc/fstab /etc/fstab.backup-home-migration

编辑:

sudo nano /etc/fstab

在末尾加入:

UUID=6fffcf0a-7941-4cab-8532-a1f21ca5c654 /home ext4 defaults,noatime 0 2

这里的 UUID 必须替换成自己电脑实际查询到的值。

如果 TTY 中不方便复制,可以直接追加:

echo 'UUID=6fffcf0a-7941-4cab-8532-a1f21ca5c654 /home ext4 defaults,noatime 0 2' \
  | sudo tee -a /etc/fstab

写完后检查:

tail -5 /etc/fstab

重新加载 systemd 配置:

sudo systemctl daemon-reload

测试 fstab:

sudo mount -a

没有报错后检查:

findmnt /home

应显示:

/home  /dev/nvme0n1p6  ext4

再执行:

df -h /home

应显示新的 300GB 分区。


十二、重启并验证新 /home

执行:

sudo reboot

重新进入桌面后先检查:

findmnt /home

确认:

/home → /dev/nvme0n1p6

再查看:

df -h

迁移后,我当时看到:

/dev/nvme0n1p5  106G  100G  5.2G   96%  /
/dev/nvme0n1p6  295G   54G  226G   20%  /home

这里根分区仍然很满,是因为:

p5 中原来的 /home 数据还存在,只是被新挂载的 p6 遮住了。

随后测试重要环境:

ls ~/zephyrproject
conda env list
code --version
ls ~/STM32_Proj

还可以测试写入:

touch ~/home_partition_test
ls -l ~/home_partition_test
rm ~/home_partition_test

确认所有项目、配置和权限正常后,再清理旧数据。


十三、为什么旧 /home 还占空间

迁移前,p5 内部结构为:

p5
└── /
    ├── etc
    ├── usr
    ├── opt
    ├── var
    └── home
        └── galbot

迁移后,p6 被挂载到 /home

当前目录树:

/
├── etc        来自 p5
├── usr        来自 p5
├── opt        来自 p5
├── var        来自 p5
└── home       来自 p6
    └── galbot

p5 里面原来的 /home/galbot 并没有消失,只是访问 /home 时被新的 p6 覆盖了。

这和把一块新硬盘挂载到一个已有目录类似:

挂载前:目录里有旧数据
挂载后:看到的是新分区内容

卸载后,旧数据仍然存在。


十四、重新挂载 p5,查看被遮住的旧 /home

创建临时挂载目录:

sudo mkdir -p /mnt/oldroot

将 p5 再挂载一次:

sudo mount /dev/nvme0n1p5 /mnt/oldroot

这里需要说明:

/dev/nvme0n1p5

仍然是当前系统的根分区,只是现在通过 /mnt/oldroot 又增加了一个访问入口。

检查:

findmnt -T /mnt/oldroot/home

我的结果为:

TARGET        SOURCE
/mnt/oldroot /dev/nvme0n1p5

说明:

/mnt/oldroot/home

确实位于 p5,而不是新 p6。

检查旧数据大小:

sudo du -sh /mnt/oldroot/home

结果:

54G /mnt/oldroot/home

十五、安全清理旧 /home

这里是整次操作中风险最高的一步。

在删除前,至少确认以下三项:

findmnt /home

必须显示:

/home → /dev/nvme0n1p6

然后:

findmnt -T /mnt/oldroot/home

必须显示来源为:

/dev/nvme0n1p5

最后确认大小:

sudo du -sh /mnt/oldroot/home

确认是旧副本。

更推荐的删除方式

不要直接删除整个 home 目录,建议只删除旧用户目录:

sudo rm -rf --one-file-system /mnt/oldroot/home/galbot

如果有多个用户,应分别确认后删除:

/mnt/oldroot/home/user1
/mnt/oldroot/home/user2

保留空的:

/mnt/oldroot/home

作为 /home 挂载点更规范。

我实际遇到的现象

我当时执行的是:

sudo rm -rf --one-file-system /mnt/oldroot/home

最后报错:

rm: 无法删除 '/mnt/oldroot/home': 设备或资源忙

一开始我以为整个删除失败了。

但检查:

sudo du -sh /mnt/oldroot/home

结果只剩:

4.0K

再执行:

df -h

发现根分区从:

100G 已用

下降到了:

46G 已用

也就是说,rm -rf 已经递归删除了旧 /home 中约 54GB 的内容,只是在最后尝试删除最外层 home 目录时,因为该目录对应当前 /home 的活动挂载位置而返回了 Device or resource busy

最终结果反而正好是:

旧用户数据被删除
空的 /home 挂载目录被保留

不过不建议依赖这种行为。更稳妥的方式仍然是只删除:

/mnt/oldroot/home/用户名

十六、清理临时挂载目录

确认旧 /home 已经为空:

sudo ls -la /mnt/oldroot/home
sudo du -sh /mnt/oldroot/home

然后:

cd ~
sudo umount /mnt/oldroot
sudo rmdir /mnt/oldroot

如果之前还保留了 /mnt/newhome

sudo rmdir /mnt/newhome

前提是它已经没有挂载任何分区:

findmnt /mnt/newhome

没有输出后才能删除。


十七、最终结果

最终执行:

df -h

得到:

文件系统          大小  已用  可用 已用% 挂载点
/dev/nvme0n1p5   106G   46G   59G   44% /
/dev/nvme0n1p6   295G   54G  226G   20% /home

现在的磁盘结构相当于:

p5:Ubuntu 系统分区
├── /
├── /usr
├── /opt
├── /var
└── /home    空挂载点

p6:用户数据分区
└── /home
    └── galbot
        ├── zephyrproject
        ├── miniconda3
        ├── STM32Cube
        ├── 项目代码
        └── 用户配置

十八、本次扩容最重要的经验

1. 分区操作前必须确认物理布局

至少执行:

lsblk -f
sudo parted -l

不要只根据 df -h 判断磁盘结构。

2. Windows 系统分区尽量在 Windows 中压缩

不要直接在 Linux 中随意缩小 Windows C 盘。

正确方式是:

Windows 磁盘管理 → 压缩卷

3. 未分配空间可以新建分区,但不要随意移动已挂载根分区

本次方案的低风险点在于:

只新建 p6
不移动 p5
不修改 p5 起始位置

4. 迁移 /home 要保留 Linux 权限和属性

推荐:

rsync -aAXH --numeric-ids

普通的:

cp -r

不适合完整迁移 Linux 用户目录。

5. /etc/fstab 写完后不要直接重启

应先执行:

sudo systemctl daemon-reload
sudo mount -a

再用:

findmnt /home

确认挂载来源。

6. 备份目标目录存在,不代表对应分区已经挂载

一定要检查:

findmnt -T 目标路径
df -hT 目标路径

否则可能将备份写回根分区。

7. dfdu 差异很大时,检查已删除但仍被占用的文件

执行:

sudo lsof +L1

程序仍然打开着已删除文件时:

du 看不到
df 仍然统计占用

关闭相关程序或重启后空间才会释放。

8. rm -rf 报错不代表之前的文件都没有删除

rm -rf 目录 的过程是:

先删除目录内部内容
最后删除目录本身

如果最后目录删除失败,前面的文件可能已经全部删掉。

所以任何删除操作后都要重新检查:

du -sh
df -h
ls -la

9. 删除旧数据前必须确认两个路径分别属于哪个分区

至少执行:

findmnt /home
findmnt -T /mnt/oldroot/home

路径相似并不代表它们属于同一个文件系统。

10. 对公司电脑,还要考虑终端安全软件

大量复制可能触发:

DLP 审计
文件存证
杀毒扫描
缓存和日志增长

公司终端出现异常空间占用时,除了检查 /home/var,还应关注:

/opt
企业安全客户端目录

不要未经授权删除公司安全软件的数据。


十九、总结

这次扩容表面上只是“给 Ubuntu 增加了 300GB”,但整个过程让我真正理解了几个 Linux 文件系统中的核心概念:

  • 分区决定磁盘空间如何划分;

  • ext4、NTFS 是建立在分区上的文件系统;

  • 挂载是把一个文件系统接入 Linux 目录树;

  • /home 只是一个路径,它可以位于根分区,也可以来自另一块独立分区;

  • 新分区挂载到 /home 后,旧目录不会自动删除,只是被遮住;

  • 同一个文件系统可以通过不同挂载点访问;

  • 删除前最重要的不是看路径名字,而是确认路径对应的真实块设备。

最终我没有通过移动根分区来扩容,而是将 /home 迁移到了独立分区。这种方式风险相对更低,也更符合我的实际使用特点:系统文件变化不大,但各种 SDK、工具链、项目和用户缓存会持续增长。

对于和我一样使用 Windows + Ubuntu 双系统,并且 Ubuntu 根分区空间不足的人来说,独立 /home 是一个值得考虑的方案。

不过必须强调:

分区和 rm -rf 都属于高风险操作。
文中的 /dev/nvme0n1p5p6 和 UUID 只适用于我的电脑,不能直接照抄。操作前一定要通过 lsblkfindmntblkid 确认自己的实际设备。


参考命令速查

# 查看空间
df -h
sudo du -xhd1 / 2>/dev/null | sort -h

# 查看分区
lsblk -f
sudo parted -l

# 查看路径属于哪个文件系统
findmnt -T /some/path
df -hT /some/path

# 挂载新 home
sudo mount /dev/nvme0n1p6 /mnt/newhome

# 复制 home
sudo rsync -aAXH --numeric-ids --info=progress2 \
  /home/ /mnt/newhome/

# 查询 UUID
sudo blkid /dev/nvme0n1p6

# 测试 fstab
sudo systemctl daemon-reload
sudo mount -a

# 确认 /home
findmnt /home
df -h /home

# 查看旧 home
sudo mount /dev/nvme0n1p5 /mnt/oldroot
sudo du -sh /mnt/oldroot/home

# 清理旧用户目录
sudo rm -rf --one-file-system /mnt/oldroot/home/用户名
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐