统一内核映像

来自 Arch Linux 中文维基
(重定向自统一内核镜像

统一内核映像(Unified Kernel Image,UKI)是一个可执行文件,可直接从 UEFI 固件进行启动,也可以无需或通过简单的配置由引导加载器自动生成。它将一个 UEFI boot stub 程序(例如 systemd-stub(7))、一个 Linux 内核映像、一个 initrd 以及其它资源打包到了单个 UEFI PE 文件中。

该文件,也就是包括其下的所有元件都可以很方便地进行签名,以便与安全启动一起使用。

注意: 在该文中,esp 指代 EFI 系统分区的挂载点。

准备统一内核映像[编辑 | 编辑源代码]

有多种方法可以生成 UKI 映像,并将其安装到正确位置下(esp/Linux 目录)。有多种工具都能完成该操作,可根据你的需要和喜好进行选择。

注意: 你只需要完成下面任一种选项。

mkinitcpio[编辑 | 编辑源代码]

除非安装了 systemd-ukify,否则 mkinitcpio 会自行构建 UKI。在安装了 systemd-ukify 的情况下,除非指定了 --no-ukify 选项,否则构建 UKI 的工作会被下放给 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#挂载子卷为根挂载点
  • rootflags 仅用于启动阶段,因此不需要将 /etc/fstab 中的全部挂载选项都复制过来。Systemd 会在启动后自动读取 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.preset 示例,用于 linux 内核并包含 Arch 启动画面:

/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"
提示:
  • If all you want to do is boot from the unified kernel images, you can mount the ESP to /efi and only those need to reside on the ESP partition.
  • 可以通过添加 --cmdline /etc/kernel/fallback_cmdlinefallback_options 以对后备映像使用不同的 cmdline(例如不带 quiet 参数)。
  • 要省略嵌入内核命令行,请在 PRESET_options= 中添加 --no-cmdline 参数。内核参数将需要通过引导加载器传入。
注意: PRESET_uki 选项之前被称作 PRESET_efi_image,于 2022 年 11 月变更(参见 archlinux/mkinitcpio/mkinitcpio#134);旧选项已被废弃,但现在尚能正常使用。

pacman 钩子[编辑 | 编辑源代码]

更新 systemd-stub(systemd 的一部分),微码(包括 intel-ucodeamd-ucode)及 linux 内核会自动重新构建 UKI,但你可能会想检查下 /etc/pacman.d/hooks/ 下如用于 NVIDIA 驱动的其它 pacman 钩子

构建 UKI[编辑 | 编辑源代码]

最后,确保用于 UKI 的目录存在,并重新生成 initramfs。例如,对于 linux预设:

# mkdir -p esp/EFI/Linux
# mkinitcpio -p linux

另外,可以移除 /boot/efi 下多余的 initramfs-*.img

kernel-install[编辑 | 编辑源代码]

首先确保已正确配置了 kernel-install。

要生成 UKI,需要安装 systemd-ukify,然后将 kernel-installlayout 设为 uki

/etc/kernel/install.conf
layout=uki
注意: mkinitcpio 会负责创建 initrd,然后d #ukify 会进一步生成 UKI。

#ukify 进行的任何配置都必须在 /etc/kernel/uki.conf 中完成,才能被 kernel-install 使用,例如:


/etc/kernel/uki.conf
[UKI]
Splash=/usr/share/systemd/bootctl/splash-arch.bmp
注意: 不要在该文件中设置命令行参数,否则设定的参数将被忽略。请参考 Kernel-install#Kernel command line

也可以通过配置默认 uki_generator 来使用 mkinitcpio 生成 UKI:

/etc/kernel/install.conf
layout=uki
uki_generator=mkinitcpio

在这种情况下可以不使用 systemd-ukify。你也可以使用其它 initrd_generator,具体信息请参考 kernel-install(8)

为了使配置生效,需要重新安装当前使用的内核软件包。

dracut[编辑 | 编辑源代码]

请参考 dracut#Unified kernel imagedracut#Generate a new initramfs on kernel upgrade

ukify[编辑 | 编辑源代码]

安装 systemd-ukify。如需使用自动签名功能,请一并安装 sbsigntools。由于 ukify 不能自行生成 initramfs,因此如有需求,需要单独通过 dracutmkinitcpiobooster 进行生成。

最小可用示例如下:

# ukify build --linux=/boot/vmlinuz-linux \
              --initrd=/boot/initramfs-linux.img \
              --cmdline="quiet rw"
注意: 如果使用了外置 initramfs 微码映像,必须将 /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)

注意: 要在内核/微码/initramfs 改变时自动更新 UKI,只需将 ukify 搭配如 kernel-installmkinitcpio 使用。具体细节请参考之前的 #kernel-install#mkinitcpio 章节。

手动构建[编辑 | 编辑源代码]

Put the kernel command line you want to use in a file, and create the bundle file using objcopy(1).

For microcode, first concatenate the microcode file and your initrd, as follows:

$ cat esp/cpu_manufacturer-ucode.img esp/initramfs-linux.img > /tmp/combined_initrd.img

When building the unified kernel image, pass in /tmp/combined_initrd.img as the initrd. This file can be removed afterwards.

注意: For IA32 UEFI, replace /usr/lib/systemd/boot/efi/linuxx64.efi.stub with /usr/lib/systemd/boot/efi/linuxia32.efi.stub in the following commands.
$ 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"

A few things to note:

  • The offsets are dynamically calculated so no sections overlap, as recommended in [1].
  • The sections are aligned to what the SectionAlignment field of the PE stub indicates (usually 0x1000).
  • The kernel image must be in the last section, to prevent in-place decompression from overwriting the sections that follow, as stated in [2].

After creating the image, copy it to the EFI system partition:

# cp linux.efi esp/EFI/Linux/

为安全启动对 UKI 进行签名[编辑 | 编辑源代码]

sbctl[编辑 | 编辑源代码]

sbctl provides a kernel-install script, a mkinitcpio post-hook, and pacman hooks to sign updated binaries.

mkinitcpio[编辑 | 编辑源代码]

By using a mkinitcpio post hook, the generated unified kernel images can be signed for Secure Boot. Create the following file and make it executable:

/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

Replace /path/to/db.key and /path/to/db.crt with the paths to the key pair you want to use for signing the image.

ukify[编辑 | 编辑源代码]

安装 sbsigntools,并在 /etc/kernel/uki.conf 中指定 --secureboot-private-key--secureboot-certificate

启动[编辑 | 编辑源代码]

注意: 当启用安全启动时,内嵌 .cmdline 的统一内核映像会忽略所有传入的命令行参数(无论是通过启动项还是交互式传入)。在不启用安全启动是,通过命令行传入的选项会绕过掉内嵌的 .cmdline

systemd-boot[编辑 | 编辑源代码]

systemd-boot 会在 esp/EFI/Linux/ 路径下搜索统一内核映像,无需额外配置。具体细节请参考 sd-boot(7) § FILES

rEFInd[编辑 | 编辑源代码]

rEFInd 会自动检测到 EFI 系统分区下的 UKI,并可以进行加载。也可以在 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#Chainload 一个统一的内核镜像

直接从 UEFI 启动[编辑 | 编辑源代码]

efibootmgr 可以为 .efi 文件创建 UEFI 启动项:

# efibootmgr --create --disk /dev/sdX --part partition_number --label "Arch Linux" --loader '\EFI\Linux\arch-linux.efi' --unicode

选项具体作用请参考 efibootmgr(8)

注意: UEFI 标准使用反斜杠\ 作为路径分隔符,但 efibootmgr 可以自动转换 UNIX 风格的 / 路径分隔符。

参阅[编辑 | 编辑源代码]