OS-4
建造二级引导器
二级引导器的作用
二级引导器作为操作系统的先驱,它需要收集机器信息,确定这个计算机能不能运行我们的操作系统,对 CPU、内存、显卡进行一些初级的配置,放置好内核相关的文件。
二级引导器的设计
运行流程
GRUB启动后,选择对应的启动菜单,GRUB会通过自带的文件驱动,定位到对应的eki文件(即我们的操作系统)
GRUB通过识别GRUB头部(imginithead.asm),获取二级引导器的信息并加载(inithead_entry.c将二级引导器的代码(这些代码将对机器进行初始设置),加载到内存里)。
代码讲解
实现GRUB头
我们的 GRUB 头有两个文件组成,一个 imginithead.asm 汇编文件,它有两个功能,既能让 GRUB 识别,又能设置 C 语言运行环境,用于调用 C 函数;第二就是 inithead.c 文件,它的主要功能是查找二级引导器的核心文件——initldrkrl.bin,然后把它放置到特定的内存地址上。
多重引导的前置知识
能够被 GRUB 引导的内核有两个条件:
- 需要有一个 Multiboot Header ,这个 Multiboot Header 必须在内核镜像的前 8192 个字节内,并且是首地址是 4 字节对其的。
- 内核的加载地址在 1MB 以上的内存中,这个要求是 GRUB 附加的,并非多重引导规范的规定。
Multiboot Header的分布必须如下所示:
偏移量 | 类型 | 域名 | 备注 |
---|---|---|---|
0 | u32 | magic | 必需,为0x1BADB002 |
4 | u32 | flags | 必需 |
8 | u32 | checksum | 必需 |
12 | u32 | header_addr | 如果flags[16]被置位 |
16 | u32 | load_addr | 如果flags[16]被置位 |
20 | u32 | load_end_addr | 如果flags[16]被置位 |
24 | u32 | bss_end_addr | 如果flags[16]被置位 |
28 | u32 | entry_addr | 如果flags[16]被置位 |
32 | u32 | mode_type | 如果flags[2]被置位 |
36 | u32 | width | 如果flags[2]被置位 |
40 | u32 | height | 如果flags[2]被置位 |
44 | u32 | depth | 如果flags[2]被置位 |
flags:flags域指出OS映像需要引导程序提供或支持的特性。0-15 位指出需求:如果引导程序发现某些值被设置但出于某种原因不理解或不能不能满足相应的需求,它必须告知用户并宣告引导失败。16-31位指出可选的特性:如果引导程序不能支持某些位,它可以简单的忽略它们并正常引导。自然,所有 flags 字中尚未定义的位必须被置为 0。这样,flags 域既可以用于版本控制也可以用于简单的特性选择。
checksum: 域 checksum 是一个 32 位的无符号值,当与其他的 magic 域(也就是 magic 和 flags)相加时,结果必须是 32 位的无符号值 0(即magic + flags + checksum = 0)
header_addr: header的地址,这里往后的 32 个字节不是必须的,并且对于内核为 ELF 格式时是不需要的。
由此我们定义结构体映像文件头描述符s_mlosrddsc和文件头描述符:
1 | //映像文件头描述符 |
imginithead.asm
主要工作为初始化CPU的寄存器,加载GDT,切换到 CPU 的保护模式
先写一些宏定义等
1 | MBT_HDR_FLAGS EQU 0x00010003 ;EQU可以理解为宏定义,相当于#define |
然后根据上面的表格结构写GRUB和GRUB2的结构代码
1 | _start: |
接下来关中断,这是因为,实地址模式情况下,如果计算机突然有请求,比如你按键盘输入,产生中断,让CPU处理你的请求,这会导致操作系统引导停止,即使不停止,也会造成其他程序的中断,从而导致操作系统加载失败,那这就麻烦了,因此需要关闭中断功能,才能避免意外。
1 | _entry: |
汇编基本知识:
- 汇编指令IN与OUT用来操作端口(因为端口是独立编址的,所以不能使用MOV指令)
- 端口有0-65535个,前0-255属于1字节端口(使用al),其他属于2字节端口(使用ax)
- 对于1字节端口,可以使用立即数进行操作
- IN表示从端口读,OUT使用正好相反
然后加载全局段描述符表GDT:
1 | ;GDT全局段描述符表 |
最后初始化寄存器,通用寄存器,栈寄存器,为调用inithead_entry.c做准备
1 | _32bits_mode: |
inithead_entry.c
我们前面已经提到,这个函数主要用来将二级引导器的代码放到内存中,但在移动文件之前,我们先进行一步