Linux 环境下硬盘数据的迁移

之前一直在用的老 SSD 只有 128GB,上边装了 Ubuntu 作为宿主 OS,另外常备一个 Windows7 的虚拟机,时常面临空间不够的问题,一般是用 Vmware 中 Hard Disk 的Compact Disk功能来挤出一些空间来,但是这样频繁的硬盘读写对硬盘寿命多少有些影响。
后来换新机时自带的 SSD 是 256GB 的,当时出于方便不想重装环境就用老的 SSD 替换了下来,现在抽出一块时间把数据都转移到新硬盘上,尽量不影响平时的工作。

操作

1. 装硬盘

将新硬盘插到笔记本的硬盘位,如果有第二个硬盘位或光驱位(可以改装成硬盘位)可以直接放旧硬盘,如果像我的 T470P 一样第二硬盘位不支持 SATA 接口的情况,就得上淘宝买个 USB-SATA 适配器了。
如果新硬盘上有重要数据必须备份,比如保存到一块足够大的机械硬盘上,老硬盘最好也备份一下(我因为偷懒略过了这一步)。

2. 查询硬盘的分区号

1
fdisk -l

老硬盘有一个 boot 分区和挂载到/的主分区,为了保持同样,需要对新硬盘进行格式化和分区,分成一个 boot 分区和一个主分区,参考这篇经验

3. 拷贝分区

假设新硬盘的主分区是/dev/sda2,旧硬盘的主分区是/dev/sdb2,那么使用下面命令拷贝数据:

1
sudo dd if=/dev/sdb2 of=/dev/sda2

硬盘内容是非常大的,而 dd 命令不会展示中间过程,非常不方便。
其实 dd 命令默认不会展示进度,只会在接收到 SIGUSR1 消息后才会打印出自己当前的进度,可以使用 kill 系的命令向 dd 进程发送消息来查看:

1
2
3
4
5
watch -n 5 pkill -USR1 ^dd$
# 每隔5秒执行一次killall命令,对dd进程发送一个自定义信号SIGUSR1(信号可以查看`man 7 signal`),返回值会提示工作的进度。
watch -n 5 killall -USR1 dd
while killall -USR1 dd; do sleep 5; done
while (ps auxww |grep " dd " |grep -v grep |awk '{print $2}' |while read pid; do kill -USR1 $pid; done) ; do sleep 5; done

注意 dd 命令也会拷贝 uuid 过去,uuid 是一个唯一的标识符,因为类似/dev/sda 这样的映射点,在新设备加入的时候,可能会生成新的映射点,比如原来系统里是/dev/sda 现在变成了/dev/sdb 等等,所以一般情况下,在/etc/fstab 里写自己规则的时候,都是用 uuid 而非映射点。
可以不修改新硬盘分区的 uuid,也就省去了修改/etc/fstab 或者/boot/grub/grub.conf 的麻烦。

4. 拷贝 boot 分区

作为实验,我拷贝 boot 分区的时候使用的是 cp 命令,相对 dd 命令来说,cp 不会原封不动地拷贝过来,而是哪里有空间就分到哪里,而且不会将 UUID 一并拷贝过去,需要手动设置。

5. 更新硬盘信息

1
2
3
4
# 操作前先卸载分区
umount /dev/sdb1
e2fsck -f /dev/sdb1
resize2fs /dev/sdb1

e2fsck 可以检查文件系统的完整性,并且可以修复一些错误。执行 e2fsck 或 fsck 之前必须先 umount partition,否则有可能导致文件系统系统损坏。

1
2
3
4
# 检查`/dev/sda1`是否存在问题,如果发现问题就自动修复
e2fsck -a -y /dev/sda1
# 即使文件系统没有错误迹象,仍强制地检查正确性
e2fsck -f /dev/sdb1

resize2fs 可以调整 ext2/ext3/ext4 文件系统的大小(扩展或缩小),可以指定一个 size 属性,如果不指定则认为需要扩展到该分区的大小。

6. 修改 uuid

如果想修改 uuid,比较简单的办法是打开gparted,在分区上右键,生成新的 uuid。
同时还要修改/etc/fstab中的内容,替换掉原来的 uuid
当然也可以通过命令修改 UUID,首先是查看分区的 UUID:

1
2
ls -l /dev/disk/by-uuid
blkid /dev/sda5

uuidgen 会返回一个合法的 uuid,结合 tune2fs 可以新生成一个 uuid 并写入 ext2,3,4 的分区中:
比如新建或改变 sda5 的 uuid (需要 root 权限)

1
uuidgen | xargs tune2fs /dev/sda5 -U

也可以把 fstab 里找到的原 uuid 写回分区:

1
tune2fs -U c1b9d5a2-f162-11cf-9ece-0020afc76f16 /dev/sda5

7. 为 boot 分区添加 boot 标记

最后,可以在 gparted 界面上右键分区设置标记,选中 boot,表明这个分区是有启动点的。

8. 修复 grub

更新 grubupdate-grub2
如果重启后,进入系统有问题,但是可以进入 grub rescue,自己又不知道到底问题出哪里的话,简单的办法是使用 boot-repair 这个工具。

1
2
3
sudo add-apt-repository ppa:yannubuntu/boot-repair
sudo apt-get update
sudo apt-get install -y boot-repair

然后打开 bootrepair 进行一键修复即可。
我的在运行前,询问我/dev/sda 是可移动硬盘吗,当然不是。。在认为他的自动处理能力有问题以后,我点开了高级设置,手动选择了正确的 grub 位置(/dev/sda),然后等待修复完成,大概不到 10mins。
完成后重启即可进入系统。

拓展

注意文件系统中的软/硬链接

/home迁移时,拷贝时不是直接 cp 的,因为/home 目录下存在很多软链接、硬链接、文件、嵌套的文件夹等,所以使用了下面的命令来拷贝:

1
$ find . -depth -print0 | cpio --null --sparse -pvd /mnt/newhome/

QA

  1. 拷贝 boot 分区为什么用 cp 而不是 dd 命令?
    1
    2
    3
    # bs每次複製的塊大小
    # count要複製的次數
    dd if=/dev/hda of=/dev/hdc bs=4 count=1024
    cp 是对文件(字节方式)操作的,dd 是对块(扇区方式)进行操作的。
    dd 将原始数据(raw data)按照数据源的格式原封不动地拷贝到目的地。cp 将文件和目录拷贝到目的地后按照目的地的格式排列新数据。cp 将一个硬盘的数据复制到第二个硬盘上,由于系统写硬盘不是顺序写的,哪里有足够的空间就放到哪,所以第二个硬盘相同扇区号上的数据和第一块硬盘有可能是不同的。
    注意:对于不能以文件或目录格式呈现的数据(如引导启动块的数据),cp 无能为力。
    似乎 dd 相对 cp 来说更合适,因为 cp 在拷贝完后还得再设置 UUID、flags,为什么还用 cp?实际上是因为以前装系统的时候没把 boot 分区和 home 分区分开来,现在没法用 dd 一个分区一个分区拷贝,这一点在上边也已经说过了。
  2. boot 分区的标记有什么作用?
    在 gparted 中可以为 boot 分区设置 boot 标记,表明这个分区是有启动点的。(暂时没有找到关于 EFI 更深入的资料,只能这么理解了)

参考

  1. ubuntu tips: Move /home to it’s own partition(移动”/home”目录到单独的分区)
  2. 迁移 linux 系统到新硬盘
  3. 如何使用 fdisk 进行分区
  4. dd 与 cp 的区别