[Make MyOS] Starting kernel
上一节loader程序装载完kernel后,执行了jmp SelectorCode64:OffsetOfKernelFile
命令跳转到kernel部分了,意味着Loader将处理的控制权交给了Kernel
处理器把控制权交给kernel后,kernel最先执行的是内核执行头程序。内核执行头程序是一段精心设计的汇编代码,而且必须借助特殊的编译链接方法才能得到最先执行。
内核头程序负责为操作系统创建段结构和页表结构,设置某些结构的默认处理函数、配置关键寄存器等工作。在完成上述工作后,依然要借助远跳指令才能进入系统内核主程序。
如何将内核执行头程序编译生成到整个内核程序文件的起始处?手动编写内核程序的链接脚本,在内核程序的链接过程中,链接器会按照链接脚本描述的地址空间布局,把编译好的各个程序片段填充到内核程序文件中。
看完有点懵,都是内核程序为啥要咱手动指定谁先谁后?大概是因为内核头是汇编写的,内核是c写的..要让head先执行就得在lds中指定第一条指令是啥..编译部分参考哈: ld - 链接脚本学习笔记与实践过程 - 知乎 (zhihu.com)
1 链接脚本
来瞅瞅这个Kernel.lds
文件
1 | /* OUTPUT_FORMAT(DEFAULT, BIG, LITTLE),给链接过程提供 默认,大端,小端 三种格式 */ |
SECTIONS部分描述了各程序段在输出文件中的部署以及它们在内存中的布局。ld命令链接各程序文件时,链接顺序大体是按照输入文件顺序来的,所以在系统内核程序的链接命令中,head.o
必须作为第一个链接文件,也就是Makefile文件里面将header与kernel编译成system时,head.o
要放在kernel.o
前面
2 内核头
接着链接脚本来看内核头文件head.S
(这的S一定得是大写!),第一条指令是文本段里面_start
开头的,这块的汇编用的AT&T格式的,因为要使用GNU提供的汇编器GAS,之前用的是Intel的,稍微有那么点区别
1 | .section .text # 此处是文本段 |
再回过头来看看全局描述表和段信息的定义
1 | .section .data # 此处是数据段 |
64bit kernel code段
.quad 0x0020980000000000
0000 0000 0010 0000 1001 1000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
Base: 0000 0000 0000 0000 0000 0000 0000 0000,G=0,E=0,这是设置成平坦模式,段大小范围从1 byte到1 MByte,步长为一个字节
Limit: 0000 0000 0000 0000
64bit user code段
.quad 0x0020f80000000000
0000 0000 0010 0000 1111 1000 0000 0000,G=0,E=0..
0000 0000 0000 0000 0000 0000 0000 0000
Base: 0000 0000 0000 0000 0000 0000 0000 0000
Limit: 0000 0000 0000 0000
32bit kernel code段
.quad 0x00cf9a000000ffff
0000 0000 1100 1111 1001 1010 0000 0000,G=1,E=1,4GB寻址能力
0000 0000 0000 0000 1111 1111 1111 1111
Base: 0000 0000 0000 0000 0000 0000 0000 0000
Limit: 1111 1111 1111 1111
这里有必要提一点的是,虽然code和data段描述符在32和64差不多;但是idt和tss的描述符64和32就不一样了,而是128bit,不过暂时看起来是用不上的
创建并初始化页表以及页表项
1 | .align 8 # 将下一条语句进行内存对齐 和C语言中结构体内存对齐类似 |
显然这是一个4级分页,每个页表项是8B,每个页大小是2MB。页表项设置可参考 Intel手册第三卷 4.5 4-LEVEL PAGING AND 5-LEVEL PAGING
PML4E
.quad 0x102007
0000 0000 0001 0000 0010 0000 0000 0111
P(bit0):1,must be 1 to reference a PML4 table
R/W(bit1):1,Read/write; if 0, writes may not be allowed to the 256-TByte region controlled by this entry
U/S(bit2):1,User/supervisor; if 0, user-mode accesses are not allowed to the 256-TByte region controlled by this entry
M-1:12:1 0000 0010,Physical address of 4-KByte aligned PML4 table referenced by this entry
PDPTE
.quad 0x103003
0000 0000 0001 0000 0011 0000 0000 0011
P(bit 0):must be 1 to reference a page directory
R/W(bit1):1,Read/write; if 0, writes may not be allowed to the 1-GByte page referenced by this entry
U/S(bit2):0,User/supervisor; if 0, user-mode accesses are not allowed to the 1-GByte page referenced by this entry
(M–1):12:1 0000 0011,Physical address of 4-KByte aligned page directory referenced by this entry
PDE
.quad 0xe0000083
1110 0000 0000 0000 0000 0000 1000 0011
P(bit 0):1,must be 1 to map a 2-MByte page
R/W(bit1):1,Read/write; if 0, writes may not be allowed to the 2-MByte page referenced by this entry
U/S(bit2):0,User/supervisor; if 0, user-mode accesses are not allowed to the 2-MByte page referenced by this entry
(M–1):21:1110 0000 0000,Physical address of the 2-MByte page referenced by this entry
PDE中的俩注释就是将物理地址0xe0000000映射到0x00a00000;将物理地址0xe0600000映射到0x1000000
3 内核主函数
内核主函数相当于应用程序的主函数,不同之处在于这个内核主函数一般来说不会返回。内核主函数负责调用系统各个模块初始化函数,这些模块初始化结束后,会创建第一个进程init,并将控制权交给init进程(太激动了,终于到C了..不用再受汇编摧残了..)
下面的程序使得进入kernel显示彩条,为了在屏幕上显示颜色,则必须通过帧缓冲存储器(Frame Buffer)来完成,帧缓存的每个存储单元对应屏幕上的一个像素,整个帧缓存对应一幅帧图像,帧缓存是对每个像素点操作,详见VBE..
此前loader设置显示芯片的显示模式(模式号:0x180,分辨率:1440*900,颜色深度:32bit),head.S
将帧缓存物理基地址(0xe0000000)映射到0xffff800000a00000处。
32bit像素点帧缓存格式:0~7位表示蓝色,8~15代表绿色,16~23位代表红色,24~31保留
屏幕坐标原点(0, 0)位于左上角
1 | void Start_Kernel(void) { |
最后瞅瞅编译文件Makefile
1 | all: system |
没啥特别需要了解的..
4 实验
执行完bochs后,显示了四条彩带
然后看了下gdt和cr3,完全都对的上
1 | <bochs:33> creg |