.. _usb_boot_pi_3: =============================== USB移动存储启动和运行树莓派3 =============================== .. note:: 本文实践是在 :ref:`pi_3` 结合USB转接卡,使用 2.5寸 笔记本硬盘构建的。后续我准备再使用 :ref:`pi_4` 结合USB SSD移动硬盘来构建 :ref:`pi_cluster` 。 注意,最初我的实践是在树莓派官方32位raspbian系统上实践的,最近为了构建 :ref:`pi_cluster` 运行 :ref:`kubernetes` ,我改为使用64位Ubuntu系统。Ubuntu for Raspberry Pi和官方Raspbian在bootloader上有点差异,详见文章最后部分。 设置USB启动模式 ================ 在设置树莓派3能够从磁盘启动之前,它首先需要从SD卡启动并配置激活USB启动模式。这个过程是通过在树莓派SoC的OTP(One Time Programmable)内存设置一个激活从USB存储设备启动的。 .. note:: 一旦这个位设置,就不再需要SD卡。但是要注意:任何修改OTP都是永久的,不能撤销。 激活USB存储启动 ================= - 通过更新准备 ``/boot`` 目录:: sudo apt update && sudo apt upgrade - 使用以下命令激活USB启动模式:: echo program_usb_boot_mode=1 | sudo tee -a /boot/config.txt 以上命令在 ``/boot/config.txt`` 中添加了 ``program_usb_boot_mode=1`` 。 使用 ``sudo reboot`` 命令重启树莓派,然后检查OTP是否已经设置成可编程:: vcgencmd otp_dump | grep 17: 确保输出是 ``17:3020000a`` 。如果不是这个输出值,责表示OTP还没有成功设置为可编程。 - 然后就可以从 ``config.txt`` 最后删除掉 ``program_usb_boot_mode`` ,这样你把这个SD卡插到其他树莓派上不会导致其进入USB启动模式。 .. note:: 确保 ``config.txt`` 配置文件最后没有空白行。 USB存储设备 ============= 从 ``2017-04-10`` 版本开始,可以通过复制方式将操作系统镜像直接安装到USB存储器。这种方式也可以针对一个SD卡。 由于我们已经在[树莓派快速起步](raspberry_pi_quick_start)过程中在SD卡上安装了Raspbian,现在我们可以从已经安装了系统的SD卡中直接复制到磁盘中,就可以保留原先所有做过的更改。 这个过程和从Raspbian镜像复制到SD卡相似,只是需要注意磁盘的命令,一定要确保是从SD卡复制到磁盘。 - ``/dev/mmcblk0`` SD卡 - ``/dev/sda`` USB接口的磁盘 .. note:: 目前我部署 :ref:`pi_cluster` 采用64位操作系统,设备是 :ref:`pi_4` 和 :ref:`pi_3` 。实际上Ubuntu提供的64位ARM操作系统是一个版本,可以同时用于树莓派3和4。 我首次安装64位操作系统是在树莓派4上,完成后,我将采用本方案clone到树莓派3上组建集群。 - Raspberry Pi 4安装的64位Ubuntu分区如下:: Disk /dev/mmcblk0: 119.9 GiB, 127865454592 bytes, 249737216 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: dos Disk identifier: 0xab86aefd Device Boot Start End Sectors Size Id Type /dev/mmcblk0p1 * 2048 526335 524288 256M c W95 FAT32 (LBA) /dev/mmcblk0p2 526336 249737182 249210847 118.9G 83 Linux - 划分USB磁盘分区 对照TF卡上的当前树莓派操作系统分区,可以看到有2个分区,一个是FAT32分区,另一个是Linux分区,我们也需要对应在USB磁盘上完成这个划分。 使用 ``parted`` 划分分区 .. literalinclude:: parted_pi_ssd :linenos: .. note:: 4k对齐参考 `How to align partitions for best performance using parted `_ ,其中参数查看 ``/dev/sda`` ,所以第一个分区起始扇区选择 ``2048s`` :: # cat /sys/block/sda/queue/optimal_io_size 0 # cat /sys/block/sda/queue/minimum_io_size 512 # cat /sys/block/sda/alignment_offset 0 # cat /sys/block/sda/queue/physical_block_size 512 注意:这里划分 ``/dev/sda2`` 只分配30G给操作系统使用,因为我准备把剩余的空间作为存储空间,将在后续使用卷管理来维护,并构建Ceph和GlusterFS存储。 - 退出 ``parted`` 检查存储的 ``PARTUUID`` :: # blkid /dev/sda /dev/sda: PTUUID="5e878358" PTTYPE="dos" # blkid /dev/sda1 /dev/sda1: SEC_TYPE="msdos" UUID="1E2C-FFAE" TYPE="vfat" PARTUUID="5e878358-01" # blkid /dev/sda2 /dev/sda2: PARTUUID="5e878358-02" 注意,此时看不到 ``UUID`` , ``UUID`` 在 ``mkfs.ext4 /dev/sda2`` 之后就会标记上。 和普通的PC不同,树莓派会默认尝试搜索可以启动的分区(默认会从SD卡启动,15秒之后将尝试从USB存储启动,即前面修改的配置)。 .. note:: 一定要有一个fat分区用于存放 ``/boot`` 分区内容,因为UEFI启动默认会寻找vfat分区内容来启动。 .. note:: - 如果使用 ``dd`` 命令复制磁盘分区,所以要确保 ``/dev/sda2`` 磁盘分区大于源SD卡分区 ``/dev/mmcblk0p2`` - 如果使用 ``tar`` 方式复制磁盘文件系统,则目标分区只要能够容纳源 ``/dev/mmcblk0p2`` 文件就可以 - 我采用的是这个方法 通过 dd 复制磁盘(我没有采用这个方法) --------------------------------------- 如果使用 ``dd`` 复制磁盘,责执行操作系统复制命令如下(不需要区分磁盘分区):: dd if=/dev/mmcblk0 of=/dev/sda conv=fsync .. note:: ``dd`` 复制命令参考了在Linux中制作镜像到SD卡的命令 `INSTALLING OPERATING SYSTEM IMAGES ON LINUX `_ 通过 tar 复制磁盘 ------------------- - 使用 ``tar`` 方式复制磁盘文件:: cd / tar -cpzf pi.tar.gz --exclude=/pi.tar.gz --one-file-system / mkfs.ext4 /dev/sda2 mount /dev/sda2 /mnt sudo tar -xpzf /pi.tar.gz -C /mnt --numeric-owner .. note:: 上述备份的 ``/pi.tar.gz`` 没有包含 ``/boot`` 分区内容,所以后面我们还有一步单独复制 ``/boot`` 分区的操作。 .. note:: 在执行了 ``mkfs.ext4 /dev/sda2`` 之后,再使用 ``blkid /dev/sda2`` 就能够看到 ``UUID`` ,这个 ``UUID`` 是文件系统UUID:: blkid /dev/sda2 显示输出:: /dev/sda2: UUID="b2e461e7-5a68-434d-bda1-c7c137e8c38e" TYPE="ext4" PARTUUID="1a99ca08-02" - 格式化 ``/dev/sda1`` 作为vfat32 分区:: # mkfs.vfat /dev/sda1 <= 这里没有指定FAT32文件系统,默认格式化是FAT16 # 检查发现`fdisk`虽然可以通过`c`这个type来标记分区为FAT32,但是如果`mkfs.fat`不指定`-F32`参数 # 会导致文件系统还是`fat16`文件系统,虽然用`fdisk -l`看不出,但是`parted`则能够看到是`fat16` mkfs.fat -F32 /dev/sda1 - 早期的32位系统可以通过以下命令复制 ``/boot`` 分区:: mount /dev/sda1 /mnt/boot (cd /boot && tar cf - .)|(cd /mnt/boot && tar xf -) - 但是现在64位操作系统 ``/dev/sda1`` 已经不是直接挂载为 ``/boot`` 目录,检查对比TF卡中操作系统可以看到:: # df -h Filesystem Size Used Avail Use% Mounted on ... /dev/mmcblk0p2 117G 3.5G 109G 4% / ... /dev/mmcblk0p1 253M 97M 156M 39% /boot/firmware 所以对应挂载目录不同,我们采用以下命令:: mount /dev/sda1 /mnt/boot/firmware (cd /boot/firmware && tar cf - .)|(cd /mnt/boot/firmware && tar xf -) .. note:: 要避免包含目录,使用 ``--exclude`` 参数。参考 `Exclude Multiple Directories When Creating A tar Archive `_ 。但是我使用如下命令依然包含了不需要的目录( **失败** ),最后还是采用 :ref:`recover_system_by_tar` 来完成:: (cd / && tar cf - --exclude "/mnt" --exclude "/sys" --exclude "/proc" --exclude "/lost+found" --exclude "/tmp" .)|(cd /mnt && tar xf -) 配置修改 =========== .. note:: 注意:除非使用 ``dd`` 来复制SD卡到HDD才能保持原有的 ``PARTUUID`` ,否则使用 ``parted`` 划分分区以及使用 ``mkfs`` 创建文件系统,都会使得目标磁盘的 ``UUID`` 和 ``PARTUUID`` 变化。则需要修改启动配置文件反映分区标识的变化。 - 检查当前SD卡的分区UUID,例如如下:: $ sudo blkid /dev/mmcblk0p1 /dev/mmcblk0p1: LABEL="boot" UUID="CDD4-B453" TYPE="vfat" PARTUUID="5e878358-01" $ sudo blkid /dev/mmcblk0p2 /dev/mmcblk0p2: LABEL="rootfs" UUID="72bfc10d-73ec-4d9e-a54a-1cc507ee7ed2" TYPE="ext4" PARTUUID="5e878358-02" $ sudo blkid /dev/mmcblk0 /dev/mmcblk0: PTUUID="5e878358" PTTYPE="dos" note:: ``/dev/mmcblk0`` 使用 ``parted`` 检查显示是 ``msdos`` 分区表,但是使用 ``blkid`` 检查可以看到具有 ``PARTUUID`` 。参考 `Persistent block device naming `_ ,原文介绍 ``GPT`` 分区表支持 ``PARTUUID`` 。不过,我实践发现树莓派默认安装的系统使用的是 ``msdos`` 分区表,但是也具有 ``PARTUUID`` 。测试验证发现,通过使用 ``parted`` 划分磁盘分区就会有 ``PARTUUID`` 。 以下是 ``/dev/mmcblk0`` 在 ``parted`` 中 ``print`` 输出:: GNU Parted 3.3 Using /dev/mmcblk0 Welcome to GNU Parted! Type 'help' to view a list of commands. (parted) print Model: SD SN128 (sd/mmc) Disk /dev/mmcblk0: 128GB Sector size (logical/physical): 512B/512B Partition Table: msdos Disk Flags: Number Start End Size Type File system Flags 1 1049kB 269MB 268MB primary fat32 boot, lba 2 269MB 128GB 128GB primary ext4 上述可以看到 | 分区 | PARTUUID | | `/dev/mmcblk0p1` | `5e878358-01` | | `/dev/mmcblk0p2` | `5e878358-02` | 如果使用 ``dd`` 命令来复制磁盘分区,则HDD磁盘的 ``/dev/sda1`` 和 ``/dev/sda2`` 的 ``PARTUUID`` 会和原先的TF卡完全相同,即依然保持 ``5e878358-01`` 和 ``5e878358-02`` 。这样就不用修改HDD文件系统中的配置。 但是通过磁盘 ``parted`` 和 ``mkfs.ext4`` 创建的HDD文件系统,然后再通过 ``tar`` 恢复操作系统。此时磁盘 ``PARTUUID`` 和 ``UUID`` 不同,则要修改对应配置 ``/boot/cmdline.txt`` 和 ``/etc/fstab`` :: # blkid /dev/sda1 /dev/sda1: UUID="CB15-2042" TYPE="vfat" PARTUUID="5e878358-01" # blkid /dev/sda2 /dev/sda2: UUID="d11f9da5-aeee-477f-9d95-d290c6f56267" TYPE="ext4" PARTUUID="5e878358-02" 32位操作系统启动配置 --------------------- .. note:: 以下32位操作系统配置方法是我之前的记录整理,当前实践是64位操作系统,方法不同。 - 检查 ``/boot/cmdline.txt`` 配置文件,可以看到原先配置内容如下:: $ cat cmdline.txt dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=PARTUUID=5e878358-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait 这里可以看到 ``root=PARTUUID=5e878358-02`` 就是SD卡的分区 ``/dev/mmcblk0p2`` 对应的 ``PARTUUID="5e878358-02"`` - 根据前述检查USB磁盘的分区 ``UUID`` ,即 ``e3f5b3fb-297c-44fe-b763-566b51b87524`` ,注意,我们要将启动指向分区 ``/dev/sda2`` ,因为这个分区就是从 ``/dev/mmcblk0p2`` 通过 ``tar`` 方式复制出来的。修改 ``/mnt/boot/cmdline.txt`` (该文件位于 ``/dev/sda2`` 这个HDD分区文件系统中):: dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=PARTUUID=1a99ca08-02 rootfstype=ext4 elevator=cfq fsck.repair=yes rootwait .. note:: 这里修改了2个地方: - ``root=PARTUUID=e3f5b3fb-297c-44fe-b763-566b51b87524`` 指向HDD磁盘分区 ``/dev/sda2`` 表示从USB外接的硬盘启动 - ``evevator=cfq`` 是修改原先针对SSD/SDCARD/TFCARD这类固态硬盘优化参数 ``deadline`` ,由于使用机械硬盘针对HDD硬盘优化参数修改成 ``cfq`` - 修改 ``/mnt/etc/fstab`` 配置文件,修改 ``/`` 行中 ``PARTUUID`` 内容:: proc /proc proc defaults 0 0 PARTUUID=5e878358-01 /boot vfat defaults 0 2 PARTUUID=5e878358-02 / ext4 defaults,noatime 0 1 - 关机,然后取出TF卡,再次加电,此时树莓派将从外接USB的HDD磁盘启动 .. note:: 测试下来,如果再次使用TF卡,依然能够优先从TF卡启动树莓派。只有TF卡不可用时候,才会从USB HDD启动。 64位Ubuntu启动修改 ---------------------- Ubuntu镜像使用 u-boot 作为bootloader,而树莓派内建的bootloader可以使用在 ``system-boot`` 分区的config.txt文件的一些修改。 64位Ubuntu操作系统 ``/boot`` 目录并没有独立建立分区,而是在 ``/boot/firmware`` 目录单独建立vfat32分区,并且这个分区是可启动分区。 对于2017年4月以后的系统,必须具备bootloader文件,所以需要获得最新的 bootcode.bin, fixup.dat 和 start.elf 并复制到 ``system-boot`` 分区。另外,Ubuntu的u-boot脚本硬编码了使用SD卡启动,所以需要修改bootloader,采用树莓派的bootloader才能够从USB磁盘启动。 解压缩内核 ~~~~~~~~~~~~~ .. warning:: 每次Ubuntu升级内核都需要重复这个步骤。 首先我们需要把 ``vmlinuz`` 解压缩成 ``vmlinux`` ,这是因为当前还不支持从压缩的 64位 arm 内核启动 - 找出内核镜像的gziped内容位置:: od -A d -t x1 vmlinuz | grep '1f 8b 08 00' 输出内容:: 0000000 1f 8b 08 00 00 00 00 00 02 03 ec 5c 0f 74 54 e 这里第一个 ``0000000`` 数字就是我们查找的位置,在这个位置右边就是镜像起点。 - 使用 ``dd`` 命令输出数据并使用 ``zcat`` 解压缩。如果你看到的数字不是 ``0000000`` ,就用你看到的数字作为 ``skip=`` 参数:: dd if=vmlinuz bs=1 skip=0000000 | zcat > vmlinux 修改bootloader ~~~~~~~~~~~~~~~~~ - 查看 ``/mnt/boot/firmware/config.txt`` (也就是USB硬盘分区 ``/dev/sda1`` 挂载后的配置文件) ,默认内容是针对不同树莓派的启动配置。注意,在配置文件开头说明建议不要直接修改 ``config.txt`` ,而是应该修改 ``usercfg.txt`` 来包含用户修改:: [pi4] kernel=uboot_rpi_4.bin max_framebuffers=2 [pi2] kernel=uboot_rpi_2.bin [pi3] kernel=uboot_rpi_3.bin [all] arm_64bit=1 device_tree_address=0x03000000 enable_uart=1 cmdline=cmdline.txt include syscfg.txt include usercfg.txt 按照Ubuntu文档,需要将修改成:: kernel=vmlinuz initramfs initrd.img followkernel #device_tree_address=0x03000000 所以我实际修改 ``config.txt`` 内容如下:: [pi4] #kernel=uboot_rpi_4.bin #max_framebuffers=2 [pi2] #kernel=uboot_rpi_2.bin [pi3] #kernel=uboot_rpi_3.bin [all] arm_64bit=1 device_tree_address=0x03000000 kernel=vmlinux initramfs initrd.img followkernel 更新.dat和.elf文件 ~~~~~~~~~~~~~~~~~~~~ - 检查 ``/mnt/boot`` 目录下内容,可以看到 ``dtbs`` 目录因为我最初是安装的树莓派4系统,所以 ``dtb`` 软链接都是只想树莓派4,这部分需要手工修改。 不过我发现使用树莓派4的TF卡插入到树莓派3上能够正常启动 以下是当前 ``/mnt/boot`` 下内容,如果有必要我准备手工修改 ``dtb`` 软链接:: # ls -lh total 81M -rw------- 1 root root 4.0M Jul 10 05:18 System.map-5.4.0-1015-raspi -rw------- 1 root root 4.0M Sep 5 01:03 System.map-5.4.0-1018-raspi -rw-r--r-- 1 root root 216K Jul 10 05:18 config-5.4.0-1015-raspi -rw-r--r-- 1 root root 216K Sep 5 01:03 config-5.4.0-1018-raspi lrwxrwxrwx 1 root root 43 Sep 18 12:53 dtb -> dtbs/5.4.0-1018-raspi/./bcm2711-rpi-4-b.dtb lrwxrwxrwx 1 root root 43 Sep 12 06:07 dtb-5.4.0-1015-raspi -> dtbs/5.4.0-1015-raspi/./bcm2711-rpi-4-b.dtb lrwxrwxrwx 1 root root 43 Sep 18 12:53 dtb-5.4.0-1018-raspi -> dtbs/5.4.0-1018-raspi/./bcm2711-rpi-4-b.dtb drwxr-xr-x 4 root root 4.0K Sep 12 06:08 dtbs drwxr-xr-x 5 root root 5.0K Jan 1 1970 firmware lrwxrwxrwx 1 root root 27 Sep 12 06:06 initrd.img -> initrd.img-5.4.0-1018-raspi -rw-r--r-- 1 root root 29M Sep 12 06:07 initrd.img-5.4.0-1015-raspi -rw-r--r-- 1 root root 29M Sep 18 12:53 initrd.img-5.4.0-1018-raspi lrwxrwxrwx 1 root root 27 Jul 31 16:44 initrd.img.old -> initrd.img-5.4.0-1015-raspi lrwxrwxrwx 1 root root 24 Sep 12 06:06 vmlinuz -> vmlinuz-5.4.0-1018-raspi -rw------- 1 root root 8.1M Jul 10 05:18 vmlinuz-5.4.0-1015-raspi -rw------- 1 root root 8.1M Sep 5 01:03 vmlinuz-5.4.0-1018-raspi lrwxrwxrwx 1 root root 24 Jul 31 16:44 vmlinuz.old -> vmlinuz-5.4.0-1015-raspi 以下是复制树莓派3的dtb文件并建立软链接步骤:: cd /mnt/boot/dtbs/5.4.0-1018-raspi cp /lib/firmware/5.4.0-1018-raspi/device-tree/broadcom/bcm2837-rpi-3-b.dtb ./ cd /mnt/boot/dtbs/5.4.0-1015-raspi cp /lib/firmware/5.4.0-1015-raspi/device-tree/broadcom/bcm2837-rpi-3-b.dtb ./ cd /mnt/boot unlink dtb ln -s dtbs/5.4.0-1018-raspi/./bcm2837-rpi-3-b.dtb ./dtb unlink dtb-5.4.0-1015-raspi ln -s dtbs/5.4.0-1015-raspi/./bcm2837-rpi-3-b.dtb ./dtb-5.4.0-1015-raspi unlink dtb-5.4.0-1018-raspi ln -s dtbs/5.4.0-1018-raspi/./bcm2837-rpi-3-b.dtb ./dtb-5.4.0-1018-raspi .. note:: 不过我执行了上述软链接修正,在树莓派3上依然不能从USB磁盘启动。从 `USB Boot Ubuntu Server 20.04 on Raspberry Pi 4 `_ 来看,可能需要更新最新的 `raspberrypi/firmware `_ - 重新开始,从 `raspberrypi/firmware `_ 下载最新版本的 .dat 和 .elf 文件 然后复制到 ``/mnt/boot/firmware`` 目录下覆盖原文件:: cd /mnt/boot/firmware cp ~/Downloads/firmware-/boot/*.dat ./ cp ~/Downloads/firmware-/boot/*.elf . .. note:: 这步待验证 修改bootloader ~~~~~~~~~~~~~~~~ - 修改 ``/mnt/boot/firmware/cmdline.txt`` 将 ``cmdline.txt`` :: net.ifnames=0 dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=LABEL=writable rootfstype=ext4 elevator=deadline rootwait fixrtc 原先记录是 ``root=LABEL=writable`` 实际上就是SD卡的原先的 ``/dev/mmcblk0p2`` (分区2) 的文件系统LABEL名字(我觉得其实格式化移动硬盘也加上这个标签,或许不需要再修订 ``cmdline.txt`` 配置) 。我现在修改成USB移动硬盘分区的PARTUUID。 另外还修改一个针对机械硬盘优化参数 ``elevator=cfq`` 。 完整修订如下:: net.ifnames=0 dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=PARTUUID=5e878358-02 rootfstype=ext4 elevator=cfq rootwait fixrtc 分区 ~~~~~~~~~~~~~~ 参考原先SD卡的分区标记,在 ``/dev/sda1`` 上加上启动标记:: # parted /dev/sda GNU Parted 3.3 Using /dev/sda Welcome to GNU Parted! Type 'help' to view a list of commands. (parted) print Model: External USB3.0 (scsi) Disk /dev/sda: 500GB Sector size (logical/physical): 512B/4096B Partition Table: msdos Disk Flags: Number Start End Size Type File system Flags 1 1049kB 256MB 255MB primary fat32 lba 2 256MB 30.0GB 29.7GB primary ext4 (parted) set 1 boot on (parted) print Model: External USB3.0 (scsi) Disk /dev/sda: 500GB Sector size (logical/physical): 512B/4096B Partition Table: msdos Disk Flags: Number Start End Size Type File system Flags 1 1049kB 256MB 255MB primary fat32 boot, lba 2 256MB 30.0GB 29.7GB primary ext4 (parted) quit Information: You may need to update /etc/fstab. - 修改 ``/mnt/etc/fstab`` 配置,对应磁盘分区的PARTID:: PARTUUID="5e878358-02" / ext4 defaults 0 0 PARTUUID="5e878358-01" /boot/firmware vfat defaults 0 1 .. note:: 我现在暂时还没有解决Raspberry Pi 3从USB存储启动Ubuntu Server 64bit for Raspberry Pi系统,似乎是需要更新firmware来解决。 不过这两天感觉有点瓶颈(疲倦),先放下,等过两天解决 :ref:`usb_boot_ubuntu_pi_4` 之后再会过来处理。 参考 ====== - `Raspberry Pi: Adding an SSD drive to the Pi-Desktop kit `_ - `HOW TO BOOT FROM A USB MASS STORAGE DEVICE ON A RASPBERRY PI 3 `_ - `Ubuntu wiki - RaspberryPi USB booting `_ - `USB Boot Ubuntu Server 20.04 on Raspberry Pi 4 `_