Unified kernel image - ArchWiki

Jump to content

A unified kernel image (UKI) is a single executable which can be booted directly from UEFI firmware, or automatically sourced by boot-loaders with little or no configuration.

Although Arch supported kernels themselves can be loaded by UEFI firmware, a unified image allows to incorporate all or a subset of the following:

- a UEFI stub loader like systemd-stub(7),
- the kernel command line,
- microcode,
- an initramfs image,
- a kernel image,
- a splash screen.

The resulting executable, and therefore all these elements can then be easily signed for use with Secure Boot.

Note: In the entire article `esp` denotes the mountpoint of the EFI system partition.

## Preparing a unified kernel image

### mkinitpcio

One can test the feature by running as an example

    # mkdir -p esp/EFI/Linux
    # mkinitcpio -p linux -- --uefi esp/EFI/Linux/test-systemd.efi

This would produce a kernel image for the linux preset.

#### Kernel command line

First, create `/etc/kernel/cmdline` with your kernel parameters, taking care to remove entries pointing at microcode and initramfs, e.g.

    /etc/kernel/cmdline

    rw quiet bgrt_disable

Tip: The `bgrt_disable` parameter tells Linux to not display the OEM logo after loading the ACPI tables.

#### .preset file

Next modify `/etc/mkinitcpio.d/linux.preset`, or the preset that you are using, as follows, with the appropriate mount point of the EFI system partition :

- If your system requires Microcode, add `ALL_microcode=(/boot/*-ucode.img)`,
- Add a `PRESET_efi_image=` parameter for each item in `PRESETS=`,
- Optionally, append a `--splash` parameter to each `PRESET_options=` line for which you want to add a splash image.

Here is a working example `linux.preset` for the linux kernel and the Arch splash screen.

    /etc/mkinitcpio.d/linux.preset

    # mkinitcpio preset file for the 'linux' package

    ALL_config="/etc/mkinitcpio.conf"
    ALL_kver="/boot/vmlinuz-linux"
    ALL_microcode="/boot/*-ucode.img"

    PRESETS=('default' 'fallback')

    default_image="/boot/initramfs-linux.img"
    default_efi_image="esp/EFI/Linux/archlinux-linux.efi"
    default_options="--splash /usr/share/systemd/bootctl/splash-arch.bmp"

    fallback_image="/boot/initramfs-linux-fallback.img"
    fallback_efi_image="esp/EFI/Linux/archlinux-linux-fallback.efi"
    fallback_options="-S autodetect"

This second example builds a default image for linux and a fallback image for linux-lts :

    /etc/mkinitcpio.d/linux.preset

    ALL_config="/etc/mkinitcpio.conf"
    ALL_microcode="/boot/*-ucode.img"

    PRESETS=('default' 'fallback')

    default_kver="/boot/vmlinuz-linux"
    default_image="/boot/initramfs-linux.img"
    default_efi_image="esp/EFI/Linux/archlinux-linux.efi"
    default_options="--splash /usr/share/systemd/bootctl/splash-arch.bmp"

    fallback_kver="/boot/vmlinuz-linux-lts"
    fallback_image="/boot/initramfs-linux-lts-fallback.img"
    fallback_efi_image="esp/EFI/Linux/archlinux-linux-lts-fallback.efi"
    fallback_options="-S autodetect"

Tip:

- If the ESP is mounted to `/boot`, make sure it has enough space, as mkinitcpio currently builds both the `initramfs-*.img` and the `*.efi` executables, thereby doubling the space used (see [1]).
- 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.
- You can append `--cmdline /etc/kernel/fallback_cmdline` to `fallback_options` to use different a different cmdline than above for the fallback image (e.g. without `quiet`)

Finally, regenerate the initramfs.

### dracut

See dracut#Unified kernel image and dracut#Generate a new initramfs on kernel upgrade.

### sbctl

Install the sbctl package. Store the kernel command line in `/etc/kernel/cmdline`. Use the `sbctl bundle` command with the `--save` parameter to create a bundle and have it be regenerated by a Pacman hook at appropriate times:

    # sbctl bundle --save esp/archlinux.efi

To create more EFI binaries for other kernels and initramfs images, repeat the above command with parameters `--kernel-img` and `--initramfs`, see sbctl(8) § EFI BINARY COMMANDS. The EFI binaries can be regenerated at any time with `sbctl generate-bundles`.

### Manually

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.

    $ stub_line=$(objdump -h "/usr/lib/systemd/boot/efi/linuxx64.efi.stub" | tail -2 | head -1)
    $ stub_size=0x$(echo "$stub_line" | awk '{print $3}')
    $ stub_offs=0x$(echo "$stub_line" | awk '{print $4}')
    $ osrel_offs=$((stub_size + stub_offs))
    $ cmdline_offs=$((osrel_offs + $(stat -c%s "/usr/lib/os-release")))
    $ splash_offs=$((cmdline_offs + $(stat -c%s "/etc/kernel/cmdline")))
    $ linux_offs=$((splash_offs + $(stat -c%s "/usr/share/systemd/bootctl/splash-arch.bmp")))
    $ initrd_offs=$((linux_offs + $(stat -c%s "vmlinuz-file")))
    $ 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 .linux="vmlinuz-file" \
        --change-section-vma .linux=$(printf 0x%x $linux_offs) \
        --add-section .initrd="initrd-file" \
        --change-section-vma .initrd=$(printf 0x%x $initrd_offs) \
        "/usr/lib/systemd/boot/efi/linuxx64.efi.stub" "linux.efi"

The offsets are simply calculated so no sections overlap, as recommended in [2].

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

    # cp linux.efi esp/EFI/Linux/

## Booting

### systemd-boot

systemd-boot searches in `esp/EFI/Linux/` for unified kernel images, and there is no further configuration needed. See sd-boot(7) § FILES

### rEFInd

rEFInd will autodetect unified kernel images on your EFI system partition, and is capable of loading them. They can also be manually specified in `refind.conf`, by default located at:

    /boot/EFI/refind/refind.conf

    menuentry Linux {
        loader esp/EFI/Linux/archlinux-linux.efi
    }

If the image is at the root of the ESP, rEFInd only requires its name, as follows: `loader archlinux-linux.efi`. Recall that no kernel parameters from `esp/EFI/refind_linux.conf` will be passed when booting this way.

### Directly from UEFI

efibootmgr can be used to create a UEFI boot entry for the .efi file:

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

See efibootmgr(8) for an explanation of the options.

Note: If `options` is present in a boot entry and Secure Boot is disabled, the value of `options` will override any `.cmdline` string embedded in the EFI image that is specified by `efi` or `linux` (see #Preparing a unified kernel image). With Secure Boot, however, `options` (and any edits made to the kernel command line in the bootloader UI) will be ignored, and only the embedded `.cmdline` will be used.

## See also

- Unified kernel image specification
- mkinitcpio v31 and UEFI stubs

Category:

- Boot loaders