Bootloader

Mar 27, 2024 20:30 · 1304 words · 3 minute read Linux OS

什么是 Bootloader

Bootloader 是一类用于在计算机开机时加载操作系统的小软件。而对处理器来说,和操作系统一样,Bootloader 也只不过是它盲目执行的一串代码。

x86 系统(IBM 兼容机)开机后 CPU 执行的第一个程序(就是那个蓝色的界面),是 BIOS(Basic Input and Output System)。这个初始化程序提前被烧录在了一个 ROM(只读存储器)中。

x86 系统刚开机时 CPU 以实模式运行,20 位地址总线只能寻址 1M,将 1M 内存空间最上面的 0xF0000 - 0xFFFFF 64K(第一个段)映射给 ROM。刚上电时第一条指令指向 0xFFFF0(CS 设置为 0xFFFF;IP 设置为 0x0000,0xFFFF « 4 + 0x0000),就会执行 ROM 中的代码,BIOS 开始检查硬件。如果一切就绪,BIOS 接着会去加载二级 Bootloader。

保护模式则能够寻址 4G。

0xAA55

装载着操作系统的磁盘的前 512 字节被称作 Bootsector(启动扇区)或者 MBR(Master Boot Record),这是一个约定。有效的启动扇区最后两个字节必须是 0xAA55,BIOS 就会认为这块磁盘是可启动的。

理想状态下,启动扇区会放置另一个 Bootloader,比如 GRUB。BIOS 在结束前将启动扇区中的内容加载到内存的 0x7C00 位置,通过 JMP 将控制权交给这部分代码。

下面给一个简单的 Bootloader 源码,GRUB 则要复杂得多:

        org 7C00h

        jmp short Start ;Jump over the data (the 'short' keyword makes the jmp instruction smaller)

Msg:    db "Hello World! "
EndMsg:

Start:  mov bx, 000Fh   ;Page 0, colour attribute 15 (white) for the int 10 calls below
        mov cx, 1       ;We will want to write 1 character
        xor dx, dx      ;Start at top left corner
        mov ds, dx      ;Ensure ds = 0 (to let us load the message)
        cld             ;Ensure direction flag is cleared (for LODSB)

Print:  mov si, Msg     ;Loads the address of the first byte of the message, 7C02h in this case

                        ;PC BIOS Interrupt 10 Subfunction 2 - Set cursor position
                        ;AH = 2
Char:   mov ah, 2       ;BH = page, DH = row, DL = column
        int 10h
        lodsb           ;Load a byte of the message into AL.
                        ;Remember that DS is 0 and SI holds the
                        ;offset of one of the bytes of the message.

                        ;PC BIOS Interrupt 10 Subfunction 9 - Write character and colour
                        ;AH = 9
        mov ah, 9       ;BH = page, AL = character, BL = attribute, CX = character count
        int 10h

        inc dl          ;Advance cursor

        cmp dl, 80      ;Wrap around edge of screen if necessary
        jne Skip
        xor dl, dl
        inc dh

        cmp dh, 25      ;Wrap around bottom of screen if necessary
        jne Skip
        xor dh, dh

Skip:   cmp si, EndMsg  ;If we're not at end of message,
        jne Char        ;continue loading characters
        jmp Print       ;otherwise restart from the beginning of the message


times 0200h - 2 - ($ - $$)  db 0    ;Zerofill up to 510 bytes

        dw 0AA55h       ;Boot Sector signature

;OPTIONAL:
;To zerofill up to the size of a standard 1.44MB, 3.5" floppy disk
;times 1474560 - ($ - $$) db 0
  1. 将上面代码写入 floppy.asm 文件并编译成 Bootloader 镜像

    $ nasm -f bin -o floppy.img floppy.asm
    
  2. 把 Bootloader 镜像写入磁盘

    dd if=floppy.img of=/dev/vdb
    

GRUB

我们常用 grub2 来修改操作系统启动时的内核参数:

  1. vim /etc/default/grub 修改内核参数
  2. grub2-mkconfig -o /boot/grub2/grub.cfg 生成 grub2 配置
  3. grub2-install /dev/vda 将 grub2 生成的 Bootloader 镜像 boot.img(不超过 512B)写入磁盘的启动扇区(MBR),和上面的 dd 命令效果差不多

但 512 字节的程序实在太小了,能做的事有限,所以 GRUB 分成两个阶段:

  1. 阶段 2 的镜像位置(所在的第一个扇区)已经写死在了 boot.img Bootloader 程序中,就由它来加载
  2. 有着 grub2 全部功能的完整镜像 core.img

GRUB 各种镜像文件参考:https://www.gnu.org/software/grub/manual/grub/html_node/Images.html

当加载到 grub2 内核 kernel.img(不是 Linux 内核)前,1M 内存空间实在不够用了,在这里就要释放 32 位寻址(4GB)的能力,从实模式切换到保护模式(打开 Gate A20)。kernel.img 是压缩过的,下面就要对其解压并加载到内存中(这时候 4GB 就非常大了),然后跳转运行 kernel.img 的代码。

linux16 命令表示加载指定的操作系统内核文件,并传递内核启动参数。选定操作系统后,就会读取并加载 Linux 内核镜像到内存中,并跳转运行内核。