最近有個老固態硬盤空下來了,雖然寫入速度沒那么快,但是足夠滿足千兆網絡了,所以我就想把現在給樹莓派使用的固態硬盤換下來。由于一些設置很浪費時間,所以我不打算重裝系統。此外這個老固態是 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 的啟動器固件。因為兩個硬盤有時候不小心同時連接到樹莓派啟動,可能會導致啟動器固件出現問題。可以看看這篇文章修復固件。