这一篇我们先来实现引导程序,即一个bootloader,用于引导后续的操作系统。
为了方便,我们的程序设计遵循多系统引导协议,更加通用化。
多系统引导协议
系统引导协议,通常指的是在计算机上安装和引导多个操作系统的方法和技术。多系统引导协议的核心组件是引导加载程序,它负责在计算机启动时选择一个操作系统并加载它。
以下是一些常见的多系统引导协议:
-
Multiboot(多重引导):
Multiboot 是一种多系统引导协议,它定义了操作系统内核和引导加载程序之间的接口。此协议最初由 GNU 项目开发,旨在简化多系统引导过程,让一个引导加载程序能够启动不同的操作系统。Multiboot 协议通过定义内核映像文件的结构和启动时引导加载程序与操作系统内核之间的通信方式,使得多个操作系统能够共享一个引导加载程序。 -
UEFI(统一可扩展固件接口):
UEFI 是一种现代的引导加载程序和操作系统接口,旨在取代传统的 BIOS。UEFI 提供了一个统一的接口,使得操作系统可以与固件进行交互。UEFI 支持多系统引导,因为它允许在启动时选择一个操作系统并加载它。UEFI 可以支持多个操作系统,包括 Windows、Linux 和 macOS。 -
GRUB(GNU GRand Unified Bootloader):
GRUB 是一种广泛使用的引导加载程序,兼容 Multiboot 和 UEFI 协议。GRUB 可以在单个计算机上加载多个操作系统,并提供一个交互式菜单供用户在启动时选择要加载的操作系统。GRUB 支持许多文件系统类型,包括 FAT、NTFS、ext2/3/4、HFS+ 等。
在设置多系统引导时,需要确保操作系统与引导加载程序兼容,以确保在引导过程中可以正确识别和加载操作系统。
Multiboot
Multiboot 是一种多系统引导协议,通过定义操作系统内核和引导加载程序之间的接口来实现。它最初由 GNU 项目开发,用于简化多操作系统引导过程。Multiboot 协议允许引导加载程序(如 GRUB)通过一个统一的接口启动多个操作系统。
Multiboot 是如何实现的?
内核映像文件结构:Multiboot 协议定义了一个标准的内核映像文件结构,包括一个特殊的 Multiboot 头部。此头部包含有关内核映像文件的信息,如魔数、标志、校验和等。引导加载程序可以通过解析这些信息来识别兼容的内核映像文件。
通信:Multiboot 协议规定了引导加载程序在加载操作系统内核之前需要提供的信息。这些信息以一个名为 Multiboot 信息结构的数据结构的形式提供,其中包含了内存映射、引导设备、命令行参数等信息。引导加载程序将此结构的指针传递给内核入口点,以便内核可以获取有关硬件和引导环境的信息。
应用 Multiboot
编写内核:为了让操作系统内核与 Multiboot 协议兼容,需要在内核映像文件中包含 Multiboot 头部。这意味着在编写操作系统内核时,需要遵循 Multiboot 协议的规定,例如在内核入口点附近放置头部。
配置引导加载程序:引导加载程序(如 GRUB)需要被配置为识别和加载 Multiboot 兼容的内核映像文件。这通常是通过配置文件(如 GRUB 的 grub.cfg)实现的,其中指定了内核映像文件的路径、命令行参数等。
启动操作系统:在计算机启动时,引导加载程序会读取配置文件并显示一个菜单,供用户选择要加载的操作系统。用户选择操作系统后,引导加载程序会根据 Multiboot 信息结构加载操作系统内核并将控制权转交给内核入口点。
Why?
通过遵循 Multiboot 协议,开发者可以确保他们的操作系统内核与诸如 GRUB 这样的引导加载程序兼容。这样,用户可以在同一台计算机上安装和引导多个操作系统。
代码
; multiboot引导协议, 兼容版本一和二
MBOOT_HEADER_MAGIC equ 0x1BADB002 ; Multiboot 魔数,由规范决定的
MBOOT2_HEADER_MAGIC equ 0x1BADB002 ; Multiboot2 魔数,由规范决定的
MBOOT_PAGE_ALIGN equ 1 << 0 ; 0 号位表示所有的引导模块将按页(4KB)边界对齐
MBOOT_MEM_INFO equ 1 << 1 ; 1 号位通过 Multiboot 信息结构的 mem_* 域包括可用内存的信息
; (告诉GRUB把内存空间的信息包含在Multiboot信息结构中)
; Multiboot 的标志
MBOOT_HEADER_FLAGS equ MBOOT_PAGE_ALIGN | MBOOT_MEM_INFO
; 域checksum是一个32位的无符号值,当与其他的magic域(也就是magic和flags)相加时,
; 要求其结果必须是32位的无符号值 0 (即magic + flags + checksum = 0)
MBOOT_CHECKSUM equ - (MBOOT_HEADER_MAGIC + MBOOT_HEADER_FLAGS)
MBOOT2_CHECKSUM equ - (MBOOT2_HEADER_MAGIC + MBOOT_HEADER_FLAGS)
; -----------------------------------------------
[BITS 32]
section .init.text
; ---------- GRUB协议header定义-------------
_start:
jmp _entry
ALIGN 8
mbt_hdr:
dd MBOOT_HEADER_MAGIC
dd MBOOT_HEADER_FLAGS
dd -(MBOOT_HEADER_MAGIC + MBOOT_HEADER_FLAGS)
dd mbt_hdr
dd _start
dd 0
dd 0
dd _entry
; --------- GRUB2协议header定义-------------------
ALIGN 8
mbt2_hdr:
dd MBOOT2_HEADER_MAGIC
dd 0
dd mbt2_hdr_end - mbt2_hdr
dd -(MBOOT2_HEADER_MAGIC + 0 + (mbt2_hdr_end - mbt2_hdr))
dw 2, 0
dd 24
dd mbt2_hdr
dd _start
dd 0
dd 0
dw 3, 0
dd 12
dd _entry
dd 0
dw 0, 0
dd 8
mbt2_hdr_end:
ALIGN 8
[GLOBAL _start] ; 内核代码入口,此处提供该声明给 ld 链接器
[GLOBAL mboot_ptr_tmp] ; 全局的 struct multiboot * 变量
[EXTERN kern_entry] ; 声明内核 C 代码的入口函数
_entry:
cli
mov [mboot_ptr_tmp], ebx ; 在ebx中存放mboot_ptr_tmp所指的值
mov esp, STACK_TOP ; 设置内核栈地址
and esp, 0FFFFFFF0H ; 栈地址按照16字节对齐
mov ebp, 0 ; 帧指针修改为 0
call kern_entry ; 调用内核入口函数
; ---------------------------------------------------------------
section .init.data ; 未初始化的数据段从这里开始
stack: times 1024 db 0 ; 这里作为内核栈
STACK_TOP equ $-stack-1 ; 内核栈顶,$ 符指代是当前地址
mboot_ptr_tmp: dd 0 ; 全局的multiboot 结构体指针
代码解析
这段汇编代码是一个简单的用于引导操作系统内核的引导程序代码,它遵循 Multiboot 和 Multiboot2 标准。这些标准允许引导加载程序,如 GRUB,通过提供一组通用引导参数来启动多种操作系统。以下是对这段代码的逐行解释:
-
[BITS 32]
在汇编代码中,[BITS 32] 是一个汇编器指令,用于告诉汇编器当前代码段的位数。在这种情况下,[BITS 32] 表示生成的代码是 32 位的。这对于正确生成目标代码和正确处理内存寻址和操作数大小至关重要。
在这段代码中,[BITS 32] 用于告诉汇编器生成适用于 32 位处理器的代码。因此,在处理这段代码时,汇编器会生成适当的 32 位指令和操作数。
对于 64 位代码,你可以使用 [BITS 64] 指令。根据处理器架构和目标代码的需求,可以在汇编代码中使用不同的 [BITS] 指令。 -
section .init.text
在汇编代码中,section .init.text 是一个指令,用于告诉汇编器当前指令和数据应该放在名为 .init.text 的代码段中。.init.text 通常用于存放操作系统内核或程序的初始化代码。这些代码在系统启动或程序运行时执行,负责进行必要的初始化操作。
在生成目标文件时,汇编器会将 .init.text 段的内容组织在一起,确保它们在链接过程中被放置在适当的内存区域。链接器可以使用链接脚本来指定这些段的布局和顺序,以便在运行时正确加载和执行这些代码。
section .init.text 指令表明紧随其后的指令和数据应该放在 .init.text 段中。这些指令通常包括与操作系统内核启动和初始化相关的代码。 -
ALIGN 8
在汇编代码中,ALIGN 8 是一个汇编器指令,用于确保紧随其后的代码或数据在内存中按照特定的对齐边界对齐。在这个例子中,ALIGN 8 表示将紧随其后的代码或数据对齐到 8 字节边界。
对齐的目的是提高数据访问性能和遵循特定处理器的对齐要求。许多处理器在访问对齐数据时比访问非对齐数据更高效。此外,某些处理器对数据对齐有严格要求,对于非对齐的数据访问可能会导致异常或错误。
在上面的代码中,ALIGN 8 的使用有以下原因:- 性能:确保紧随其后的数据或代码(如 Multiboot 头)对齐到 8 字节边界,可以提高内存访问速度。
- 兼容性:某些处理器对数据对齐有严格要求,使用 ALIGN 8 可确保代码在这些处理器上正常运行。
当处理紧密关联的数据结构或代码段时,合适的对齐可以提高程序的性能和可靠性。
-
MBOOT_HEADER_MAGIC
和MBOOT2_HEADER_MAGIC
分别定义了Multiboot
和Multiboot2
标准的魔数,这些数值是用于识别Multiboot
兼容的内核映像的。 -
MBOOT_PAGE_ALIGN
和MBOOT_MEM_INFO
是Multiboot
标志,分别表示引导模块按页对齐和包含可用内存信息。 -
MBOOT_HEADER_FLAGS
是Multiboot
标志的组合。 -
MBOOT_CHECKSUM
和MBOOT2_CHECKSUM
是使得魔数、标志和校验和相加结果为0的校验和值。
下面是关于实际引导代码的解释: -
_start
是引导代码的入口点。首先执行一个跳转指令,跳转到_entry
标签。
定义了Multiboot
和Multiboot2
的头部结构,这些头部结构包含了所需的魔数、标志、校验和等信息。这些信息使得 GRUB 能够识别并加载操作系统内核。 -
_entry
标签是实际的引导代码入口点。
首先,它禁用中断(cli
),将ebx
中的Multiboot
结构指针保存到mboot_ptr_tmp
变量,设置内核栈地址(esp
)和帧指针(ebp
),然后调用内核 C 代码的入口函数(kern_entry
)。
此外,还有一些定义和声明:
stack
是内核栈的定义,大小为 1024 字节。STACK_TOP
是栈顶地址。mboot_ptr_tmp
是一个全局变量,用于保存Multiboot
结构的指针。
总之,这段汇编代码定义了一个遵循 Multiboot
和 Multiboot2
标准的简单引导程序。当 GRUB 加载内核时,它将通过这段引导代码来设置栈、处理 Multiboot
结构并最终调用 C 语言编写的内核入口函数。