分类
Linux

在 Arch Linux ARM (UEFI) 上使用 Grub

如何优雅地处理内核参数(cmdline)问题?今天我探索了一下 Arch Linux ARM(UEFI)下的 Grub 引导。这样做可以完全做到和固件分离,并且非常方便。

如果你阅读了上一篇文章: 在 RPI3 UEFI 上安装 Arch Linux ARM,那么可能会有这么一个问题:如何优雅地处理内核参数(cmdline)问题?今天我探索了一下 Arch Linux ARM(UEFI)下的 Grub 引导。这样做可以完全做到和固件分离,并且非常方便。

注意!

这不是任何产品的官方文档、帮助或使用说明,仅代表博主个人的经验总结,难免会有疏漏和错误之处。请务必结合官方文档进行阅读,以作补充。

我个人不喜欢 Grub 的其中一个原因是其配置复杂,我并不熟悉操作,所以很少使用之。不过,在 Arch Linux ARM 上,默认使用 EFISTUB。如果要更改参数,没有更优的选项:

  • 直接在 UEFI 固件更改。有长度限制,不好升级(?),容易丢失(?)等。
  • cmdline.txt 似乎不可行?(没有测试)
  • systemd-bootrEFINDClover 均不可用。

所以,Grub 就是我们最后一根稻草。幸运的是,Grub 有着对 AArch64 的支持,而且 Arch Linux ARM 有包。

首先,需要一个 Patch。

这里不得不提到 Grub 的配置生成脚本,它们位于 /etc/grub.d/。由于个人并不懂 Shell 编程,所以不能做一个很详细的解释和研究。不过,它似乎会定位 /boot/vmlinuz-* 作为内核(Arch 和 Ubuntu 上都使用 vmlinuz-<name> 作为内核可执行文件名,可以用 file 工具查看文件类型)。然而,Arch Linux ARM 默认会将内核安装到 /boot/Image。这个文件是 Pre-built EFISTUB 可执行文件,并不是由 mkinitcpio 生成的(请参考 这个新闻)。

很明显,grub-mkconfig 不会找到 Linux 内核,也就不会生成正确的配置。于是,有两种方式可以解决。

  1. 修改 /etc/grub.d/10_linux 脚本,让它搜索 Image
  2. 重命名 /boot/Image,让它符合习惯。

很明显,由于我不会 Shell 编程,第二种方式是最优的。考虑到每次更新 linux(内核包)都需要重命名,我写了一个 libalpm Hook,会自动在 /boot/Image 被软件包更新或新建后重命名:

# /etc/pacman.d/hooks/99-kernel-rename.hook
[Trigger]
Operation = Install
Operation = Upgrade
Type = Path
Target = boot/Image
Target = boot/Image.gz

[Action]
When = PostTransaction
Exec = /usr/bin/sh -c "/usr/bin/mv /boot/Image /boot/vmlinuz-linux"

然后重装 linux,就可以看到效果。至此,我们已经可以安装 Grub 了。

安装 Grub

安装 Grub 并不是难事,我们开始吧。

首先,确保 ESP 挂载到了 /boot 。然后安装软件包 grubefibootmgr。Grub 是本体,efibootmgr 用于修改 UEFI 引导项(虽说似乎没有作用,而且在本次并不需要修改,这个后面会讲)。然后,执行:

grub-install --target=arm64-efi --efi-directory=/boot --bootloader-id=Grub --removable # 需要 Root

详解:

  • --target=arm64-efi 设定平台。具体可选项可以用 grub-install --help 查看。
  • --efi-directory 设定 ESP 挂载位置。
  • --bootloader-id 这个有什么用不太清楚,不过大概是 UEFI 引导项的名字吧。
  • --removable 这一点很重要,虽说它不是启动系统必须的。它会把 EFI 可执行文件安装到 /boot/EFI/BOOT/bootaa64.efi 而不是默认的 /boot/EFI/Grub/grubEFI/boot/bootaa64.efi 是 UEFI 的默认路径(在启动的时候选择整个分区,即不设定具体程序,就会默认启动它),方便和 UEFI 固件分离设置,更加清晰、便捷。这也就是为什么我们不需要修改 UEFI 启动项,因为这是默认路径。

至此,Grub 已安装到系统。

修改内核参数

又到了这一部分。由于没有了 UEFI 固件的长度限制,我们可以写多些。默认内核参数保存在 /etc/default/grub

# /etc/default/grub
# (Skipped)
GRUB_CMDLINE_LINUX_DEFAULT=""
GRUB_CMDLINE_LINUX=""
# (Skipped)

这两个变量组合,就会成为最终的参数。我们这里选择修改 GRUB_CMDLINE_LINUX_DEFAULT

和上一篇一样,我们设定 rootrootwait。不过,既然没有了长度限制,那么就可以用 PARTUUID 了,这样就可以做到和分区号的分离,方便迁移。同时,还可以从 Arch Linux ARM 官方 GitHub 的一个 cmdline 里面复制一些其他的参数,我也不太明白是做甚么的,读者不要随意模仿(尤其是 RPI4 用户,因为 Arch Linux ARM 还有一个叫 rpi4 的 cmdline,具体有什么用就请读者自行研究了)。

最终,这里是我的配置:

# /etc/default/grub
# (Skipped)
GRUB_CMDLINE_LINUX_DEFAULT="root=PARTUUID=639a9dd5-06e6-ed4e-b09e-0ddb2657b546 rw rootwait console=ttyAMA0,115200 console=tty1 selinux=0 plymouth.enable=0 smsc95xx.turbo_mode=N dwc_otg.lpm_enable=0 kgdboc=ttyAMA0,115200 elevator=noop aduit=0"
GRUB_CMDLINE_LINUX=""
# (Skipped)

生成配置

生成配置是最简单的,一行便可。请注意,每次修改配置都需要重新生成(来源请求),并且不要手工修改生成的配置。

grub-mkconfig -o /boot/grub/grub.cfg # 需要 Root

-o 指定输出位置,Grub 会自动加载 /boot/grub/grub.cfg,所以无需修改。

修改引导项

诶,不是说和固件分离吗,怎么又要修改引导项了?

我们的配置确实和固件分离了,不过还需要清理干净之前的配置。打开 UEFI 固件设置,

  1. 看看有没有之前自定义的引导项(名字不是分区的等等),删掉
  2. 看看引导顺序,可以留默认(如果是 USB 引导,U 盘的 ESP 可能是第二个)
  3. 如果 U 盘的 ESP 不在引导顺序的第一个,确保之前的分区没有 EFI/boot/bootaa64.efi。之前已经说了,EFI 会自动启动这个文件。如果有,自然会优先引导他们啦。

好了,本篇就到此为止了。重启系统,看看效果吧。

参考