Arch Linux UEFI 启动二三事
Arch Linux UEFI 启动二三事
由于我对 Linux 乃至整个 UEFI 的启动机制尚且“浅尝辄止”,本文并不会展开很多硬核内容,只是对我个人使用过的启动方案做个总结。
引导和启动
在维基百科中二者似乎是同一概念,搜索“启动程序”会跳转到“引导程序”的介绍。
另见英文维基:Booting
国内很多折腾 WinPE 的人(包括我)对此也并没有很明确的区分;当然有些博客则对开机装载 Linux 的过程拆分成引导、启动两个阶段。本文为了方便起见,用词不作区分。
在此感谢岛风 @frg2089 指路。
UEFI 启动简述:启动项管理
UEFI 规范定义了名为“UEFI 启动管理器”的一项功能 …… 是一种固件策略引擎,可通过修改固件架构中定义的全局 NVRAM 变量来进行配置。启动管理器将尝试按全局 NVRAM 变量定义的顺序依次加载 UEFI 驱动和 UEFI 应用程序(包括 UEFI 操作系统启动装载程序)。……
本“议题”只讨论 UEFI 原生启动项和回退路径启动项。恕不对 BIOS 兼容的部分作详细展开。
i. 原生启动项
用 Windows 7 及更高版本系统的朋友肯定知道这个东西:Windows Boot Manager。bootmgr
它代替了ntldr
,从此便沿用至今。
事实上,Windows Boot Manager 是系统安装完成后,初次加载系统时为其创建的原生启动项。它明确指出需要启动指定设备中的指定引导文件(即bootmgfw.efi
)。
即便 WinToGo 也是如此——在初次以 U 盘身份进入 WTG 系统时,Windows 也会为该设备作配置——所谓“正在准备设备”。在此过程中,顺带把原生启动项建立好。然后重启之后再按快捷键进入启动菜单,你可能会在部分主板上发现有两个启动项,指向同一个设备:
Windows Boot Manager ( Koi Series Pro ...)
USB HDD: Koi Series Pro ...
需要注意的是,原生启动项是存储在主板里的(更准确的说,是全局 NVRAM 变量)。这多少可以解释为什么 Grub 引导那么脆弱(
ii. 回退路径启动项
对于 WinPE、Windows 安装镜像而言,它们并非用于长线运行,往往没有“准备设备”的步骤,那么 UEFI 如何认出它们捏? 还记得上面提到的同一设备双启动项吗?UEFI 固件是能够找到可启动设备,并且尝试启动的。但它是依据什么去找的捏?
UEFI 固件首先会遍历各硬盘的 ESP 分区,并在其中查找\EFI\BOOT\boot{cpu_arch}.efi
。前面的这一固定路径就称为回退路径,通过查找回退路径建立的启动项就称作回退路径启动项。其中,cpu_arch
即 CPU 架构,已知的有:
x64
:x86-64ia32
:x86-32ia64
:Itaniumarm
:AArch32,即 arm32(胳膊 32)aa64
:AArch64,即 arm64(64 条胳膊)
注
UEFI 的路径系统与 Windows 类似:以\
分隔,不区分大小写。
如果同一硬盘、同一 CPU 架构存在多个匹配的 EFI 文件(比如,可能有两个 ESP 分区,分开装不同系统的 EFI),那么只会选第一个有效的去执行。
对于 WinPE U 盘,通常它们是 MBR 分区表,那么会考虑更泛用的搜索:采用 FAT 文件系统的活动分区; 对于 GPT 分区表,可以直接搜索 ESP 分区。当然如今的主板并不会卡那么死,哪怕只是普通的 FAT 分区,也会尝试搜索、执行。
也就是说,哪怕原生启动项意外被固件扬了,只要还有回退启动项,便仍可从同一个硬盘启动系统。
相关信息
实际上bcdboot
工具会在 ESP 分区里同时写入bootx64.efi
和bootmgfw.efi
,这两个 EFI 除了文件名以外并无区别。 前者即回退路径启动项,作为启动 Windows 的后备方案。
启动加载器(以 Grub 为主)
这也是最广泛使用的启动方式 ,Windows 也干了。在 Linux 当中,最常用的加载器是 Grub。当然,也有使用 rEFInd 的。
启动加载器(bootloader)本身作为跳板,被 UEFI 固件加载后,需要根据配置找到真正的 Linux 内核,并经由内核引导用户硬盘上的 Arch 系统。而在 Windows 中,bootmgfw.efi
会根据BCD
配置文件,执行硬盘其中一个 Windows 副本中的winload.exe
,并将该副本的其余加载流程交给它完成。
正常使用 Windows 单系统的用户可能对启动过程并无察觉,因为 Windows 为了确保能够启动,会时不时刷新启动项。但一旦与 Linux 混用,你就需要留意 Linux 的加载器会不会被 Windows 刷下去(甚至被覆盖)。除此之外,尽管因“机”而异,但 UEFI 固件有可能会自动清理不再可用的启动项。比如重新插拔固态,有可能会出现掉引导的情况。因此就个人来说,我不会再考虑 bootloader 了。
i. 修复 Grub 引导
Windows 启不动我们会尝试修复引导,Arch 亦然。修复 Grub 引导实际上就是重走 Grub 安装流程:
mount
挂载相应分区;genfstab
重建挂载表(如有必要);
个人建议无论如何都重建一遍
fstab
。反正刷完绝对是最新的。
arch-chroot
切换进硬盘上的系统;grub-install
重建 grub 引导。
ii. 补充回退启动项
事实上,需要反复重建 Grub 引导的一大原因就在于,Grub 只会写入它自己的grubx64.efi
,以及原生启动项:
那么办法也很简单:像 Windows 那样也建一个回退路径启动项。具体来说,在 ESP 分区里建立EFI\BOOT
目录,复制grubx64.efi
重命名成bootx64.efi
嘛。Windows 不也干了(
当然如果是像图中那样不止一个 Grub,甚至同盘 Windows 和 Arch 双系统,那我不推荐你这么做。
注意
不要在这边试图软链接节省空间!
固件直接引导(EFIStub)
Grub 本身写入 ESP 的内容不多,配置啊、Linux 内核啊都在/boot
。有人便主张把/boot
还给/
,ESP 分区实际挂载/efi
。 而岛风则提出了更激进的主张:让固件直接引导内核。
An EFI boot stub (aka EFI stub) is a kernel that is an EFI executable, i.e. that can directly be booted from the UEFI.
根据 Wiki,默认情况下 Arch Linux 的内核本身就是可启动 EFI,只是需要附加内核参数:
# 为便于阅读,这里分了三行。
root=UUID=7a6afcd0-a25a-4a6c-bf7b-920b53097eae
resume=UUID=b84ae173-edbc-442c-b00b-5c47eef203f1
rw loglevel=3 quiet initrd=\intel-ucode.img initrd=\initramfs-linux.img
内核参数详解
Grub 等启动加载器的本职工作就是帮你引导内核,因此它们的配置文件已经包含完整的内核参数了。 我上面列的内核参数是参照 Wiki 自行搭配,确认可行的参数。你也可以查 Wiki 自行组合。
root
:/
分区。目前只见到 UUID 填法。rw rootflags=subvol=@
:对/
分区挂载的附加属性,比如可读写、指定 Btrfs 子卷。resume
:休眠使用的交换分区,同样只见到 UUID 填法。休眠时会在指定 Swap 里创建内存映像。loglevel=3 quiet
:内核加载时的附加属性,如日志等级之类。initrd=\intel-ucode.img
:加载的初始化内存盘 (Init RAM Disk)。
一个.img
一条initrd=
,路径用\
分隔,顺序自左向右(可以参见 grub 的配置文件)
注
个人觉得这里 initrd 称作“初始化映像”更合适,毕竟需要填.img
嘛。
LiveCD 里的efibootmgr
工具可以直接操作固件的启动项。当然若是遵照律回指南和 Miku 指南,那么efibootmgr
业已安装到你的系统中,你可以在运行中的本机 Arch 系统中折腾:
# 首先确定你要操作的硬盘和分区,不要搞错。UUID 马上就会用到。
lsblk -o name,mountpoint,uuid
# 参见 Wiki,以 Btrfs 为例,仅供参考
sudo efibootmgr --create --disk /dev/nvme0n1 --part 1 \
--label "Arch Linux" --loader /vmlinuz-linux \
--unicode 'root=UUID=f6419b76-c55b-4d7b-92f7-99c3b04a2a6f rw rootflags=subvol=@ loglevel=3 quiet initrd=\intel-ucode.img initrd=\initramfs-linux.img'
创建启动项命令详解
--part 1
:你的 ESP 分区序号。根据lsblk
的树状图顺序判别。--label "Arch Linux"
:启动项名称。大多数固件并不支持中文。--unicode
后面跟内核参数。
归根结底,EFIStub 代替了启动加载器,由我们用户手动建立 UEFI 原生启动项。但这种方式硬要说优点吧……可能也就比 Grub 快那么几秒而已。维护起来并不比 Grub 轻松多少。
统一内核映像(UKI)
在应用 EFIStub 的时候我就在想,有没有可能写一个bootx64.efi
,直接带内核参数启动vmlinuz-linux
呢。后面偶然找到了“统一内核映像”的介绍,豁然开朗。
A unified kernel image (UKI) is a single executable which can be booted directly from UEFI firmware, or automatically sourced by boot loaders with little or no configuration.
根据介绍,UKI 实际上就是将内核引导的资源整合起来,打包而成的 EFI 可执行文件。某种意义上这也算是一种「固件直接引导」,只不过 EFIStub 只创建原生启动项,而它两种启动项都可以做。
UKI 通常包含……
- EFI 执行代码(决定它“可执行 EFI”的本质)
- Linux 内核
- 【可选】内核参数
- 【可选】初始化内存盘
- 【可选】CPU 微码
- 【可选】描述信息、启动屏幕图、设备树……(不重要)
只要集成了 EFI 执行代码和 Linux 内核,就可以称作统一内核映像了。
接下来以mkinitcpio
为例,但是不走寻常路。
i. 内核参数
Wiki 中介绍了两种方法:
- 动
/etc/cmdline.d/
里的.conf
配置。像root.conf
决定/
如何挂载,等等。 - 直接把所有参数搓成一行 echo 给
/etc/kernel/cmdline
文件。
于我而言,显然第二种更方便。
# 我在 LiveCD 里 arch-chroot 进去做的。别问我为什么没权限。
echo 'root=UUID=... resume=UUID=... rw loglevel=3 quiet' > /etc/kernel/cmdline
与 EFIStub 不同,这里不需要指定initrd=
——工具会自己打包。
注意
若启用“安全启动”,且 UKI 封装了内核参数,则 UEFI 固件会无视外部传入的其余参数。
ii. 预设文件
编辑/etc/mkinitcpio.d/linux.preset
。我们前面说过“不走寻常路”,关键就在这里。
下面是我自用的linux.preset
。想走寻常路的话还是抄别人的预设吧。
# mkinitcpio preset file for the 'linux' package
ALL_config="/etc/mkinitcpio.conf"
ALL_kver="/boot/vmlinuz-linux"
PRESETS=('default' 'fallback')
#default_config="/etc/mkinitcpio.conf"
#default_image="/boot/initramfs-linux.img"
default_uki="/efi/EFI/BOOT/bootx64.efi"
#default_uki="/efi/EFI/Linux/arch-linux.efi"
#default_options="--splash /usr/share/systemd/bootctl/splash-arch.bmp"
#fallback_config="/etc/mkinitcpio.conf"
#fallback_image="/boot/initramfs-linux-fallback.img"
fallback_uki="/boot/arch-linux-fallback.efi"
#fallback_uki="/efi/EFI/Linux/arch-linux-fallback.efi"
fallback_options="-S autodetect"
注意
有的教程会出现ALL_microcode=(/boot/*-ucode.img)
这一句。 这种指定微码的方法已经废弃,/etc/mkinitcpio.conf
已经有针对微码的 Hook 了,无需手动指定。
正常来说 UKI 均会输出到/efi/EFI/Linux/arch-linux*.efi
,据称systemd-boot
可以扫到并引导 UKI。 但生成bootx64.efi
让固件去加载不更直接?所以我选择直接导出到回退路径,如此连efibootmgr
都不需要了。
另一方面,fallback
也会生成 UKI,但bootx64.efi
只有一个。注意到之前 EFIStub 一节我只给initramfs-linux
构建了原生启动项,那么fallback
自然丢回/boot
里不管它。
注
把fallback
移回/boot
还有一重原因:尽量缩减 ESP 分区体积——本来/efi
设计上也有减小 ESP 分区大小的意图。 我试跑完发现fallback.efi
高达 140+MB,而arch-linux.efi
不过 30MB。
当然,保险起见,我并没有尝试default
生成 UKI、fallback
仍生成内存盘的做法,也没有试图把fallback
预设直接干掉。 如有勇士试验过一切正常,欢迎 Issues 反馈(
iii. 创建映像
按需建立路径,并跑一遍生成:
mkdir -p /efi/EFI/BOOT/
mkinitcpio -p linux
还是那句话,想循规蹈矩的不要学我。
建完之后退出系统,重启按快捷键进入启动菜单,这下该有你硬盘的 UEFI 回退路径启动项了:
HDD: PM8512GPKTCB4BACE-E162