跳转至内容

统一内核镜像 (Unified kernel image)

来自 ArchWiki
(重定向自 UKI)

一个 统一内核镜像 (UKI) 是一个可以直接从 UEFI 固件启动的可执行文件,或者能被引导加载程序自动加载,只需很少或无需配置。它结合了一个 UEFI 引导存根程序(例如 systemd-stub(7))、一个 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#挂载子卷作为根目录
  • 无需复制 /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"
警告 将内核(ALL_kver)放在未加密的文件系统上(例如,如果 EFI 系统分区 挂载在 /boot),会使其容易受到启动盘或其他操作系统在双启动时进行潜在的篡改。
提示
  • 如果您只想从统一内核镜像启动,您可以将 ESP 挂载到 /efi,并且只有这些镜像需要驻留在 ESP 分区上。
  • 您可以将 --cmdline /etc/kernel/fallback_cmdline 追加到 fallback_options,以使用与上面不同的 命令行 来生成备用镜像(例如,不带 quiet)。
  • 要省略嵌入内核命令行,请在 PRESET_options= 中添加 --no-cmdline。内核参数需要通过引导加载程序传递。
注意 PRESET_uki 选项以前称为 PRESET_efi_image,已于 2022 年 11 月更改(请参阅 archlinux/mkinitcpio/mkinitcpio#134),旧选项已被弃用但目前仍可工作。

pacman hook

对 systemd-stub(systemd 的一部分)、微码(包括 intel-ucodeamd-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-installsystemd 的一部分,它需要 systemd-ukify 来构建统一内核镜像。请确保 正确设置 kernel-install。

要生成 UKI,请安装 systemd-ukify 并将 kernel-installlayout 设置为 uki

/etc/kernel/install.conf
layout=uki
注意 默认情况下,mkinitcpio 会生成 initramfs,然后 #ukify 会将其包含在 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 包。由于 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 选项时,可以通过在文件名之前添加 @ 符号来指定一个文件名来读取内核参数(例如 --cmdline=@/path/to/cmdline)。

有关更多信息,请参阅 ukify(1)

注意 要在内核、微码或 initramfs 更改时自动更新 UKI,只需将 ukify 与例如 kernel-installmkinitcpio 一起使用。有关详细信息,请参阅前面的章节 #kernel-install#mkinitcpio

手动设置

将您想使用的内核命令行放入一个文件,然后使用 objcopy(1) 创建捆绑文件。

对于 微码,首先将微码文件与您的 initramfs 连接起来,如下所示:

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

构建统一内核镜像时,将 /tmp/combined_initramfs.img 作为 initramfs 传递。之后可以删除此文件。

注意 对于 IA32 UEFI,在以下命令中将 /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/

要使用 xen 创建 UKI,请参阅 [3]

为安全启动签署 UKI

sbctl

sbctl 提供了一个 kernel-install 脚本、一个 mkinitcpio post-hook 以及用于签名更新二进制文件的 pacman 钩子。

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
注意 目前使用 systemd-sbsign(1) 进行签名需要设置 SignKernel=true。请参阅 [4]

引导

  • 安全启动 处于活动状态时,带有嵌入式 .cmdline 的统一内核镜像将忽略传递给它们的任何命令行选项(无论是通过启动项还是交互式传递)。当安全启动不处于活动状态时,通过命令行传递的选项将覆盖嵌入的 .cmdline
  • 可以将 UKI 放置在 备用启动路径 esp/EFI/BOOT/BOOTx64.EFI(对于 32 位 IA32 UEFI,则为 BOOTIA32.EFI)。使用备用启动路径可以避免在 NVRAM 中显式创建 UEFI 启动项的需要。

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 路径文档

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)

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

参见

© . This site is unofficial and not affiliated with Arch Linux.

Content is available under GNU Free Documentation License 1.3 or later unless otherwise noted.