[Make MyOS] Starting kernel
上一节 loader 程序装载完 kernel 后,执行了jmp SelectorCode64:OffsetOfKernelFile命令跳转到 kernel 部分了,意味着 Loader 将处理的控制权交给了 Kernel
处理器把控制权交给 kernel 后,kernel 最先执行的是内核执行头程序。内核执行头程序是一段精心设计的汇编代码,而且必须借助特殊的编译链接方法才能得到最先执行。
内核头程序负责为操作系统创建段结构和页表结构,设置某些结构的默认处理函数、配置关键寄存器等工作。在完成上述工作后,依然要借助远跳指令才能进入系统内核主程序。
如何将内核执行头程序编译生成到整个内核程序文件的起始处?手动编写内核程序的链接脚本,在内核程序的链接过程中,链接器会按照链接脚本描述的地址空间布局,把编译好的各个程序片段填充到内核程序文件中。
看完有点懵,都是内核程序为啥要咱手动指定谁先谁后?大概是因为内核头是汇编写的,内核是 c 写的.. 要让 head 先执行就得在 lds 中指定第一条指令是啥.. 编译部分参考哈: ld - 链接脚本学习笔记与实践过程
¶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 |