嵌入式操作系统
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

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模块开始处,继续执行。