统一内核镜像 (Unified kernel image)
A 统一内核镜像 (UKI) 是一个单一的可执行文件,可以直接从 UEFI 固件启动,或者被引导加载器自动加载,只需很少甚至无需配置。它是 systemd-stub(7) 这样的 UEFI 引导存根程序、一个 Linux 内核镜像、一个 initramfs,以及 其他资源 的组合,封装在一个单一的 UEFI PE 文件中。
这个文件,因此所有这些组件都可以轻松地 签名 以用于 安全启动。
esp 表示 EFI 系统分区 的挂载点。准备统一内核镜像
有几种方法可以生成 UKI 镜像并将其安装到正确的位置 (esp/Linux 目录)。目前有几个工具在竞争这项功能,所以根据您的需求和喜好选择以下一种。
您只需要执行小节中的一个即可。
mkinitcpio
mkinitcpio 会自行组装 UKI,除非安装了 systemd-ukify。在这种情况下,UKI 创建将被卸载到 ukify,除非使用 --no-ukify 选项明确禁用。注意:当卸载到 ukify 时,mkinitcpio 会强制为其提供一个来自 {/etc,/usr/lib}/kernel/uki.conf 的配置文件。
内核命令行
mkinitcpio 支持从 /etc/cmdline.d 目录中的命令行文件中读取 内核参数。Mkinitcpio 会将此目录中所有扩展名为 .conf 的文件的内容连接起来,并使用它们来生成内核命令行。命令行文件中的任何以 # 开头的行都被视为注释,并被 mkinitcpio 忽略。请注意 **删除** 指向微码和 initramfs 的条目。
例如:
/etc/cmdline.d/root.conf
root=UUID=0a3407de-014b-458b-b5c1-848e92a327a3 rw
- 如果您的根文件系统位于非默认的 Btrfs 子卷上,请确保在
rootflags中设置必要的挂载标志。例如,如果您的系统子卷 ID 是256,您应该在内核命令行中添加rootflags=subvolid=256。请参阅 Btrfs#Mounting subvolume as root。 - 不需要复制
/etc/fstab中的所有标志,因为rootflags只在启动时使用。 systemd-remount-fs.service(8) 会在启动后自动读取/etc/fstab,重新挂载并应用其中列出的标志。
/etc/cmdline.d/security.conf
# enable apparmor lsm=landlock,lockdown,yama,integrity,apparmor,bpf audit=1 audit_backlog_limit=256
或者,可以使用 /etc/kernel/cmdline 来配置内核命令行。
例如:
/etc/kernel/cmdline
root=UUID=0a3407de-014b-458b-b5c1-848e92a327a3 rw quiet bgrt_disable
- 如果根分区 被 systemd 自动挂载,则可以省略
root=参数。 bgrt_disable参数告诉 Linux 在加载 ACPI 表后不显示 OEM logo。
.preset 文件
接下来,修改 /etc/mkinitcpio.d/linux.preset,或者您正在使用的预设文件,如下所示,并包含 EFI 系统分区 的适当挂载点
- 取消注释 (即删除
#)PRESETS=中每个项的PRESET_uki=参数, - 可选地,注释掉
PRESET_image=以避免存储冗余的initramfs-*.img文件, - 可选地,为需要添加启动画面镜像的每个
PRESET_options=行添加或取消注释--splash参数。
这是一个为 linux 内核和 Arch 启动画面准备的 linux.preset 工作示例。
/etc/mkinitcpio.d/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="esp/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="esp/EFI/Linux/arch-linux-fallback.efi"
fallback_options="-S autodetect"
PRESET_uki 参数以前被称为 PRESET_efi_image,于 2022 年 11 月更改 (参见 archlinux/mkinitcpio/mkinitcpio#134),旧参数已被弃用但目前仍可用。pacman hook
对 systemd-stub (属于 systemd)、微码 (包括 intel-ucode 和 amd-ucode) 以及 linux 内核的更新将自动触发 UKI 重建。但是,您可能需要查看 /etc/pacman.d/hooks/ 目录中的其他 pacman hooks,例如用于 NVIDIA 驱动程序 的。
构建 UKI
最后,请确保 UKI 的目录存在并 重新生成 initramfs。例如,对于 linux 预设
# mkdir -p esp/EFI/Linux # mkinitcpio -p linux
可选地,删除 /boot 或 /efi 中任何剩余的 initramfs-*.img 文件。
kernel-install
Kernel-install 是 systemd 的一部分,需要 systemd-ukify 来构建统一内核镜像。请确保 kernel-install 已正确 设置。
要生成 UKI,请安装 systemd-ukify 并将 kernel-install 的 layout 设置为 uki
/etc/kernel/install.conf
layout=uki
对 #ukify 的任何配置都必须在 /etc/kernel/uki.conf 中完成,才能被 kernel-install 使用,例如:
/etc/kernel/uki.conf
[UKI] Splash=/usr/share/systemd/bootctl/splash-arch.bmp
/usr/lib/kernel/uki.conf 复制到 /etc/kernel/uki.conf 并取消注释部分标题和有用的行。不要在此文件中设置内核命令行,它将被忽略。请使用 Kernel-install#Kernel command line。或者,为了让 mkinitcpio 生成 UKI,请将其设置为默认的 uki_generator
/etc/kernel/install.conf
layout=uki uki_generator=mkinitcpio
在这种情况下,不需要 systemd-ukify。您也可以设置一个不同的 initrd_generator,请参见 kernel-install(8)。
重新安装您使用的内核包,以便更改生效。
dracut
有关 dracut#Unified kernel image 和 dracut#Generate a new initramfs on kernel upgrade,请参阅。
ukify
安装 systemd-ukify 包。由于 ukify 无法自行生成 initramfs,如果需要,必须使用,例如,dracut、mkinitcpio 或 booster 来生成。
一个最小的工作示例可能看起来像这样:
# ukify build --linux=/boot/vmlinuz-linux \
--initrd=/boot/initramfs-linux.img \
--cmdline="quiet rw"
/boot/amd-ucode.img 或 /boot/intel-ucode.img 必须始终放在主 initramfs 镜像的前面。例如 --initrd=/boot/intel-ucode.img --initrd=/boot/initramfs-linux.img。- 为了跳过将生成的 EFI 可执行文件复制到 EFI 系统分区的过程,请在 ukify 命令中使用
--output=esp/EFI/Linux/filename.efi命令行选项。 - 指定
--cmdline选项时,可以通过在文件名之前添加@符号来指定一个文件名来读取内核参数 (例如/etc/kernel/cmdline,如--cmdline=@/path/to/cmdline。
有关更多信息,请参阅 ukify(1)。
手动设置
将您想使用的内核命令行放入一个文件中,并使用 objcopy(1) 创建捆绑文件。
对于 微码,首先将微码文件和您的 initramfs 连接起来,如下所示:
$ cat esp/cpu_manufacturer-ucode.img esp/initramfs-linux.img > /tmp/combined_initramfs.img
构建统一内核镜像时,将 /tmp/combined_initramfs.img 作为 initramfs 传入。此文件之后可以删除。
/usr/lib/systemd/boot/efi/linuxx64.efi.stub 替换为 /usr/lib/systemd/boot/efi/linuxia32.efi.stub。$ align="$(objdump -p /usr/lib/systemd/boot/efi/linuxx64.efi.stub | awk '{ if ($1 == "SectionAlignment"){print $2} }')"
$ align=$((16#$align))
$ osrel_offs="$(objdump -h "/usr/lib/systemd/boot/efi/linuxx64.efi.stub" | awk 'NF==7 {size=strtonum("0x"$3); offset=strtonum("0x"$4)} END {print size + offset}')"
$ osrel_offs=$((osrel_offs + "$align" - osrel_offs % "$align"))
$ cmdline_offs=$((osrel_offs + $(stat -Lc%s "/usr/lib/os-release")))
$ cmdline_offs=$((cmdline_offs + "$align" - cmdline_offs % "$align"))
$ splash_offs=$((cmdline_offs + $(stat -Lc%s "/etc/kernel/cmdline")))
$ splash_offs=$((splash_offs + "$align" - splash_offs % "$align"))
$ initramfs_offs=$((splash_offs + $(stat -Lc%s "/usr/share/systemd/bootctl/splash-arch.bmp")))
$ initramfs_offs=$((initramfs_offs + "$align" - initramfs_offs % "$align"))
$ linux_offs=$((initramfs_offs + $(stat -Lc%s "initramfs-file")))
$ linux_offs=$((linux_offs + "$align" - linux_offs % "$align"))
$ objcopy \
--add-section .osrel="/usr/lib/os-release" --change-section-vma .osrel=$(printf 0x%x $osrel_offs) \
--add-section .cmdline="/etc/kernel/cmdline" \
--change-section-vma .cmdline=$(printf 0x%x $cmdline_offs) \
--add-section .splash="/usr/share/systemd/bootctl/splash-arch.bmp" \
--change-section-vma .splash=$(printf 0x%x $splash_offs) \
--add-section .initrd="initramfs-file" \
--change-section-vma .initrd=$(printf 0x%x $initramfs_offs) \
--add-section .linux="vmlinuz-file" \
--change-section-vma .linux=$(printf 0x%x $linux_offs) \
"/usr/lib/systemd/boot/efi/linuxx64.efi.stub" "linux.efi"
几点注意事项
- 偏移量是动态计算的,因此没有节重叠,正如 [1] 中建议的那样。
- 节被对齐到 PE 存根的
SectionAlignment字段 (通常是 0x1000)。 - 内核镜像必须放在最后一个节,以防止就地解压缩覆盖后续节,正如 [2] 中所述。
创建镜像后,将其复制到 EFI 系统分区
# cp linux.efi esp/EFI/Linux/
为安全启动签名 UKI
sbctl
sbctl 提供了一个 kernel-install 脚本、一个 mkinitcpio 后钩,以及用于签名更新二进制文件的 pacman hooks。
mkinitcpio
通过使用 mkinitcpio post hook,生成的统一内核镜像可以被签名以用于 安全启动。 创建 以下文件并使其 可执行:
/etc/initcpio/post/uki-sbsign
#!/usr/bin/env bash
uki="$3"
[[ -n "$uki" ]] || exit 0
keypairs=(/path/to/db.key /path/to/db.crt)
for (( i=0; i<${#keypairs[@]}; i+=2 )); do
key="${keypairs[$i]}" cert="${keypairs[(( i + 1 ))]}"
if ! sbverify --cert "$cert" "$uki" &>/dev/null; then
sbsign --key "$key" --cert "$cert" --output "$uki" "$uki"
fi
done
将 /path/to/db.key 和 /path/to/db.crt 替换为您要用于签名镜像的密钥对的路径。
ukify
要使用 systemd-sbsign(1) 自动签名 UKI,请在您的配置文件中设置以下内容:
/etc/kernel/uki.conf
[UKI] SecureBootSigningTool=systemd-sbsign SignKernel=true SecureBootPrivateKey=/etc/kernel/secure-boot-private-key.pem SecureBootCertificate=/etc/kernel/secure-boot-certificate.pem
引导
Limine
Limine 不会自动检测统一内核镜像 (UKI)。但是,可以在 limine.conf 中手动配置以加载它们。
示例 1: 从默认 EFI 系统分区引导 UKI
如果 UKI 文件存储在 esp/EFI/Linux/ 中,请将以下配置添加到 limine.conf:
limine.conf
/Arch Linux protocol: efi path: boot():/EFI/Linux/arch-linux.efi
示例 2: 从另一个分区引导 UKI
如果 UKI 文件位于另一个 FAT32 分区上,请使用 guid(PARTUUID) 和 PARTUUID 代替:
limine.conf
/Arch Linux protocol: efi path: guid(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx):/EFI/Linux/arch-linux.efi
有关支持的路径和配置选项的更多详细信息,请参阅 Limine Paths documentation。
systemd-boot
systemd-boot 在 esp/EFI/Linux/ 中搜索统一内核镜像,无需进一步配置。请参阅 sd-boot(7) § FILES
rEFInd
rEFInd 将自动检测 EFI 系统分区上的统一内核镜像,并能够加载它们。也可以在 refind.conf 中手动指定它们,默认位置为:
esp/EFI/refind/refind.conf
menuentry "Arch Linux" {
icon \EFI\refind\icons\os_arch.png
ostype Linux
loader \EFI\Linux\arch-linux.efi
}
请记住,通过这种方式引导时,来自 esp/EFI/refind_linux.conf 的内核参数将不会传递。如果 UKI 在没有 .cmdline 部分的情况下生成,请在菜单条目中使用 options 行指定内核参数。
GRUB
GRUB 可以按照 GRUB#Chainloading a unified kernel image 中的描述链式加载 UKI。
直接从 UEFI 启动
efibootmgr 可用于为 .efi 文件创建 UEFI 引导条目:
# efibootmgr --create --disk /dev/sdX --part partition_number --label "Arch Linux" --loader '\EFI\Linux\arch-linux.efi' --unicode
有关选项的解释,请参阅 efibootmgr(8)。
\ 作为路径分隔符,但 *efibootmgr* 可以自动转换 UNIX 风格的 / 路径分隔符。