咱们的内容都是连栽的,如果您没看过我之前的文章,本节您是看不懂的。
接上节。
介绍完内核初始化的函数kernel_init后,本节代码部分还差一点点没说啦,下面看代码:
…略
179 ;在开启分页后,用gdt新的地址重新加载
180 lgdt [gdt_ptr] ; 重新加载
181
182 ;;;;;;;;;;;;;;;;;;;;;;;;;;;; 此时不刷新流水线也没问题 ;;;;;;;;;;;;;;;;;;;;;;;;
183 ;由于一直处在32位下,原则上不需要强制刷新,;经过实际测试没有以下这两句也没问题.
184 ;但以防万一,还是加上啦,免得将来出来莫句奇妙的问题.
185 jmp SELECTOR_CODE:enter_kernel ;强制刷新流水线,更新gdt
186 enter_kernel:
187 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
188 call kernel_init
189 mov esp, 0xc009f000
190 jmp KERNEL_ENTRY_POINT ; 用地址0x1500访问测试,结果ok
…略
在代码的开头是咱们之前已经完成的重新加载GDT,就是将原来GDT的基地址0x900变成0xc0000900后重新加载。按理说,当前已经是32位环境啦,而且内核也是32位程序,不需要“显式”地清空流水线。实话实说,之前loader.S中是没有第185~186行,而且经我简单测试后其运行结果也是正确的。不过,在调试过程中有可能会碰到稀奇古怪的问题,当然这绝对是人为的错误,不要轻易怀疑计算机。对于内核中这类“灵异”事件,咱们当然希望少碰到,某些bug真的会让人调试好多天,所以为了保险起见,还是用无条件跳转指令刷新了流水线,请大家知晓。
在进入内核之后,我们用的栈也要重新规划了,栈起始地址不能再用之前的0xc0000900啦。为了方便编写程序,我们在进入内核前将栈指针改成我们期待的值,在第189行,我们将esp改成了0xc009f000。此地址的选择也是根据之前的内存布局图。也许有同学会说,为什么不把esp选为0x9fc00,这才是最合理的。没错,您说的对,我们都是会过日子的人,0x9fc00确实是最省空间的选择,这样做,以后的程序也不会出错。但这牵扯到以后要说的pcb,即程序控制块(咱们在以后线程相关章节会细说pcb,这里仅要求大家对此有个浅表的了解即可),每个pcb都是自然页,也就是要求4KB对齐,即4KB的范围是0x000~0xfff,而不是类似0x333~0x1332这样的范围。我们打算在4KB内的最高地址做为栈底,如果以0x9fc00做为栈底,虽然不出会什么问题,但它显得太个性了,比其它pcb少了0x400字节。所以,为了统一pcb大小,我们这里选择栈底的要求是:它接近最大可用地址0x9fbff,并且以4KB对齐,所以0x9f000是最合适的。
为了打消部分同学的疑虑,容我再多说两句。我担心有同学可能会这样想,咱们loader加载的物理地址是0x900,loader中使用的栈的栈底是0x900,栈是往下发展的,在loader以后的压栈操作中,并不会破坏掉loader自身,似乎这种“完美”的方案可以在咱们的kernel中延续,也就是为何不让kernel的栈为入口地址之下?比如咱们这里的入口地址是0xc0001500,也让栈底esp为该值有何不妥。不管怎么说,如果有这种想法,说明您是个爱动脑的同学,我会为您悄悄点赞。其实loader.bin是纯二进制文件,而kernel是elf格式的二进制文件,这两者的区别是elf比纯二进制文件多了文件头,纯二进制文件相当于elf文件中的所有段(segment)的集合。在前面我们分析过啦,程序的入口地址是很可能会在段中的,并不是在段的起始,就拿咱们的kernel.bin来说,它代码段的入口地址是0xc0001500,起始地址却是0xc0001000,入口并不在段的开头。其实0xc0001000~0xc0001500之间的部分是文件头,并不是真正的代码。真要把esp赋值为0xc0001500,如您所料,将来内核中有压栈操作时一定会破坏0xc0001000~0xc0001500之间的部分,虽然它只是文件头不是实际代码,似乎破坏了也无关紧要,但这样做确实不美啊。您看,咱们的内核预计70KB左右,起始物理地址是0x1500,而栈底若为0x9f000,这0x9f000-0x1500约为630KB的空间,在正常情况下,栈是不会碰撞到内核的,这样多省心。所以,兄弟们,我看还是算了吧,咱就选个高地址0x9f000,就它了。
啰嗦过后,loader通过第190行的跳转指令进入内核。这里所见的KERNEL_ENTRY_POINT是boot/include/boot.inc中定义的宏,其值为0xc0001500,它正是我们用ld命令链接kernel时指定的代码段地址,这个宏必须要与其一致才行。
经过这样一番的规划后,现在0x500~0x9fbff可用内存中,咱们自己的文件布局如图:
和内核相关的内容咱们暂告一段落,在本章的结束,咱们说说保护模式下最闪亮的内容——特权