2.2.4 保护模式下的初始化
实模式下的初始化完成之后,CPU已经进入保护模式,并跳转到MINIKER.BIN模块的开始处继续执行。下面是MINIKER.BIN模块的开始部分代码。
bits 32 ;;The mini-kernal is a pure 32 bits OS kernel. org 0x00100000 mov ax,0x010 mov ds,ax mov es,ax jmp gl_sysredirect ;;The first part of the mini-kernal image is ;;data section,so the first instruction must ;;to jump to the actually code section,which ;;start at gl_sysredirect.
上述代码明确地指示编译器编译成32位指令代码,即保护模式下指令,且偏移地址设定为1MB开始处。MINIKER.BIN模块也是被编译成纯二进制可执行文件格式的,没有任何特定的文件头部信息。
代码重新初始化DS和ES寄存器,在REALINIT.BIN中,转换到保护模式之后,仅仅初始化了CS寄存器(JMP指令完成),此处对DS和ES寄存器进行初始化,为代码的继续执行建立环境。然后,又通过一条跳转指令跳转到标号为gl_sysredirect的指令处开始执行。在上述代码和gl_sysredirect标号之间,定义了一些全局的数据结构,包括全局描述表、中断描述表等。
下面是gl_sysredirect标号处的代码。
align 4 gl_sysredirect: ;;Redirect code of mini-kenal,this code ;;moves the mini-kernal from con_org_st- ;;art_addr to con_start_addr. mov ecx,con_mini_size+con_mast_size shr ecx,0x02 mov esi,con_org_start_addr ;;Original address. mov edi,con_start_addr ;;Target address. cld rep movsd mov eax,gl_initgdt jmp eax ;;After moved mini-kernal to the start ;;address,the mini-kernal then jump to ;;the start entry,labeled by gl_initgdt.
上述代码首先把MINIKER.BIN和MASTER.BIN两个模块从最初位置(加载后的位置,位于低端的1MB内存范围内)重新搬移到1MB物理内存开始处(此时CPU工作在32位模式下,可以访问32位地址空间的任何位置)。其中,con_mini_size和con_mast_size是两个预定义的宏,分别指出了MINIKER.BIN和MASTER.BIN两个模块的长度,把这两个模块的长度相加,便是要搬移的大小。然后执行movsd(转移32位的字符串)指令,并以rep为前缀,一次性把MINIKER.BIN和MASTER.BIN搬移到指定位置。搬移完成后,内存的布局如图2-7所示。
图2-7 搬移后的内存布局
上述搬移完成之后,紧接着又通过一条跳转指令(绝对位置跳转),跳转到标号为gl_initgdt的位置开始运行。需要注意的是,这时候MINIKER.BIN已经被搬移到1MB开始处,因此,后续的执行必须也相应地调整到新的MINIKER.BIN所在的位置。若通过通常的跳转指令,直接以标号为参数,则不能正常工作,因为标号为参数,只能是一个相对位置的跳转,即跳转到当前位置加上当前位置到标号的偏移量所在的位置,而不是跳转到标号的绝对位置。因此,必须采用绝对跳转,即直接跳转到标号所在位置。这样,采用寄存器作为参数来执行JMP指令,可以实现绝对跳转。详细信息,可参考Intel CPU的指令手册。
顾名思义,gl_initgdt标号开始的代码,应该是用来完成初始化GDT的,如下所示。
gl_initgdt: ;;The following code initializes the GDT ;;and all of the segment registers. lgdt [gl_gdtr_ldr] ;;Load the new gdt content into gdt regis- ;;ter. mov ax,0x010 mov ds,ax mov ax,0x018 mov ss,ax mov esp,DEF_INIT_ESP ;;The two instructions,mov ss and mov esp ;;must resides together. mov ax,0x020 mov es,ax mov fs,ax ;;Initialize the fs as the same content as ;;es.If need,we can change the fs's value. mov ax,0x020 mov gs,ax jmp dword 0x08:gl_sysinit ;;A far jump,to renew the cs register's value, ;;and clear the CPU's prefetched queue, ;;trans the control to the new squence.
这段代码重新初始化了GDT寄存器,这时候的初始化,不但对DS、ES等寄存器做了初始化,而且还初始化了SS寄存器和ESP寄存器,这样后续代码就可以执行调用(CALL)指令了。需要注意的是,上述对各段寄存器的初始化,虽然采用了不同的段描述符,但所有段描述符的基址和长度都是相同的,因为目前Hello China的实现采用了平展模式,每个段都可以完整覆盖整个线性地址空间。详细信息,请参考第5章。
对于堆栈寄存器(ESP)的初始化,是直接把一个预先定义的值DEF_INIT_ESP装入了ESP寄存器,这个值目前定义为0x13FFFFFF,即物理内存20MB地址处。
完成GDT的初始化,以及相关寄存器的初始化后,又通过一条远跳转指令,跳转到gl_sysinit处继续执行。这条指令不但完成了执行路径的转移,而且更新了CS寄存器,并更新了CPU的内部上下文,包括指令预取队列、CACHE等。下面是gl_sysinit的实现。
gl_sysinit: ;;The start position of the init process.
mov eax,gl_trap_int_handler
push eax
call np_fill_idt ;;Initialize the IDT table.
pop eax
lidt [gl_idtr_ldr] ;;Load the idtr.
call np_init8259 ;;Reinitialize the interruptcontroller.
sti
nop
nop
nop
mov eax,con_mast_start
jmp eax
上述代码调用一个本地过程np_fill_idt,用来完成中断描述符表的初始化,然后初始化idtr寄存器(lidt指令),并重新初始化中断控制寄存器,这样做的目的是在实模式下,中断控制器的中断向量,是从零开始的,而一旦转移到保护模式下,外部中断却是从32开始,因此,必须对中断控制器进行重新编程,以产生新的中断向量。
完成上述功能后,该过程使用中断(sti指令),并采用一个绝对跳转指令跳转到con_mast_start处开始执行。con_mast_start是一个预定义的宏,指明了MASTER.BIN模块所在的内存位置(物理内存位置)。至此,MINIKER.BIN模块执行完毕,控制转移到MASTER.BIN模块。
此时,采用汇编语言部分实现的功能已经执行完毕,这部分也是与特定硬件平台相关的。后续所有功能,都是采用C语言实现的并与机器无关部分功能。
在嵌入式开发领域,往往把整个软件分成两部分:BSP和应用代码部分。其中BSP是单板支撑包的缩写,一般情况下,在BSP中,实现了特定硬件相关的初始化代码,以及特定硬件的驱动程序,这样做是为了提升整个系统的可移植性。因为在把代码移植到不同的硬件平台的时候,从理论上说,只需要修改BSP部分就可以了(实际上远没有这么方便)。在Hello China的实现中,可以把REALINIT.BIN和MINIKER.BIN两个模块看作是BSP,因为在这两个模块中,完成了对特定硬件平台(PC)的硬件初始化功能,而且在MINIKER.BIN中,还实现了针对标准PC显示器和PC键盘的驱动程序,这样在MASTER.BIN模块中,就不用考虑这些硬件的差异,而直接通过特定的接口,调用硬件提供的服务。
MASTER.BIN模块是Hello China的核心模块,是所有操作系统核心功能的实现模块。该模块也完成一些初始化工作,但这些初始化工作不是特定硬件的初始化,而是操作系统正常工作的核心数据结构和核心数据对象的初始化。在完成这些初始化工作后,在PC平台上,操作系统启动一个Shell线程,用来完成跟用户的交互,到达这一步后,操作系统才算成功启动完毕。