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

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线程,用来完成跟用户的交互,到达这一步后,操作系统才算成功启动完毕。