最近有个老固态硬盘空下来了,虽然写入速度没那么快,但是足够满足千兆网络了,所以我就想把现在给树莓派使用的固态硬盘换下来。由于一些设置很浪费时间,所以我不打算重装系统。此外这个老固态是 120GB 的,要小于正在使用的固态硬盘(512GB),所以一些常见的复制硬盘的方法就不能使用了。
本文只需要使用树莓派,但是我是使用 SSH 访问树莓派的,所以截图会是 macOS 的样式。
在写完文章后,我自己又照着重新弄了一遍,确定没啥问题,但还是建议读者先对重要文件进行备份,以防万一。
为什么不使用dd
或SD card copier
本文不会使用 CLI 程序dd
或 GUI 应用程序 SD card copier。
不使用dd
因为这些程序复制的时候,是把整个盘复制了,这就导致写入肯定会失败(目标盘小于目的盘)。而且速度很慢,就算尽量减少传输经过的控制器数量和提高传输接口,但是从固态写固态也就是 30~45MB/s(不优化的话就 15~20MB/s)。
但是SD card copier 没有这个问题。不使用 SD card copier 是因为我还要插 HDMI 的线,而且不适合平时备份等操作,所以想研究一下使用 CLI 的方式。如果你能使用桌面模式,那么推荐直接使用 SD card copier 应用程序。而且这种方法要快很多,速度能稳定在 50~70MB/s。
原理
本文的方法和 SD card copier 原理是一样的:先在目标硬盘上分两个区,启动区(名称为bootfs
,格式为 Windows_FAT_32)和使用区(也被称为根区名称为rootfs
,格式为 Linux),然后使用rsync
复制,这样速度也快很多。
如果在树莓派上使用lsblk -f
(这里只是为了演示,后面会介绍一个信息更详细的工具)查看硬盘的结构和文件系统可以看到:
如果你好奇树莓派的启动分区
bootfs
为啥不使用 ext4 文件系统,这是因为要求最小为 16 GiB,但是启动区占这么大有点离谱了。此外,使用 FAT32 这种在 Windows、Linux、macOS 上都可以读取的文件系统,也方便更改一些设置(树莓派可以通过修改启动区bootfs
中的文件来修改一些配置的,比如 HDMI 级别等)。
由于启动需要,我们需要分区是按照“启动区-使用区”的顺序存在于硬盘上。所以我们需要先格式化出启动区,再将剩余部分格式化为使用区。
第一步:分区和新建文件系统
首先将两个盘连到树莓派上,记得将目标硬盘格式化成 Linux 可以识别的文件系统(Mac 上可以使用磁盘工具格式化为 exFAT,或者使用 Raspberry Pi Imager 将其格式化成 FAT32)。
接下来使用parted
工具进行分区,倒不是因为可以进行交互操作,主要是可以显示更详细的分区信息,而且设置一些参数也更方便。
首先使用sudo parted -l
命令当前连接的硬盘信息:
第一部分是系统盘,我们可以看到其中的每个分区的大小、起始地址、终止地址、文件系统等等信息。第二部分是我们的目标硬盘。
然后我们使用下面的命令在目标硬盘生成相似分区结构:
sudo parted /dev/sdb --script 'mklabel msdos mkpart primary fat32 4194.5kB 541MB mkpart primary ext4 541MB 120GB print quit'
/dev/sdb
是目标盘的设备名称。不要使用/dev/sdb1
,这是已存在的分区名称。- 使用
--script
则不会进入交互模式,这样一条命令即可完成操作。需要注意官方没有使用这个选项,但是如果按照上面的内容,不使用该选项,直接尾接后面的命令,那么会摧毁整个硬盘的分区(会有提示)。 mklabel msdos
设置分区模式为 MBR 格式。这里不使用gpt
是因为系统上就是msdos
(从上图的Partition Table
可以看到这个信息)。mkpart primary fat32 4194.5kB 541MB
这部分是划分启动区的命令,primary
表示是独立分区(或者称为“主分区”,对应的是“扩展分区”)。使用fat32
文件系统,起始地址使用4194.5kB
,因为树莓派启动是从第 8192 块开始的,但是parted
显示的数值是有误差的,以sudo fdisk -l
的显示内容为准。终止地址和系统盘的终止地址一样就行。mkpart primary ext4 541MB 120GB
这部分是划分根区的命令,使用ext4
文件系统,起始地址就是启动区的终止地址,但是终止地址是硬盘的大小,也就是和最开始图中显示的终止地址一样。print
会打印出分区信息。exit
会退出parted
。
需要注意个命令不能拆成两个,因为退出后再使用
mkpart
会抹除原有分区,这样最后还是生成一个分区。
显示如下:
这时工作还未全部完成,我们需要手动给两个分区手动创建一下FAT32
和ext4
文件系统,如下:
sudo mkfs.vfat -F 32 /dev/sdb1
sudo mkfs.ext4 -L rootfs /dev/sdb2
如果不进行这一步,那么会出现一些很奇怪的问题。前面分区的信息中,正确显示了我们是创建了一个供ext4
使用的分区,编号为2
。但是如果你用sudo parted -l
查看一下,会发现如下情况,File system
一栏中,第二个分区没有任何信息:
这时候加载这个分区发现会提醒以下错误信息:
$ sudo mount /dev/sdb2 /mnt/rootfs/
mount: /mnt/rootfs: wrong fs type, bad option, bad superblock on /dev/sdb2, missing codepage or helper program, or other error.dmesg(1) may have more information after failed mount system call.
mount: (hint) your fstab has been modified, but systemd still usesthe old version; use 'systemctl daemon-reload' to reload.
所以手动创建。
第二部:使用rsync
进行复制
在使用rsync
进行复制之前,需要将两个分区加载一下。
首先是在/mnt/
目录下新建一个目录,用来装载根分区:
sudo mkdir /mnt/rootfs
然后装载根分区:
sudo mount /dev/sdb2 /mnt/rootfs/
那么从哪复制(同步)到哪呢?还记得文章第一张图中的内容吗?显示了系统盘上两个分区对应的目录:/boot/firmware
(启动分区)和/
(根分区)。
首先是启动分区:
sudo rsync -axHAWXS --numeric-ids --info=progress2 --exclude={"/mnt/","/boot/firmware/"} /* /mnt/rootfs
这里的选项意思如下:
a
是存档模式,这会递归读取目录,不破坏符号链接、权限等信息。x
表示不会跨越文件系统的边界。H
保留硬连接。A
保留ACL(访问控制表)。W
禁用网络传输使用的增量算法。由于这里是都是本地路径,所以可以提高速度。S
可以有效地处理稀疏文件,这样传输完占用的空间更少。(不用这个的话,所有源文件可能 8GB,但是目的文件总和可能有 10GB)numeric-ids
使用数字 ID,而不是映射。info=progress2
会显示传输进度和信息,而且是整个传输的进度和信息,而不是每个文件的统计信息。- 有几个目录不需要复制,因为是重复的,所以要使用
--exclude=
来排除它们。在很多关于系统克隆的文章中,列出了其他可以忽略的目录/dev
、/sys
、/proc
,但是在我花了两个小时抢救之后,可以确定像/dev
、/sys
、/proc
这些都是不能忽略的,(关于如何抢救如果写了一篇博客我会在这里列出链接)
这里解释一下排除的两个目录。
- 第一个
/mnt/
是避免重复复制,因为这里就是复制的目的地。不然会复制两次甚至更多次。 - 最后一个
/boot/firmware/
是因为这部分内容是启动的内容,我们后面单独复制(一起复制可能会跳过这部分)。
在复制/sys/
目录下的部分文件会提示一堆错误。这些错误不用管,因为不能读取(显示没权限)或者是虚拟文件。详细内容可以看看这个帖子:Why does rsync fail to copy files from /sys in Linux? - Unix & Linux stackexchange
由于前面排除了一些目录,所以这里需要手动创建它们:
sudo mkdir /mnt/rootfs/mnt /mnt/rootfs/boot/firmware
然后就可以装载启动分区了:
sudo mount /dev/sdb1 /mnt/rootfs/boot/firmware/
然后就是复制启动分区,这里大部分选项和启动分区中的选项一样:
sudo rsync -axHAWXS --numeric-ids --info=progress2 /boot/firmware/* /mnt/rootfs/boot/firmware/
如果你比较细心的话,会发现原先红色的软链接文件现在变蓝了:
可以靠这个现象判断是否复制成功。但是并不是所有的文件都能这样判断的,比如说/proc/
中的一些软链接文件可能需要后续自动更新和生成才能变红。
第三步:修复/etc/fstab
和/boot/cmdline.txt
/etc/fstab
文件会列出启动时自动挂载的所有磁盘分区。由于我们是直接复制的,UUID 对不上,所以需要手动进行修改。
使用sudo fdisk -l
可以在Disk identifier
部分看到硬盘的标识符,这个我们后面需要用:
可以看到/dev/sdb1
和/dev/sdb2
两个分区的 UUID。根据这个内容对相应的文件进行修改,需要注意修改的文件是/mnt/rootfs/etc/fstab
(路径可能会有所不同),而不是系统盘中的/etc/fstab
。
/mnt/rootfs/etc/fstab
的两个PARTUUID=
的后面修改成对应的内容即可,如下(下图是没改完的,按理说两个 UUID 只有后面编号不同):
然后修改/mnt/rootfs/boot/cmdline.txt
(这是个软链接,实际文件在firmware
中)中的相应部分(下图中高亮部分):
这时候就一切完工了。我们可以关机、拔掉原来的硬盘启动试试看(一定要拔掉旧的,不然可能会用旧的启动分区来启动新的,我在实验的时候遇到过,然后又抢救了一回硬盘)。
可以看到能直接使用,几乎没什么区别,使用sudo fdisk -l
查看硬盘可以看到现在的/dev/sda
是这个硬盘了。
希望能帮到有需要的人~
参考资料或扩展阅读
本文虽然我付出了不少的时间和精力进行实验和尝试,但是也要感谢很多人编写的博客为我提供了思路和解决方案。
How can I change the volume name of a FAT32 filesystem? - Unix & Linux stackexchange:这篇帖子介绍了如何给 FAT32 修改分区名。
How to Format Disk Partitions in Linux - Dejan Tucakov:从这篇文章中我才知道某些命令行分区工具并不会创建文件系统,然后我发现parted
也是。
Clone File System Hierarchy to Another Disk With Rsync - Francesco Galgani:这篇文章介绍了如何使用rsync
克隆磁盘,也为我解决了很多rsync
复制的问题,还为我调整启动引导提供了灵感。文章主要是关于大众设备上的 Linux 系统,所以只提及了/etc/fstab
,而且引导是通过 GRUB。树莓派自己的系统的引导是通过config.txt
和cmdline.txt
文件进行的。如果你想尝试 GRUB 引导启动树莓派,那么可以看看这个贴子GRUB on RPi 4 - Raspberry Pi Forums(我没有尝试,对内容实际意义不做保证)。
The config.txt file - Raspberry Pi Documentation:这是config.txt
的官方文档,虽然本文没使用到config.txt
,但是在这篇文档中介绍了树莓派的大致启动流程,我也是从中发现cmdline.txt
,才能完成最后的修复工作。
Raspberry Pi 4 and Raspberry Pi 5 Boot Flow - Raspberry Pi Documentation:这部分文档介绍了详细的启动流程,作为扩展阅读可以看看。
Raspberry Pi 4/400 Bootloader Firmware Update/Recovery Guide - James A. Chambers:这篇博客介绍了如何修复和更新树莓派 4/400 的启动器固件。因为两个硬盘有时候不小心同时连接到树莓派启动,可能会导致启动器固件出现问题。可以看看这篇文章修复固件。