统一内核镜像
一个 统一内核镜像 (UKI) 是一个可以从 UEFI 固件直接启动的单个可执行文件,或由引导加载器自动加载,几乎无需或完全无需配置。它是 UEFI 启动存根程序(如 systemd-stub(7))、Linux 内核镜像、initrd 和 其他资源 在单个 UEFI PE 文件中的组合。
这个文件,以及因此所有的这些元素都可以很容易地被签名,以便用于安全启动。
esp
表示 EFI 系统分区 的挂载点。准备统一内核镜像
有几种方法可以生成 UKI 镜像并将其安装到正确的位置(esp/Linux
目录)。目前有几种工具都在竞争实现此功能,因此请根据您的需求和喜好选择以下方法之一。
- 您只需要执行以下小节中的一个。
- 可以将 UKI 放置在 后备启动路径
esp/EFI/BOOT/BOOTx64.EFI
(或 32 位 IA32 UEFI 的BOOTIA32.EFI
)中。使用后备启动路径可以避免显式在 NVRAM 中创建 UEFI 启动项的需求。
mkinitcpio
mkinitcpio 将自行组装 UKI,除非安装了 systemd-ukify。在这种情况下,UKI 创建将被卸载到 ukify,除非使用 --no-ukify
选项显式禁用此功能。
内核命令行
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#将子卷挂载为根目录。 - 没有必要复制
/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 徽标。
.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 钩子
systemd-stub(systemd 的一部分)、微代码(intel-ucode 和 amd-ucode)和 linux 内核的更新将自动触发 UKI 重建。但是您可能需要查看 /etc/pacman.d/hooks/
目录中的其他 pacman 钩子,例如 NVIDIA 驱动程序 的钩子。
构建 UKI
最后,确保 UKI 的目录存在,并重新生成 initramfs。例如,对于 linux 预设:
# mkdir -p esp/EFI/Linux # mkinitcpio -p linux
可选地,从 /boot
或 /efi
中删除任何遗留的 initramfs-*.img
。
kernel-install
确保 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#内核命令行。或者,为了让 mkinitcpio 生成 UKI,将其设置为默认的 uki_generator
/etc/kernel/install.conf
layout=uki uki_generator=mkinitcpio
在这种情况下,systemd-ukify 不是必需的。您还可以设置不同的 initrd_generator
,请参阅 kernel-install(8)。
重新安装您使用的内核软件包,以使更改生效。
dracut
请参阅 dracut#统一内核镜像 和 dracut#在内核升级时生成新的 initramfs。
ukify
安装 systemd-ukify 软件包。要使用自动签名功能,请额外安装 sbsigntools。由于 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 系统分区的步骤,请使用
--output=esp/EFI/Linux/filename.efi
命令行选项来运行 ukify。 - 当指定
--cmdline
选项时,可以指定一个文件名来从中读取内核参数(例如/etc/kernel/cmdline
,通过在文件名之前添加@
符号,如--cmdline=@/path/to/cmdline
)。
有关更多信息,请参阅 ukify(1)。
手动
将您想要使用的内核命令行放在一个文件中,并使用 objcopy(1) 创建捆绑文件。
对于 微代码,首先连接微代码文件和您的 initrd,如下所示:
$ cat esp/cpu_manufacturer-ucode.img esp/initramfs-linux.img > /tmp/combined_initrd.img
在构建统一内核镜像时,传入 /tmp/combined_initrd.img
作为 initrd。此文件可以在之后删除。
/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")) $ initrd_offs=$((splash_offs + $(stat -Lc%s "/usr/share/systemd/bootctl/splash-arch.bmp"))) $ initrd_offs=$((initrd_offs + "$align" - initrd_offs % "$align")) $ linux_offs=$((initrd_offs + $(stat -Lc%s "initrd-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="initrd-file" \ --change-section-vma .initrd=$(printf 0x%x $initrd_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 钩子。
mkinitcpio
通过使用 mkinitcpio 后期钩子,可以为 安全启动 签名生成的统一内核镜像。创建 以下文件并使其可执行:
/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
安装 sbsigntools 并在 /etc/kernel/uki.conf
中指定 --secureboot-private-key
和 --secureboot-certificate
。
启动
.cmdline
的统一内核镜像将忽略传递给它们的所有命令行选项(无论是使用启动项还是交互式方式)。当安全启动未激活时,通过命令行传递的选项将覆盖嵌入的 .cmdline
。Limine
Limine 不会自动检测统一内核镜像 (UKI)。但是,可以手动配置 limine.conf
来加载它们。
示例 1:从默认 EFI 系统分区启动 UKI
如果 UKI 文件存储在 esp/EFI/Linux/
中,请将以下配置添加到 limine.conf
:
limine.conf
/Arch Linux protocol: efi_chainload image_path: boot():/EFI/Linux/arch-linux.efi
示例 2:从另一个 EFI 分区启动 UKI
如果 UKI 文件位于另一个磁盘上的不同 EFI 分区上,请使用 uuid(分区 UUID)
代替。
PARTUUID
的分区 UUID,请使用:$ lsblk -o NAME,FSTYPE,PARTUUID,PARTTYPENAME,MOUNTPOINT,SIZE,LABEL
limine.conf
/Arch Linux protocol: efi_chainload image_path: uuid(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx):/EFI/Linux/arch-linux.efi
有关支持的路径和配置选项的更多详细信息,请参阅 Limine 路径文档。
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
与 rEFInd 类似,GRUB 可以链式加载 EFI UKI,如 GRUB#链式加载统一内核镜像 中所述。
直接从 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 样式的 /
路径分隔符。