1.loader的作用
loader的作用就在于切换cpu运行模式并读取加载硬盘上的kernel文件。由于kernel文件较为庞大,所以我们会逐个字节将其复制到1M内存之后。
整个loader文件的流程图如下:

2.loader的实现
2.1 在文件系统中寻找kernel文件
在loader中寻找kernel文件与在boot中寻找loader文件逻辑完全相同,区别只是将我们所需寻找到文件名从LOADER BIN改成KERNEL BIN。
2.2 找到kernel之后
找到了kernel文件之后,与boot相似,我们首先需要获得kernel文件的文件大小以及存放的扇区位置,同样用到在boot中使用的Func_DirPointerToStartSectorOfFile以及Func_DirPointerToStartSectorOfFilePointer。所以在加载kernel文件之前的代码应该是
;===开辟内存空间来保存kernel文件的起始扇区以及kernel文件所占扇区的大小
SectorOfKernel dw 0
StartSectorOfKernel dw 0
;===定义保护模式段描述符
gdt_start_desc dq 0x0000000000000000
gdt_code_desc dq 0x00cf9a000000ffff
gdt_data_desc dq 0x00cf92000000ffff
gdt_size dw $ - gdt_start_desc
gdt_base dd gdt_start_desc
Label_KernelFound:
;将获得的kernel目录指针转为kernel文件的起始扇区以及kernel文件所占扇区的大小
call Func_DirPointerToSectorOfFile
mov word[SectorOfKernel], ax
call Func_DirPointerToStartSectorOfFilePointer
mov word[StartSectorOfKernel], ax
;===加载保护模式段描述符表
lgdt [gdt_size]
;===开启A20地址线
in al, 0x92
or al, 00000010B
out 0x92, al
;===通过cr0开启保护模式
mov eax, cr0
or eax, 1
mov cr0, eax
;===将保护模式的数据段描述符放入fs段寄存器中
mov ax, 0x0010
mov fs, ax
;===关闭保护模式
mov eax, cr0
and al, 11111110b
mov cr0, eax
复制代码
保护模式段描述符详见从零开始写一个操作系统 —— 1.5实模式与保护模式。在找到以及获得了kernel的大小与位置后,就可以开始着手加载kernel文件。而与加载loader文件不同的是,kernel文件整体大小可能会超出BIOS中断13h所能读取的范围,以及所需放置的1M内存后无法通过实模式寻址,所以我们需要通过先开启保护模式,将保护模式的段描述符放入fs段寄存器中,然后再退出保护模式。虽然此时已经退出保护模式,但是fs段寄存器中依然为保护模式的段描述符,所以我们此时可以即在实模式下使用BIOS中断,也可以通过保护模式的寻址方式访问1M内存之后的内容。
2.3 加载kernel
加载一个kernel扇区至缓冲区与加载loader时所用的函数一样,区别是此时我们仅仅需要它帮我们加载一个扇区就可以,而复制一个扇区到1M内存之后的函数如下
OffsetOfCache equ 0x9000
OffsetOfKernel equ 0x100000
ByteIndexOfKernel dd 0
Func_CopySingleSectorOfKernel:
;loop指令会循环运行cx寄存器中数据的次数
mov cx, 512
;lodsb指令会逐个将si寄存器所指向的内存地址中的数据加载至al寄存器中
mov si, OffsetOfCache
;edi指向kernel的起始位置
mov edi, OffsetOfKernel
Label_CopySingleByteOfSector:
lodsb
mov edx, dword[ByteIndexOfKernel]
;fs:edi+edx指向当前kernel所应该放置的内存地址
mov byte[fs:edi + edx], al
;每放置完成一个数据,当前kernel索引加一
inc dword[ByteIndexOfKernel]
loop Label_CopySingleByteOfSector
ret
复制代码
所以整体加载kernel文件的程序为
Label_LoadKernel:
mov word[Index], 0
Label_ForIndexInSectorOfKernel:
;所需要读取的扇区数量
mov si, 1
;读取扇区开始的位置
mov di, word[StartSectorOfKernel]
;数据放置的位置
mov dx, OffsetOfCache
call Func_ReadSector
Call Func_CopySingleSectorOfKernel
inc word[Index]
inc word[StartSectorOfKernel]
mov ax, word[Index]
cmp ax, word[SectorOfKernel]
jne Label_ForIndexInSectorOfKernel
;重新打开保护模式
mov eax, cr0
or eax, 1
mov cr0, eax
;加载保护模式数据段描述符至ds段寄存器
mov ax, 0x0010
mov ds, ax
;跳转到IA-32e进入段
jmp 0x0008:Label_ToLongMode
复制代码
3.切换至IA-32e模式

IA-32e模式详见从零开始写一个操作系统 —— 1.6 IA-32e模式。与保护模式类似,IA-32e模式寻址方式也需要通过GDT全局描述符表寻址。所以我们第一步需要和保护模式一样,建立全局描述符表。
gdt64_start_desc dq 0x0000000000000000
gdt64_code_desc dq 0x0020980000000000
gdt64_data_desc dq 0x0000920000000000
gdt64_size dw $ - gdt64_start_desc
gdt64_base dd gdt64_start_desc
复制代码
IA-32e模式取消了保护模式中段描述符的段基址与段限长,使得描述符可以直接寻址整个地址空间。然后我们需要建立页表:
PML4:
mov dword[0x90000], 0x91007
PDPT:
mov dword[0x91000], 0x92007
PDT:
mov dword[0x92000], 0x000087
复制代码
在第三级页表PDT中,通过0x87结尾,表示使用2MB大小的页。接下来就是开启IA-32e模式:
;加载IA-32e模式段描述符表
lgdt [gdt64_size]
;将各个段寄存器载入数据段描述符表
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
;通过将cr4控制寄存器第5位置1开启PAE页表功能
mov eax, cr4
bts eax, 5
mov cr4, eax
;将PML4页表基址载入cr3控制寄存器
mov eax, 0x90000
mov cr3, eax
;开启IA-32e模式
mov ecx, 0x0c0000080
rdmsr
bts eax, 8
wrmsr
;通过将cr0寄存器第0位及第31位置1,开启页表
mov eax, cr0
bts eax, 0
bts eax, 31
mov cr0, eax
复制代码
最终,通过jmp 0x0008:0x100000长跳转,使得程序开始运行我们放置于1M内存中的内核程序。接下来就是内核程序kernel的开发。























![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)