統一內核映像(Unified Kernel Image,UKI)是一個可執行文件,可直接從 UEFI 固件進行啟動,也可以無需或通過簡單的配置由引導加載器自動生成。它將一個 UEFI boot stub 程序(例如 systemd-stub(7))、一個 Linux 內核映像、一個 initrd 以及其它資源打包到了單個 UEFI PE 文件中。
該文件,也就是包括其下的所有元件都可以很方便地進行簽名,以便與安全啟動一起使用。
esp
指代 EFI 系統分區的掛載點。準備統一內核映像
有多種方法可以生成 UKI 映像,並將其安裝到正確位置下(esp/Linux 目錄)。有多種工具都能完成該操作,可根據你的需要和喜好進行選擇。
- 你只需要完成下面任一種選項。
- 可以將 UKI 放置到默認啟動路徑
esp/EFI/BOOT/BOOTx64.EFI
(對於 32 位 IA32 UEFI 固件為BOOTIA32.EFI
)。使用默認啟動路徑無需在 NVRAM 顯式創建啟動項。
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-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.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"
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/
下如用於 NVIDIA 驅動的其它 pacman 鉤子。
構建 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。也可以通過配置默認 uki_generator
來使用 mkinitcpio 生成 UKI:
/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 系統分區的步驟,可對 ukify 使用
--output=esp/EFI/Linux/filename.efi
命令行選項。 - 在使用
--cmdline
選項時,可在文件名前使用@
符號來指定內核參數來源文件(以/etc/kernel/cmdline
為例,使用--cmdline=@/path/to/cmdline
)
更多信息請參考 ukify(1)。
手動構建
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.
/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
。
啟動
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 文檔。
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
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)。
\
作為路徑分隔符,但 efibootmgr 可以自動轉換 UNIX 風格的 /
路徑分隔符。