Skip to content

GRUB

GNU GRUB (GRand Unified Bootloader) là bootloader mặc định của hầu hết bản phân phối Linux trên PC và server. Bản đang dùng là GRUB 2 (Fedora/RHEL gọi là grub2); GRUB Legacy (0.97) đã ngừng phát triển. Vị trí của bootloader trong chuỗi boot xem tại Bootloader trong hệ nhúng Linux.

GRUB trong hệ nhúng

Khác với U-Boot, GRUB không khởi tạo phần cứng: nó chạy sau firmware (BIOS hoặc UEFI), firmware đã cấu hình xong CPU, DRAM và cung cấp dịch vụ đọc disk, console. Vì vậy GRUB gần như không xuất hiện trên SoC ARM nhỏ, nhưng hay gặp trong các trường hợp:

  • Máy x86 nhúng / công nghiệp: IPC, gateway, thiết bị mạng dùng CPU Intel Atom, AMD Ryzen Embedded. Firmware UEFI có sẵn trên board.
  • ARM64 có UEFI: server ARM64 (EDK2), hoặc board nhúng mà U-Boot đóng vai trò firmware UEFI (lệnh bootefi). U-Boot nạp grubaa64.efi từ phân vùng EFI, GRUB nạp kernel. Đây là cách các bản phân phối "generic" (Debian, Fedora, Ubuntu) boot được trên nhiều board ARM64 bằng một image.
  • Thiết bị cần Secure Boot theo chuẩn UEFI, dùng chuỗi shim → GRUB → kernel được ký.
U-Boot GRUB
Khởi tạo phần cứng (clock, DDR) Có (SPL) Không, dựa vào firmware
Nền tảng ARM, RISC-V, MIPS, PowerPC... x86 BIOS/UEFI, ARM64/RISC-V UEFI
Cấu hình Environment + script grub.cfg (ngôn ngữ giống shell)
Định dạng kernel zImage, Image, uImage, FIT bzImage, Image (EFI stub)
Device tree Tự nạp và fixup Thường do firmware cung cấp; có lệnh devicetree
Secure Boot FIT signature (verified boot) UEFI Secure Boot qua shim, hoặc chữ ký GPG

Quá trình boot

BIOS (legacy)

 BIOS ──► boot.img (MBR, 446 byte) ──► core.img ──► modules + grub.cfg ──► kernel
  1. BIOS đọc sector đầu tiên (MBR) của disk. Phần code trong MBR là boot.img của GRUB, chỉ đủ để nạp sector đầu của core.img.
  2. core.img chứa kernel của GRUB cùng các module tối thiểu để đọc được filesystem chứa /boot/grub. Với disk MBR, core.img nằm ở khoảng trống sau MBR (trước phân vùng đầu, thường 1 MB). Với disk GPT, cần một BIOS boot partition (type EF02, khoảng 1 MB, không format).
  3. core.img nạp thêm module từ /boot/grub/i386-pc/, đọc grub.cfg và hiện menu.

UEFI

 UEFI firmware ──► (shimx64.efi) ──► grubx64.efi ──► grub.cfg ──► kernel (+ initrd)
  1. Firmware UEFI đọc biến Boot#### trong NVRAM (xem bằng efibootmgr -v) để biết file EFI cần chạy trên EFI System Partition (ESP, FAT32, type EF00). Nếu không có biến nào, firmware chạy file mặc định \EFI\BOOT\BOOTX64.EFI (ARM64: BOOTAA64.EFI).
  2. Khi bật Secure Boot, file đầu tiên thường là shimx64.efi (ký bằng key của Microsoft), shim verify và chạy grubx64.efi (ký bằng key của bản phân phối).
  3. GRUB là một ứng dụng EFI: nó đọc grub.cfg, nạp kernel và initrd, rồi chuyển giao cho kernel.

Bố trí thường gặp trên Debian/Ubuntu:

/boot/efi/                    # ESP (FAT32), mount tại /boot/efi
└── EFI/
    ├── BOOT/BOOTX64.EFI      # fallback
    └── debian/
        ├── shimx64.efi
        ├── grubx64.efi
        └── grub.cfg          # file nhỏ, chỉ trỏ tới /boot/grub/grub.cfg
/boot/
├── vmlinuz-6.12.0-amd64
├── initrd.img-6.12.0-amd64
└── grub/
    ├── grub.cfg              # file cấu hình chính (sinh tự động)
    ├── grubenv               # biến môi trường, 1024 byte
    └── x86_64-efi/           # module

Cấu hình

grub.cfg sinh tự động

Trên bản phân phối, không sửa trực tiếp /boot/grub/grub.cfg vì nó bị ghi đè khi cập nhật kernel. File này được sinh bởi grub-mkconfig từ:

  • /etc/default/grub: các biến chung.
  • /etc/grub.d/: các script sinh từng phần (10_linux tìm kernel trong /boot, 30_os-prober tìm hệ điều hành khác, 40_custom cho entry tự viết).
/etc/default/grub
GRUB_DEFAULT=0
GRUB_TIMEOUT=5
GRUB_CMDLINE_LINUX_DEFAULT="quiet"
GRUB_CMDLINE_LINUX="console=ttyS0,115200 isolcpus=2-3"
GRUB_TERMINAL="console serial"
GRUB_SERIAL_COMMAND="serial --unit=0 --speed=115200"
sudo grub-mkconfig -o /boot/grub/grub.cfg   # Debian/Ubuntu: sudo update-grub
                                            # Fedora/RHEL: grub2-mkconfig -o /boot/grub2/grub.cfg

Fedora/RHEL dùng BLS

Fedora và RHEL 8+ dùng Boot Loader Specification: mỗi kernel là một file trong /boot/loader/entries/*.conf, grub.cfg gọi lệnh blscfg để đọc các file này. Sửa tham số kernel bằng grubby --update-kernel=ALL --args="...".

GRUB_CMDLINE_LINUX là chỗ thêm tham số kernel cho tuning real-time (isolcpus, nohz_full, rcu_nocbs), xem Real-time KVM tuning.

Cú pháp grub.cfg

grub.cfg dùng ngôn ngữ script giống shell (biến, if, for, function). Với hệ nhúng, thường viết tay một file ngắn thay vì dùng grub-mkconfig:

grub.cfg
set default=0
set timeout=3

serial --unit=0 --speed=115200
terminal_input serial console
terminal_output serial console

insmod part_gpt
insmod ext2

menuentry "Linux" {
    search --no-floppy --fs-uuid --set=root 3f1c2a7e-0b5d-4c1e-9a6f-2d8b4e7c1a90
    linux /boot/vmlinuz root=PARTUUID=9c2e...-02 rw rootwait console=ttyS0,115200
    initrd /boot/initrd.img
}

menuentry "Linux (recovery)" {
    search --no-floppy --label --set=root rootfs_b
    linux /boot/vmlinuz root=LABEL=rootfs_b ro single
}
Lệnh Ý nghĩa
insmod <mod> Nạp module (filesystem, bảng phân vùng, gzio, serial...)
search --fs-uuid/--label/--file --set=root Tìm phân vùng theo UUID, label hoặc file có sẵn và gán vào biến root
set root=(hd0,gpt2) Gán phân vùng trực tiếp. Đánh số disk từ 0, phân vùng từ 1
linux <path> <args> Nạp kernel và command line
initrd <path> Nạp initramfs
devicetree <path> Nạp DTB (ARM), khi firmware không cung cấp DTB phù hợp
chainloader <path> Chạy một bootloader/ứng dụng EFI khác (ví dụ Windows Boot Manager)
configfile <path> Chuyển sang đọc một grub.cfg khác
load_env, save_env Đọc và ghi biến vào grubenv

GRUB shell

Bấm c ở menu để vào shell, e để sửa tạm một entry (sửa không lưu, chỉ áp dụng cho lần boot đó). Shell hữu ích khi grub.cfg hỏng:

grub> ls
(hd0) (hd0,gpt1) (hd0,gpt2)
grub> ls (hd0,gpt2)/boot
grub> set root=(hd0,gpt2)
grub> linux /boot/vmlinuz root=/dev/sda2 rw
grub> initrd /boot/initrd.img
grub> boot

Khi GRUB không tìm thấy thư mục module hoặc grub.cfg (thường do UUID đổi sau khi format lại phân vùng), nó rơi vào grub rescue> với rất ít lệnh. Cách gỡ: dùng ls tìm phân vùng chứa /boot/grub, set prefix=(hd0,gpt2)/boot/grub, set root=(hd0,gpt2), insmod normal, normal. Sau khi boot được, chạy lại grub-install và grub-mkconfig.

Serial console qua IPMI SOL

Trên server có BMC, menu GRUB có thể xem và điều khiển từ xa qua Serial-over-LAN. GRUB không làm việc trực tiếp với IPMI, chỉ cần xuất ra đúng cổng UART mà BMC đang chuyển qua mạng, thường là COM2 (ttyS1):

/etc/default/grub
GRUB_TERMINAL="console serial"
GRUB_SERIAL_COMMAND="serial --unit=1 --speed=115200"   # unit=1 là COM2 / ttyS1
GRUB_CMDLINE_LINUX="console=tty0 console=ttyS1,115200n8"

Sai --unit thì phiên SOL chỉ thấy log BIOS mà không thấy menu GRUB, lệch baudrate thì ký tự bị rác. Cấu hình đầy đủ cho BIOS, kernel và các lỗi hay gặp xem tại IPMI → Serial-over-LAN.

Cài đặt và build image

grub-install

# BIOS: ghi boot.img vào MBR, core.img vào khoảng trống sau MBR
sudo grub-install --target=i386-pc /dev/sda

# UEFI: copy grubx64.efi vào ESP và tạo biến NVRAM
sudo grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=debian

Khi tạo image cho thiết bị (không chạy trên chính máy đích), thêm --removable để cài vào đường dẫn fallback \EFI\BOOT\BOOTX64.EFI và không ghi NVRAM của máy build.

Image standalone

Với hệ nhúng, thường đóng gói GRUB, module và grub.cfg thành một file EFI duy nhất, không cần thư mục /boot/grub trên disk:

grub-mkstandalone -O x86_64-efi -o BOOTX64.EFI \
    --modules="part_gpt ext2 search linux serial" \
    "boot/grub/grub.cfg=./grub.cfg"

grub-mkimage cho phép kiểm soát chi tiết hơn: chọn chính xác module nhúng vào và đặt prefix. Image nhỏ, ít module cũng thu hẹp bề mặt tấn công khi dùng Secure Boot.

Yocto

Yocto hỗ trợ GRUB qua recipe grub-efi. Trong local.conf hoặc file machine:

MACHINE_FEATURES:append = " efi"
EFI_PROVIDER = "grub-efi"

Image wic (ví dụ mkefidisk.wks) sẽ tạo ESP chứa bootx64.efi và grub.cfg. EFI_PROVIDER = "systemd-boot" là lựa chọn thay thế đơn giản hơn. Xem thêm Yocto Overview.

grubenv và A/B update

grubenv là file cố định 1024 byte lưu biến của GRUB, dùng để nhớ trạng thái giữa các lần boot:

grub-editenv /boot/grub/grubenv list
grub-editenv /boot/grub/grubenv set slot=b
sudo grub-reboot 1          # chỉ boot entry 1 ở lần khởi động tiếp theo
sudo grub-set-default 0     # đổi entry mặc định (cần GRUB_DEFAULT=saved)

Đây là cơ chế các framework OTA (Mender, RAUC, SWUpdate) dùng khi target là GRUB, tương tự vai trò của fw_setenv với U-Boot. Một mẫu A/B tối giản có bootcount:

grub.cfg
load_env
if [ "${slot}" = "" ]; then set slot=a; fi
if [ "${upgrade_available}" = "1" ]; then
    if [ "${bootcount}" = "3" ]; then
        # Bản mới boot lỗi 3 lần: quay về slot cũ
        if [ "${slot}" = "a" ]; then set slot=b; else set slot=a; fi
        set upgrade_available=0
        set bootcount=0
    elif [ "${bootcount}" = "2" ]; then set bootcount=3
    elif [ "${bootcount}" = "1" ]; then set bootcount=2
    else set bootcount=1
    fi
    save_env slot upgrade_available bootcount
fi

menuentry "Linux slot ${slot}" {
    search --no-floppy --label --set=root rootfs_${slot}
    linux /boot/vmlinuz root=LABEL=rootfs_${slot} rw rootwait
}

Sau khi boot thành công, ứng dụng trong Linux đặt lại upgrade_available=0 và bootcount=0. Ngôn ngữ script của GRUB không có phép cộng, nên bộ đếm phải viết bằng các nhánh if như trên.

GRUB ghi grubenv tại chỗ

save_env ghi đè trực tiếp các block của file mà không qua driver filesystem, nên chỉ hoạt động khi grubenv nằm trên filesystem thường, không nén, không nằm trên LVM, RAID, Btrfs hay ZFS. Thường đặt grubenv trên ESP hoặc một phân vùng boot riêng.

Secure Boot

Có hai cách để GRUB tham gia chain of trust (khái niệm xem tại Secure boot và TrustZone):

  • UEFI Secure Boot: firmware chỉ chạy file EFI có chữ ký hợp lệ theo key trong biến db. Các bản phân phối dùng shim (ký bởi Microsoft) chứa key của bản phân phối, shim verify GRUB, GRUB verify kernel qua shim. Người dùng thêm key riêng (để chạy module/kernel tự build) bằng MOK (Machine Owner Key, mokutil --import). SBAT là cơ chế thu hồi theo thế hệ (generation) để chặn các bản GRUB cũ có lỗ hổng.
  • Sản phẩm tự quản lý key: ghi key của nhà sản xuất vào PK/KEK/db của firmware, ký trực tiếp image GRUB standalone bằng sbsign, không cần shim.

GRUB còn có cơ chế verify riêng bằng GPG (--pubkey khi tạo image, set check_signatures=enforce), yêu cầu mọi file nạp vào (kernel, initrd, grub.cfg) có file chữ ký .sig đi kèm.

Lỗ hổng GRUB

GRUB parse nhiều định dạng (filesystem, font, ảnh) nên đã có nhiều CVE nghiêm trọng, ví dụ BootHole (CVE-2020-10713). Với sản phẩm có Secure Boot, nên theo dõi CVE của GRUB và shim như với kernel. Xem CVE.

References

  1. GNU GRUB Manual - gnu.org
  2. GRUB - ArchWiki - wiki.archlinux.org
  3. Unified Extensible Firmware Interface/Secure Boot - ArchWiki - wiki.archlinux.org
  4. Boot Loader Specification - uapi-group.org
  5. UEFI on U-Boot - docs.u-boot.org