UEFI GRUBをちょっとだけ理解する回
前置き
この記事を開いた人は、UEFIとはなんぞや?GRUBって何してるの?という疑問を持った方だと思います。
そんな方々に、なんとなくこの記事を読んでふんわり理解していただくことを目標としています。
なお、環境はx86_64 のDebian 環境です。Ubuntu系でも同じコマンドで確認することは可能なはずです。
全体をざっくり掴む
Linuxが起動するまでの流れは以下のようになっています。
@startuml
title Linux Boot Sequence
hide empty description
left to right direction
state "UEFI Firmware" as UEFI : 1. Boot Entryを読む\n2. ESPを読む\n3. shimx64.efiを起動
state "GRUB" as GRUB : 1. grub.cfgを読む\n2. menuentryを表示\n3. kernel/initramfs/cmdlineを決める
state "Linux Kernel" as Kernel : 1. kernel起動\n2. initramfsを展開\n3. root=UUIDを探す
state "Root FS / systemd" as RootFS : 1. /dev/nvme0n1p2を/にmount\n2. systemd起動\n3. loginへ
UEFI --> GRUB
GRUB --> Kernel
Kernel --> RootFS
@enduml
この流れを構成している中身を追いかけてみます。
今回はUEFIとGRUBについて深堀りしていきます。
UEFI
UEFI(Unified Extensible Firmware Interface)は、PCに電源を入れた際に最初に起動し、HWの初期化とOSの起動を担うシステムです。
今でも習慣的にBIOSと呼ばれることもありますが、最近のPCでは実体としてはUEFI firmwareが使われていることが多いです。
Boot Entry
UEFIは起動後、まずBoot Entryを読みます。実際に見てみましょう。efibootmgr -vで見ることができます。
$ efibootmgr -v
BootCurrent: 0000
Timeout: 1 seconds
BootOrder: 0000,0001,0002,0003,0004
Boot0000* debian HD(1,GPT,af626011-632d-419a-8bdc-4ac700496055,0x800,0x1e8000)/File(\EFI\debian\shimx64.efi)
dp: 04 01 2a 00 01 00 00 00 00 08 00 00 00 00 00 00 00 80 1e 00 00 00 00 00 11 60 62 af 2d 63 9a 41 8b dc 4a c7 00 49 60 55 02 02 / 04 04 34 00 5c 00 45 00 46 00 49 00 5c 00 64 00 65 00 62 00 69 00 61 00 6e 00 5c 00 73 00 68 00 69 00 6d 00 78 00 36 00 34 00 2e 00 65 00 66 00 69 00 00 00 / 7f ff 04 00
Boot0001* UEFI: PXE IPv4 Realtek PCIe 2.5GBE Family Controller PciRoot(0x0)/Pci(0x2,0x1)/Pci(0x0,0x0)/Pci(0x3,0x0)/Pci(0x0,0x0)/MAC(9c6b009a81cd,0)/IPv4(0.0.0.00.0.0.0,0,0)0000424f
dp: 02 01 0c 00 d0 41 03 0a 00 00 00 00 / 01 01 06 00 01 02 / 01 01 06 00 00 00 / 01 01 06 00 00 03 / 01 01 06 00 00 00 / 03 0b 25 00 9c 6b 00 9a 81 cd 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 / 03 0c 1b 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 / 7f ff 04 00
data: 00 00 42 4f
Boot0002* UEFI:CD/DVD Drive BBS(129,,0x0)
dp: 05 01 09 00 81 00 00 00 00 / 7f ff 04 00
Boot0003* UEFI:Removable Device BBS(130,,0x0)
dp: 05 01 09 00 82 00 00 00 00 / 7f ff 04 00
Boot0004* UEFI:Network Device BBS(131,,0x0)
dp: 05 01 09 00 83 00 00 00 00 / 7f ff 04 00
先頭にあるBootCurrent: 0000という部分が今回起動に使ったBoot Entryです。
また、BootOrderは、UEFIがどのBoot Entryをどの順番で試すかを意味しています。
ここでBoot000Xとなっているのが、UEFI NVRAMに登録されているBoot Entryです。OSローダーを指すものもあれば、PXEやRemovable Deviceのような汎用的な起動候補もあります。
Boot0000は debian というBoot Entryで、メインのNVMe SSD上のESPにある \EFI\debian\shimx64.efi を指しています。結果として、このEntryからDebian環境が起動します。
Boot0001はPXE Boot用のBoot Entryです。ネットワーク越しにブートローダーやOSイメージを取得して起動させる方法です。そのためマザーボードについている2.5GbEが表示されています。OSの自動インストールなどにも使われます。
Boot0000をもう少し細かく見ると、HD(1,GPT,af626011-632d-419a-8bdc-4ac700496055,0x800,0x1e8000)/File(\EFI\debian\shimx64.efi)という箇所があります。
これがUEFIが最初に実行するEFIファイルを示しています。
本環境ではshimx64.efiを起動 --> grubx64.efiを起動、という流れなので、shimx64.efiがここに書かれています。
GRUB
GRUBは、起動時に黒い選択画面として表示されることが多い、Linux kernelを起動するためのブートローダーです。
GRUBが起動し、その後にLinux kernelが起動します。
GRUBは実際には以下のことをやっています。
1. grub.cfgを読み込む
2. menuentryを表示する
3. kernel / initramfs / kernel parametersを選ぶ
では、まずgrub.cfgを覗いてみましょう。grub.cfgは/boot/grub/grub.cfgにあります。
ただし、実際に見るなら情報を絞ったほうがいいでしょう。
$ sudo awk '
/^[[:space:]]*(menuentry|submenu) / {print "\n" NR ":" $0}
/^[[:space:]]*search .*--fs-uuid/ {print NR ":" $0}
/^[[:space:]]*linux[[:space:]]/ {print NR ":" $0}
/^[[:space:]]*initrd[[:space:]]/ {print NR ":" $0}
' /boot/grub/grub.cfg
63:search --no-floppy --fs-uuid --set=root 8d5dc983-1a78-4ee2-82fb-45b9e595677e
101:menuentry 'Debian GNU/Linux' --class debian --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-simple-8d5dc983-1a78-4ee2-82fb-45b9e595677e' {
107: search --no-floppy --fs-uuid --set=root 8d5dc983-1a78-4ee2-82fb-45b9e595677e
109: linux /boot/vmlinuz-6.12.95+deb13-amd64 root=UUID=8d5dc983-1a78-4ee2-82fb-45b9e595677e ro quiet amd_iommu=on iommu=pt
111: initrd /boot/initrd.img-6.12.95+deb13-amd64
113:submenu 'Advanced options for Debian GNU/Linux' $menuentry_id_option 'gnulinux-advanced-8d5dc983-1a78-4ee2-82fb-45b9e595677e' {
114: menuentry 'Debian GNU/Linux, with Linux 6.12.95+deb13-amd64' --class debian --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-6.12.95+deb13-amd64-advanced-8d5dc983-1a78-4ee2-82fb-45b9e595677e' {
120: search --no-floppy --fs-uuid --set=root 8d5dc983-1a78-4ee2-82fb-45b9e595677e
122: linux /boot/vmlinuz-6.12.95+deb13-amd64 root=UUID=8d5dc983-1a78-4ee2-82fb-45b9e595677e ro quiet amd_iommu=on iommu=pt
124: initrd /boot/initrd.img-6.12.95+deb13-amd64
126: menuentry 'Debian GNU/Linux, with Linux 6.12.95+deb13-amd64 (recovery mode)' --class debian --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-6.12.95+deb13-amd64-recovery-8d5dc983-1a78-4ee2-82fb-45b9e595677e' {
132: search --no-floppy --fs-uuid --set=root 8d5dc983-1a78-4ee2-82fb-45b9e595677e
134: linux /boot/vmlinuz-6.12.95+deb13-amd64 root=UUID=8d5dc983-1a78-4ee2-82fb-45b9e595677e ro single dis_ucode_ldr
136: initrd /boot/initrd.img-6.12.95+deb13-amd64
138: menuentry 'Debian GNU/Linux, with Linux 6.12.86+deb13-amd64' --class debian --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-6.12.86+deb13-amd64-advanced-8d5dc983-1a78-4ee2-82fb-45b9e595677e' {
144: search --no-floppy --fs-uuid --set=root 8d5dc983-1a78-4ee2-82fb-45b9e595677e
146: linux /boot/vmlinuz-6.12.86+deb13-amd64 root=UUID=8d5dc983-1a78-4ee2-82fb-45b9e595677e ro quiet amd_iommu=on iommu=pt
148: initrd /boot/initrd.img-6.12.86+deb13-amd64
150: menuentry 'Debian GNU/Linux, with Linux 6.12.86+deb13-amd64 (recovery mode)' --class debian --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-6.12.86+deb13-amd64-recovery-8d5dc983-1a78-4ee2-82fb-45b9e595677e' {
156: search --no-floppy --fs-uuid --set=root 8d5dc983-1a78-4ee2-82fb-45b9e595677e
158: linux /boot/vmlinuz-6.12.86+deb13-amd64 root=UUID=8d5dc983-1a78-4ee2-82fb-45b9e595677e ro single dis_ucode_ldr
160: initrd /boot/initrd.img-6.12.86+deb13-amd64
183: menuentry 'UEFI Firmware Settings' $menuentry_id_option 'uefi-firmware' {
少々わかりにくいですが、menuentryと、その時のsearch, linux, initrdを表示しています。
menuentry
menuentryは、GRUBの画面のときに選択する要素です。debianの6.12.86と6.12.95があり、それぞれにrecovery modeがありますね。
実際には画面を表示したままRebootすると、選択画面が一致しているのが分かるかと思います。
ちなみに、6.12.86はLinux kernelのバージョンです。Debian linuxのバージョンは13です。
search
searchでは、kernel image/initramfs imageを探す対象のfilesystemを示しています。
search --no-floppy --fs-uuid --set=root <UUID>みたいな感じで設定していますね。
今回の環境では /boot が独立パーティションではなく、root filesystem / の中にあるため、GRUBがkernel/initramfsを探す対象FSと、Linuxが / としてmountするroot filesystemが同じUUIDになっています。
なお、環境によっては /boot を独立パーティションにする場合もあります。たとえば、root filesystem が暗号化されている場合、LVM/RAID/ZFS/BtrfsなどGRUBから直接扱いにくい構成の場合、あるいはboot領域を明確に分けたい場合です。
ここで注意したいのは、GRUBが見ている root と、Linux起動後の / は別の概念だという点です。GRUBの root はkernel imageやinitramfs imageを探すための基準filesystemであり、Linuxのroot filesystemはkernel起動後に root=UUID=... に従ってmountされます。
linux
linux行では、GRUBが読み込むkernel imageのパスと、kernelへ渡すkernel parameterが指定されています。
このパスは直前のsearch --fs-uuid --set=root ...で設定されたGRUB上のroot filesystemから見たパスです。
実際に見てみると、確かにファイルが置かれています。
$ ls /boot
System.map-6.12.86+deb13-amd64 config-6.12.86+deb13-amd64 efi initrd.img-6.12.86+deb13-amd64 vmlinuz-6.12.86+deb13-amd64
System.map-6.12.95+deb13-amd64 config-6.12.95+deb13-amd64 grub initrd.img-6.12.95+deb13-amd64 vmlinuz-6.12.95+deb13-amd64
initrd
initrdも同じようにパスを指定しています。
Linuxではこのlinuxで指定したLinux kernelと、initrd行で指定したinitramfs imageを使って、システムを起動させていきます。
GRUB上のコマンド名は initrd ですが、現在のLinuxでは実体としてはinitramfs imageを指している、と理解しておけばよいです。
詳しくは次で説明します。
kernel parameterと/proc/cmdline
linux行の後半にあるroot=UUID=... ro quiet amd_iommu=on iommu=ptはLinux kernelへ渡されるkernel parameterです。
起動後には以下のように/proc/cmdlineから確認できます。
$ cat /proc/cmdline
BOOT_IMAGE=/boot/vmlinuz-6.12.95+deb13-amd64 root=UUID=8d5dc983-1a78-4ee2-82fb-45b9e595677e ro quiet amd_iommu=on iommu=pt
解説しておくと、以下のような意味合いです。
- BOOT_IMAGE: どのカーネルを起動したか、を示す。
- root=UUID:root filesystemはこのUUIDを使う、という命令。
- ro: 起動直後はRead OnlyでMountする。systemdなどが正常性チェックをした後に、rwに変更される。後で見るfindmntではrwになっている。つまり、ro -> fsck + rwという流れ。
- quiet: 起動メッセージを減らす、という意味。
- amd_iommu=on: AMD環境でIOMMUを有効化する指定。PCIe / DMA / VFIO周りを学ぶために自分で追加した設定
- iommu=pt: pass throughという意味。IOMMU自体は有効にしつつ、通常のデバイスではなるべく identity mapping / pass-through 的に扱う設定。ざっくり言うと、Device DMA address と Physical address が素通しに近い形になる、というイメージ。
蛇足:UUIDを見てみる
ところで、UUID自体は簡単に見ることができます。
$ ls -l /dev/disk/by-uuid/
合計 0
lrwxrwxrwx 1 root root 15 7月 12 14:45 74E2-37E0 -> ../../nvme0n1p1
lrwxrwxrwx 1 root root 15 7月 12 14:45 8d5dc983-1a78-4ee2-82fb-45b9e595677e -> ../../nvme0n1p2
lrwxrwxrwx 1 root root 15 7月 12 14:45 e57c56de-7494-49d0-80af-6400e24100ac -> ../../nvme0n1p3
$ lsblk -o NAME,FSTYPE,UUID,PARTUUID,MOUNTPOINTS
NAME FSTYPE UUID PARTUUID MOUNTPOINTS
nvme0n1
├─nvme0n1p1 vfat 74E2-37E0 af626011-632d-419a-8bdc-4ac700496055 /boot/efi
├─nvme0n1p2 ext4 8d5dc983-1a78-4ee2-82fb-45b9e595677e 7680da86-784e-4209-b378-32ee6e1e4845 /
└─nvme0n1p3 swap e57c56de-7494-49d0-80af-6400e24100ac d410038f-424a-4ba3-be3e-15abfac8df35 [SWAP]
$ sudo blkid
/dev/nvme0n1p3: UUID="e57c56de-7494-49d0-80af-6400e24100ac" TYPE="swap" PARTUUID="d410038f-424a-4ba3-be3e-15abfac8df35"
/dev/nvme0n1p1: UUID="74E2-37E0" BLOCK_SIZE="512" TYPE="vfat" PARTUUID="af626011-632d-419a-8bdc-4ac700496055"
/dev/nvme0n1p2: UUID="8d5dc983-1a78-4ee2-82fb-45b9e595677e" BLOCK_SIZE="4096" TYPE="ext4" PARTUUID="7680da86-784e-4209-b378-32ee6e1e4845"
よく見るとUUID / PART UUIDの2種類がありますが、GRUBで探すUUIDは左側にあるもので、PART UUIDはまた別物です。
また、/boot/efiのPartitionのUUIDはたったの8文字で他のPartitionより少ないですが、これはファイルシステムのフォーマットがvfatになっているからです。
UEFIが扱えるフォーマットは限られており、ext4では認識できないためこのような状態になっています。
なお、GRUBはext4を含むいくつかのフォーマットに対応しています。/boot/grub/x86_64-efi/*.modという名前でGRUBのモジュールが置かれており、そこから何が対応しているか探すこともできます。
まとめ
今回は以下のことを学びました。
- UEFIはPCに電源を入れた際に最初に起動し、HWの初期化とOSの起動を担うシステム
- GRUBなどのブートローダーを起動する。
- efimanager -vでどんなものがあるか確認できる。
- GRUBはLinux kernelを起動するためのブートローダー
- Linux kernelを起動させ、initramfsを展開する
- menuentryが選択できるBoot Entryで、それぞれにsearch, linux,initrdが登録されている。
- grub.cfgに詳細は書いてある。
UEFIは正直これ以上はLinux上では深く学ぶのは難しいと感じています。
GRUBはまだまだ触ってみる箇所がたくさんあります。気が向いたらConfig系などもいじってみたいと思います。
次回はinitramfsです。これが何を意味し、どんなことをしているのか、少しだけ掘り下げていきます。