2.2.3 实地址模式下的初始化
Hello China引导完成之后,内存布局如图2-5所示。
图2-5 各模块加载到内存后的布局
图2-5中,所有地址都是物理内存地址,由于这时候CPU尚工作在实模式下,因此对内存的访问采用段基址加段偏移的方式,形成20位地址,直接定位到物理内存。
引导完成之后,引导程序通过一条JMP指令,跳转到Realinit.bin开始处(0x00001000)开始执行。下面是Realinit.bin的开始部分代码,采用NASM编译,目标格式为BIN格式,即纯粹的二进制可执行文件,不带任何文件头。为了便于理解,我们分段解释。
bits 16 ;;The real mode code. org 0x0000 %define DEF_RINIT_START 0x01000 ;;Start address of this module. ;;This code is loaded into memory by ;;boot sector,and resides at 0x01000. %define DEF_MINI_START 0x02000 ;;The start address of the mini-kernal ;;when it be loaded into memory.
上面部分代码,除了告诉编译器代码工作在实模式(16位)、偏移地址为零外,还定义了两个宏。其中第一个DEF_RINIT_START,用于指出realinit.bin被加载到内存后的物理地址;第二个宏DEF_MINI_START,定义了miniker.bin模块被加载到内存后的物理地址。这两个宏定义,一个用来计算代码段寄存器的值,一个用来作为跳转目标地址,即在realinit.bin执行完毕后,跳转的目标地址。
gl_initstart: mov ax,DEF_RINIT_START ;;The following code prepare the execute ;;context. shr ax,4 mov ds,ax mov es,ax mov ss,ax mov sp,0x0fff
上述代码初始化了代码段寄存器、堆栈段寄存器、数据段寄存器和扩展段寄存器。其中,CS、ES、SS和DS初始化为相同的值,都是指向realinit.bin在内存中的起始地址。realinit.bin可以不用考虑自己在内存中的位置,直接从零偏移开始执行。对于堆栈寄存器的值,设置为0x0FFF,即相对于段寄存器,偏移约4KB处,如图2-6所示。
图2-6 SP和SS寄存器的初始化
因为按照目前的实现,为realinit.bin预留了4KB的空间,但实际上,该模块的大小尚不超过2KB,因此,4KB空间的上面2KB作为堆栈区是不会有问题的。
mov ax,okmsg call np_strlen mov ax,okmsg call np_printmsg mov ax,initmsg call np_strlen mov ax,initmsg call np_printmsg
上述代码调用realinit模块里面定义的几个函数,打印出一些字符串,提示操作的进度,以及操作的结果。需要注意的是,在Hello China正常启动的过程中,这些字符串是看不到的,不是因为没有打印出来,而是因为该模块内定义的功能很快就执行完了,转而跳转到miniker模块,而在miniker.bin模块的开始处马上做了一个清屏操作,所以这些信息在正常情况下是看不到的。但若执行过程中发生错误,进入了死循环,这些信息就可以看到了。
;;The following code initializes the system hardware. call np_init_crt ;;Initialize the crt display. call np_init_keybrd ;;Initialize the key board. call np_init_dmac ;;Initialize the DMA controller. call np_init_8259 ;;Initialize the interrupt controller. call np_init_clk ;;Initialize the clock chip. call np_get_syspara ;;Gather the system parameters.
上述代码初始化了系统中的一些关键硬件。在基于PC的实现中,初始化的硬件包括CRT显示器、键盘、DMA控制器、中断控制器(8259芯片)、时钟等,并收集了系统的一些硬件配置信息,比如物理内存的大小等。上述每个操作都对应realinit.bin模块内定义的一个函数。若把Hello China移植到其他的非PC系统,也可以在这个地方对特定目标系统的硬件进行初始化。
call np_act20addr ;;First,activate the above 12 address lines.
上面这个过程调用用来激活A20地址线。这在PC上十分关键,因为只有激活了A20地址线,才能确保CPU的实模式下,可以访问到所有的32位物理地址。这其中的原因在很多资料上都有描述,在此不再详述。
xor eax,eax mov ax,ds shl eax,0x04 add eax,gl_gdt_content ;;Form the line address of the gdt content. mov dword [gl_gdt_base],eax lgdt [gl_gdt_ldr] ;;Now,load the gdt register.
上述代码完成gdt寄存器(全局描述表寄存器)的初始化。gdt寄存器指向一个全局描述表,这个表定义了CPU保护模式下正常工作所需要的段。
mov eax,cr0 or eax,0x01 ;;Set the PE bit of CR0 register. mov cr0,eax ;;Enter the protected mode. jmp dword 0x08 : DEF_MINI_START ;;Transant the control to Mini Kernal.
上面的代码完成CPU工作模式的转移功能,即从实地址模式转移到保护模式。在IA32 CPU中,有一个控制寄存器(CR0),该寄存器的第一位(PE,Protected Enable)控制了CPU工作在哪种模式下。若该位为1,则CPU工作在保护模式下,否则工作在实地址模式下。
完成模式转换之后,通过一条远转移指令,转移到miniker.bin模块的开始处,继续运行。这条指令不但完成执行路径的转换,而且还完成CPU上下文的刷新工作,比如,刷新CPU的指令预取队列,刷新CPU的本地Cache等。需要注意的是,上述指令是在保护模式下运行的,0x08指明了代码段在段描述表(全局描述表)中的偏移,而DEF_MINI_START则指明了miniker.bin模块在内存中的偏移地址。在Hello China的定义中,代码段的基址是零,因此,根据代码段基址和偏移,形成的目标地址就是DEF_MINI_START。
到此为止,realinit.bin模块就执行完了,总结一下,该模块主要完成下列工作。
(1)初始化PC机中关键的系统硬件;
(2)转换到保护模式;
(3)跳转到MINIKER.BIN模块开始处,继续执行。