Hello China引导完成,即引导扇区把所有操作系统核心模块成功加载到内存之后,内存布局如图3-5所示。
图3-5 引导完成后的内存布局
该图是按照内存地址从小到大的顺序,从下往上画的。图中,所有地址都是物理内存地址,由于这时候CPU尚工作在实模式下,因此对内存的访问采用段基址加段偏移的方式,形成20位地址,直接定位到物理内存。
引导完成之后,引导程序通过一条JMP指令,跳转到Realinit.bin开始处(0x1000)开始执行。下面是Realinit.bin开始部分的代码,采用NASM编译,目标格式为BIN格式,即纯粹的二进制可执行文件,不带任何文件头。为了便于理解,我们分段解释:
上述代码,除了告诉编译器代码工作在实模式(16位)、偏移地址为0外,还定义了两个宏,第一个宏DEF_RINIT_START,用于指出realinit.bin被加载到内存后的物理地址;第二个宏DEF_MINI_START,定义了miniker.bin模块被加载到内存后的物理地址。这两个宏定义,一个用来计算代码段寄存器的值,一个用来作为跳转目标地址,即在realinit.bin执行完后,进一步跳转的目标地址。
上述代码初始化了代码段寄存器、堆栈段寄存器、数据段寄存器和扩展段寄存器。其中,CS、ES、SS和DS初始化为相同的值,都是指向realinit.bin在内存中的起始地址。这样realinit.bin就不用考虑自己在内存中的位置,直接从0偏移开始执行。对于堆栈寄存器的值,设置为0x0FFF,即相对于段寄存器,偏移约4KB,如图3-6所示。
图3-6 SP和SS寄存器的初始化
需要记住的是,在代码执行过程中,堆栈指针是向下递减的。按照目前的实现,为realinit.bin预留了4KB的空间,但实际上,该模块的大小尚不超过2KB。因此,把堆栈指针sp设置为0x0FFF,意味着有2KB左右的堆栈空间使用,这是足够的。
上述代码调用realinit模块里面定义的几个函数,打印出一些字符串,提示操作进度及结果。需要注意的是,在Hello China正常启动过程中,这些字符串是看不到的,不是因为没有打印出来,而是因为该模块内定义的功能很快就执行完了,转而跳转到miniker模块。而在miniker.bin模块的开始处马上做了一个清屏操作,所以这些信息在正常情况下是看不到的。但若执行过程中发生错误,进入了死循环,这些信息就可以看到了。
上述代码初始化了系统中的一些关键硬件。在基于PC的实现中,初始化的硬件包括CRT显示器、键盘、DMA控制器、中断控制器(8259芯片)、时钟等,并收集了系统的一些硬件配置信息,比如物理内存的大小等。上述每个操作都对应realinit.bin模块内定义的一个函数。若把Hello China移植到其他非PC系统,也可以在这个地方对特定目标系统的硬件进行初始化。在Hello China V1.75版针对PC的实现中,这些代码大部分都比较简单,尽量保留了BIOS的初始设置。
上面这个过程调用用来激活A20地址线。这在PC上十分关键,因为只有激活了A20地址线,才能确保CPU在保护模式下,可以访问到所有的32位物理地址。其中的原因在很多资料上都有描述,在此不再详述。
上述代码完成gdt寄存器(全局描述表寄存器)的初始化。gdt寄存器指向一个全局描述表,这个表定义了CPU保护模式下正常工作所需要的段。在初始化全局描述表寄存器的时候,实际上是用全局描述表的物理地址填入了该寄存器。因此首先要计算出GDT的物理地址,计算方式就是由当前数据段地址左移4位,再加上GDT在段内的偏移。(www.xing528.com)
gl_gdt_content是GDT的定义,这个定义如下:
按照Intel的定义,每个段描述表项占用8B,其结构如图3-7所示。
图3-7 段描述符的结构
其中下面的方框是低字节部分,上面的方框是高字节部分。里面的几个关键字段含义如下:
(1)Segment Limit:段界限,即这个段的最大尺寸,以4KB为单位。在Hello China的实现中,我们一般设置为4GB,即整个CPU的寻址空间。
(2)Base Address:段基址,这个段在内存中的起始地址。
(3)Type:段类型,如代码段、数据段等。
(4)DPL:描述符权限级别,在Hello China的实现中,统一设置为0。
更详细的信息,请参考本书参考文献[1]。同时按照Intel的定义,全局描述表中的第一个表项必须是空表项(全为0),然后才是实际的描述表项。Hello China的实现中,所有的段的基址都是0,界限都是4GB,优先级都是0(即系统级)。不同的只是段类型,有的是代码段,有的是数据段。按照这样的填写方式,形成了上面gl_gdt_content处的GDT定义。填写GDT,并完成GDT寄存器的加载,是为转移到保护模式做准备。GDT寄存器成功加载之后,就可以转移到保护模式了:
上面的代码完成CPU工作模式的转移功能,即从实地址模式转移到保护模式。在IA32CPU中,有一个控制寄存器(CR0),该寄存器的第一个比特(PE,Protected Enable)控制了CPU工作在哪种模式下。若该比特为1,则CPU工作在保护模式下,否则工作在实地址模式下。
完成模式转换之后,通过一条远转移指令,转移到miniker.bin模块的开始处,继续运行。这条指令的作用,不但完成执行路径的转换,而且还完成CPU上下文的刷新工作,比如,刷新CPU的指令预取队列,刷新CPU的本地Cache等。需要注意的是,上述指令是在保护模式下运行的,0x08指明了代码段在段描述表(全局描述表,即上述gl_gdt_content标号处的定义)中的偏移,而DEF_MINI_START则指明了miniker.bin模块在代码段内的偏移地址。在Hello China的定义中,代码段的基址是0,因此,根据代码段基址和偏移,形成的目标地址就是DEF_MINI_START。
到此为止,realinit.bin模块就执行完了,总结一下,该模块主要完成下列工作。
(1)初始化PC中关键的系统硬件。
(2)转换到保护模式。
(3)跳转到MINIKER.BIN模块开始处,继续执行。
接下来CPU就正式进入了保护模式,之后的初始化工作,就完全运行在保护模式下了。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。