1)上节课安装的GRUB不是已经 把我们的操作 系统加载到内存中了吗?为什么还要二级引导器?
-
二级引导器是操作系统的排头兵,他先去收集计算机的信息,看看计算机硬件支持不支持运行我们的操作系统。GRUB负责的是操作系统加载进内存,而二级引导器负责的是检验计算机 能不能运行我们的操作系统,并且初始化好一些硬件(cpu,显卡,内存)的配置,把内核相关的文件放到正确的位置上。二者的工作使命是不一样的。
2)二级引导器先去收集了计算机的硬件信息,那么这个硬件信息以何种方式,在哪个位置让我的操作系统知道呢?
-
信息的话我们设计 一个数据结构来存放,地址的话放在内存1MB的地方
typedef struct s_MACHBSTART
{
u64_t mb_krlinitstack;//内核栈地址
u64_t mb_krlitstacksz;//内核栈大小
u64_t mb_imgpadr;//操作系统映像
u64_t mb_imgsz;//操作系统映像大小
u64_t mb_bfontpadr;//操作系统字体地址
u64_t mb_bfontsz;//操作系统字体大小
u64_t mb_fvrmphyadr;//机器显存地址
u64_t mb_fvrmsz;//机器显存大小
u64_t mb_cpumode;//机器CPU工作模式
u64_t mb_memsz;//机器内存大小
u64_t mb_e820padr;//机器e820数组地址
u64_t mb_e820nr;//机器e820数组元素个数
u64_t mb_e820sz;//机器e820数组大小
//……
u64_t mb_pml4padr;//机器页表数据地址
u64_t mb_subpageslen;//机器页表个数
u64_t mb_kpmapphymemsz;//操作系统映射空间大小
//……
graph_t mb_ghparm;//图形信息
}__attribute__((packed)) machbstart_t;
3)二级引导器的功能文件我们怎样设计的?
4)上面的文件我们需要编译才能使用,编译过程是怎样的?
把三个文件打包成映像文件
lmoskrlimg -m k -lhf initldrimh.bin -o Cosmos.eki -f initldrkrl.bin initldrsve.bin
5)现在二级引导器文件虽然有了,那GRUB如何找到他呢?
-
写一个GRUB头,这里分两个子文件
-
imginithead.asm 汇编文件,让 GRUB 识别,同时设置 C 语言运行环境,用于调用 C 函数
-
inithead.c 文件,找到二级引导器的核心文件——initldrkrl.bin,并且把它放置到特定的内存地址上。
-
6)imginithead.asm具体代码是怎样实现的?
-
首先是 GRUB1 和 GRUB2 需要的两个头结构
MBT_HDR_FLAGS EQU 0x00010003
MBT_HDR_MAGIC EQU 0x1BADB002
MBT2_MAGIC EQU 0xe85250d6
global _start
extern inithead_entry
[section .text]
[bits 32]
_start:
jmp _entry
align 4
mbt_hdr:
dd MBT_HDR_MAGIC
dd MBT_HDR_FLAGS
dd -(MBT_HDR_MAGIC+MBT_HDR_FLAGS)
dd mbt_hdr
dd _start
dd 0
dd 0
dd _entry
ALIGN 8
mbhdr:
DD 0xE85250D6
DD 0
DD mhdrend - mbhdr
DD -(0xE85250D6 + 0 + (mhdrend - mbhdr))
DW 2, 0
DD 24
DD mbhdr
DD _start
DD 0
DD 0
DW 3, 0
DD 12
DD _entry
DD 0
DW 0, 0
DD 8
mhdrend: -
然后是关中断并加载 GDT
_entry:
cli ;关中断
in al, 0x70
or al, 0x80
out 0x70,al ;关掉不可屏蔽中断
lgdt [GDT_PTR] ;加载GDT地址到GDTR寄存器
jmp dword 0x8 :_32bits_mode ;长跳转刷新CS影子寄存器
;………………
;GDT全局段描述符表
GDT_START:
knull_dsc: dq 0
kcode_dsc: dq 0x00cf9e000000ffff
kdata_dsc: dq 0x00cf92000000ffff
k16cd_dsc: dq 0x00009e000000ffff ;16位代码段描述符
k16da_dsc: dq 0x000092000000ffff ;16位数据段描述符
GDT_END:
GDT_PTR:
GDTLEN dw GDT_END-GDT_START-1 ;GDT界限
GDTBASE dd GDT_START -
最后是初始化段寄存器和通用寄存器、栈寄存器,这是为了给调用 inithead_entry 这个 C 函数做准备
_32bits_mode:
mov ax, 0x10
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
xor eax,eax
xor ebx,ebx
xor ecx,ecx
xor edx,edx
xor edi,edi
xor esi,esi
xor ebp,ebp
xor esp,esp
mov esp,0x7c00 ;设置栈顶为0x7c00
call inithead_entry ;调用inithead_entry函数在inithead.c中实现
jmp 0x200000 ;跳转到0x200000地址
7)上述代码的最后调用了 inithead_entry 函数,这个函数 在哪里呢?
-
在 inithead.c 中
我们实现了 inithead_entry 函数,它主要干了两件事,即分别调用 write_realintsvefile();、write_ldrkrlfile() 函数,把映像文件中的 initldrsve.bin 文件和 initldrkrl.bin 文件写入到特定的内存地址空间中,具体地址在上面代码中的宏有详细定义。
8)前面我们的imghead.asm 汇编文件代码中,我们的最后一条指令是“jmp 0x200000”,那么它是跳到哪里去了 ?
-
其实就是 我们下面写 的C文件中把二级引导器核心文件放置的位置。这一跳就直接 进入二级引导器的主模块了。
9)现在我们跳到二级引导器的主模块了,那对应的CPU啊 ,寄存器啊这类的环境应该也要做相应的改变,代码应该是怎样的?
_entry:
cli
lgdt [GDT_PTR];加载GDT地址到GDTR寄存器
lidt [IDT_PTR];加载IDT地址到IDTR寄存器
jmp dword 0x8 :_32bits_mode;长跳转刷新CS影子寄存器
_32bits_mode:
mov ax, 0x10 ; 数据段选择子(目的)
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
xor eax,eax
xor ebx,ebx
xor ecx,ecx
xor edx,edx
xor edi,edi
xor esi,esi
xor ebp,ebp
xor esp,esp
mov esp,0x90000 ;使得栈底指向了0x90000
call ldrkrl_entry ;调用ldrkrl_entry函数
xor ebx,ebx
jmp 0x2000000 ;跳转到0x2000000的内存地址
jmp $
GDT_START:
knull_dsc: dq 0
kcode_dsc: dq 0x00cf9a000000ffff ;a-e
kdata_dsc: dq 0x00cf92000000ffff
k16cd_dsc: dq 0x00009a000000ffff ;16位代码段描述符
k16da_dsc: dq 0x000092000000ffff ;16位数据段描述符
GDT_END:
GDT_PTR:
GDTLEN dw GDT_END-GDT_START-1 ;GDT界限
GDTBASE dd GDT_START
IDT_PTR:
IDTLEN dw 0x3ff
IDTBAS dd 0 ;这是BIOS中断表的地址和长度
10)接下来我们要获取内存布局信息,还要设置显卡的工作模式,这些都是需要BIOS提供的中断服务才能去完成的,可是我们在C函数中要想直接调用中断是不可能的,因为C的CPU工作环境是32位保护模式,中断的工作环境是16位的实模式。这该如何调用呢?
-
我们的C正在工作,然后被中断了,所以我们要把C语言环境下的CPU上下文,也就是保护模式下的一些寄存器啥的保存到我们的内存中。
-
现在我们可以切换到实模式了,我们调用BIOS中断,设置好我们的显卡,内存啊相关的配置,然后把他们保存到内存中。
-
最后我们切换回保护模式,重新把我们寄存器的信息恢复,那我们的环境就是C环境了。
11)上面的三个流程用代码是怎样实现的?
realadr_call_entry:
pushad ;保存通用寄存器
push ds
push es
push fs ;保存4个段寄存器
push gs
call save_eip_jmp ;调用save_eip_jmp
pop gs
pop fs
pop es ;恢复4个段寄存器
pop ds
popad ;恢复通用寄存器
ret
save_eip_jmp:
pop esi ;弹出call save_eip_jmp时保存的eip到esi寄存器中,
mov [PM32_EIP_OFF],esi ;把eip保存到特定的内存空间中
mov [PM32_ESP_OFF],esp ;把esp保存到特定的内存空间中
jmp dword far [cpmty_mode];长跳转这里表示把cpmty_mode处的第一个4字节装入eip,把其后的2字节装入cs
cpmty_mode:
dd 0x1000
dw 0x18
jmp $
12)现在还差二级引导器的主函数,长什么样?
void ldrkrl_entry()
{
init_bstartparm();
return;
}
-
init_bstartparm()就是收集计算机信息的函数,下节内容我们再去编写他。
-