1

GRUB2 menu and kernel-install

 2 years ago
source link: https://diabloneo.github.io//2022/05/23/GRUB2-menu-and-kernel-install/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

GRUB2 menu and kernel-install

May 23, 2022

BLS: Boot Loader Specification

在新的发行版上,普遍采用了 GRUB2 (以下简称 GRUB) 作为 boot loader。GRUB2 现在采用了 Boot Loader Specification (简称 BLS) 来管理启动菜单: https://systemd.io/BOOT_LOADER_SPECIFICATION/

在非 UEFI Secure 启动的情况下,启动菜单直接使用文本文件保存,方便维护。你可以在一个使用该标准的系统上查看这些文件,例如 Rocky Linux 8.5:

# ll /boot/loader/entries/
total 12
-rw-r--r--. 1 root root 405 May 10 13:54 a0284538aa5b498cb38b8e530b2a6be4-0-rescue.conf
-rw-r--r--. 1 root root 353 May 10 13:54 a0284538aa5b498cb38b8e530b2a6be4-4.18.0-348.el8.0.2.x86_64.conf

# cat a0284538aa5b498cb38b8e530b2a6be4-4.18.0-348.el8.0.2.x86_64.conf
title Rocky Linux (4.18.0-348.el8.0.2.x86_64) 8.5 (Green Obsidian)
version 4.18.0-348.el8.0.2.x86_64
linux /vmlinuz-4.18.0-348.el8.0.2.x86_64
initrd /initramfs-4.18.0-348.el8.0.2.x86_64.img $tuned_initrd
options $kernelopts $tuned_params
id rocky-20211114010422-4.18.0-348.el8.0.2.x86_64
grub_users $grub_users
grub_arg --unrestricted
grub_class kernel

Machine ID

Machine ID 是一个在系统安装的时候生成的 UUID,用于表示系统的唯一性。它存放在 /etc/machine-id 文件中

# cat /etc/machine-id
a0284538aa5b498cb38b8e530b2a6be4

可以查看 man 手册获取更多信息: man machine-id

GRUB 菜单的生成

GRUB 菜单是在 kernel 安装的时候生成的,在 kernel 卸载的时候删除的,这整个过程是由 kernel-install 命令来完成的。在 RHEL 8 及衍生系统上,这个程序由 systemd-udev 包来提供。

kernel-install

这个程序只有两个功能,添加内核,和删除内核:

# kernel-install --help
Usage:
        /usr/bin/kernel-install add KERNEL-VERSION KERNEL-IMAGE
        /usr/bin/kernel-install remove KERNEL-VERSION

它本身是通过一系列的脚本来实现的,这些脚本存放在:

  • /usr/lib/kernel/install.d/
  • /etc/kernel/install.d/

默认脚本存放在 /usr/lib/kernel/install.d 目录下:

[root@overlord-sz3 install.d]# cd /usr/lib/kernel/install.d/
[root@overlord-sz3 install.d]# ll
total 40
-rwxr-xr-x. 1 root root 7120 Nov 14  2021 20-grub.install
-rwxr-xr-x. 1 root root 2252 Nov  9  2021 20-grubby.install
-rwxr-xr-x. 1 root root  368 Jun 22  2018 50-depmod.install
-rwxr-xr-x. 1 root root 1657 Nov  9  2021 50-dracut.install
-rwxr-xr-x. 1 root root 3338 Nov  9  2021 51-dracut-rescue.install
-rwxr-xr-x. 1 root root  791 Oct 13  2021 60-kdump.install
-rwxr-xr-x. 1 root root 1975 Nov  9  2021 90-loaderentry.install
-rwxr-xr-x. 1 root root  989 Jul 22  2021 92-tuned.install
-rwxr-xr-x. 1 root root  454 Nov 14  2021 99-grub-mkconfig.install

这些都是 shell 脚本,主要是完成一些配置。例如 20-grub.install90-loaderentry.install 这两个脚本就是完成 grub 相关的一些配置。

另外,kernel-install 对于这些脚本返回值做了一个特殊的规定:

An executable should return 0 on success. It may also return 77 to cause the whole operation to terminate (executables later in lexical order will be skipped).

所以,如果一个脚本返回了 77,那么后续的都会被跳过。

生成菜单的脚本

在 RHEL 8 及其衍生系统中,默认安装的 kernel-install 脚本里有两个脚本会负责 GRUB 菜单的生成:

如果你观察上面所展示的菜单文件的内容,并且对比这两个脚本,那么你就会发现他们是由 20-grub.install 这个文件生成的。为什么嗯?

这个是两个脚本的实现问题,首先 20-grub.install 脚本会被先执行,并且生成了 entries 目录中的文件。等到 90-loaderentry.install 被执行时,它有一个判断如下:

if ! [[ -d "$BOOT_DIR_ABS" ]]; then
    exit 0
fi

这个 BOOT_DIR_ABS 如果不是一个目录,就不会执行。这个变量如何定义呢?它在这里定义:https://github.com/systemd/systemd/blob/v239-50/src/kernel-install/kernel-install#L92

if ! [[ $MACHINE_ID ]]; then
    BOOT_DIR_ABS=$(mktemp -d /tmp/kernel-install.XXXXX) || exit 1
    trap "rm -rf '$BOOT_DIR_ABS'" EXIT INT QUIT PIPE
elif [[ -d /efi/loader/entries ]] || [[ -d /efi/$MACHINE_ID ]]; then
    BOOT_DIR_ABS="/efi/$MACHINE_ID/$KERNEL_VERSION"
elif [[ -d /boot/loader/entries ]] || [[ -d /boot/$MACHINE_ID ]]; then
    BOOT_DIR_ABS="/boot/$MACHINE_ID/$KERNEL_VERSION"                           # ------------- This line
elif [[ -d /boot/efi/loader/entries ]] || [[ -d /boot/efi/$MACHINE_ID ]]; then
    BOOT_DIR_ABS="/boot/efi/$MACHINE_ID/$KERNEL_VERSION"
elif mountpoint -q /efi; then
    BOOT_DIR_ABS="/efi/$MACHINE_ID/$KERNEL_VERSION"
elif mountpoint -q /boot/efi; then
    BOOT_DIR_ABS="/boot/efi/$MACHINE_ID/$KERNEL_VERSION"
else
    BOOT_DIR_ABS="/boot/$MACHINE_ID/$KERNEL_VERSION"
fi

/boot/$MACHINE_ID/$KERNEL_VERSION 这个形式,是 BLS 规范定义的新的形式,即每个系统的启动项都放在自己的独立目录中。因为现在 RHEL 8 及其衍生版本还未用到这个规范,所以他们的 GRUB 菜单项就不是用 90-loaderentry.install 这个脚本生成的。

一个菜单文件的规范涉及两个部分:文件名和内容。

一般来说,文件名类似:a0284538aa5b498cb38b8e530b2a6be4-4.18.0-348.el8.0.2.x86_64.conf。这个文件名主要的限制是必须跨系统唯一,所以一般使用如下格式: {machine_id}-{kernel_version}.conf

文件的内容可能如下:

title Rocky Linux (4.18.0-348.el8.0.2.x86_64) 8.5 (Green Obsidian)
version 4.18.0-348.el8.0.2.x86_64
linux /vmlinuz-4.18.0-348.el8.0.2.x86_64
initrd /initramfs-4.18.0-348.el8.0.2.x86_64.img $tuned_initrd
options $kernelopts $tuned_params
id rocky-20211114010422-4.18.0-348.el8.0.2.x86_64
grub_users $grub_users
grub_arg --unrestricted
grub_class kernel

每行第一个空格之前的就是 key,后面则是 value。其中,title 就是展示在菜单上的内容。这个 title 就是纯文本,所以你可以随意修改。

20-grub.install 文件可以看出,这里的 title 的内容会使用到来自 /etc/os-release 中的 NAME 变量和 VERSION 变量。

# cat /etc/os-release
NAME="Rocky Linux"
VERSION="8.5 (Green Obsidian)"
ID="rocky"
ID_LIKE="rhel centos fedora"
VERSION_ID="8.5"
PLATFORM_ID="platform:el8"
PRETTY_NAME="Rocky Linux 8.5 (Green Obsidian)"
ANSI_COLOR="0;32"
CPE_NAME="cpe:/o:rocky:rocky:8.5:GA"
HOME_URL="https://rockylinux.org/"
BUG_REPORT_URL="https://bugs.rockylinux.org/"
ROCKY_SUPPORT_PRODUCT="Rocky Linux"
ROCKY_SUPPORT_PRODUCT_VERSION="8"

grub_ 开头的 key,是 GRUB 平台实现的:

  • On grub platforms, the following grub-specific keywords have been implemented:
    • the BLS filename is also used for menuentry’s –id parameter, so you can use it in saved_entry
    • grub_hotkey - same as grub’s “–hotkey” menuentry parameter
    • grub_users - same as grub’s “–users” menuentry parameter; used for password protection
    • grub_class - same as grub’s “–class” menuentry paramter
    • grub_arg - passes extra arguments to menuentry

这些是兼容原来 GRUB 的参数:https://www.gnu.org/software/grub/manual/grub/grub.html#menuentry

默认启动项

你可以使用 grub2-set-default 命令修改默认的启动项。

此外,我们还可以在安装了一个新的 kernel 之后,让 kernel-install 自动将这个新的 kernel 设置为默认 kernel。这个行为的配置,是在文件 /etc/sysconfig/kernel 文件中控制的:

# cat /etc/sysconfig/kernel
# UPDATEDEFAULT specifies if kernel-install should make
# new kernels the default
UPDATEDEFAULT=yes

# DEFAULTKERNEL specifies the default kernel package type
DEFAULTKERNEL=kernel-core

这个注释已经解释得很清楚了。这部分逻辑的实现,也是在 20-grub.install 中实现的,调用的是 grub2-editenv 命令:https://git.rockylinux.org/staging/rpms/grub2/-/blob/r8/SOURCES/20-grub.install#L134

顺便说一下,如果你安装的 kernel 采用了另外一个名字来打包,比如 elrepo 的 kernel-lt,那么修改 DEFAULTKERNEL 这行即可。

Reference


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK