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

2.2.2 Hello China的引导过程

在目前Hello China版本的实现中,整个系统是从软盘启动的。根据功能的不同,目前的Hello China在PC上的实现由4个二进制模块组成,如表2-2所示。

表2-2 Hello China各组成模块

其中,上述4个二进制模块被一个特定的程序FMTLDRF.COM写到一张标准软盘的固定扇区上(BOOTSECT.BIN占据了第一个扇区)。BOOTSECT.BIN是引导扇区,该模块被BIOS加载到内存之后,会进一步加载剩余的模块(REALINIT.BIN、MINIKER.BIN和MASTER.BIN),完成后,跳转到REALINIT.BIN模块处开始执行。

对于一张大小为1.44MB的高密度软盘,在格式化的时候被分成了两个盘面,分别对应软驱的两个磁头,每个盘面又进一步被分成了80个磁道,每个磁道又被分成18个扇区,每个扇区的大小是512B。Hello China的每个模块在软盘上的位置(被FMTLDRF. COM写入)如表2-3所示。

表2-3 各组成模块在引导盘上的布局

根据上述布局BOOTSECT.BIN模块调用BIOS提供的软盘读写调用(中断),把REALINIT.BIN等三个模块依次读入内存。读入内存的位置为0x1000(即4KB偏移处)。下面是BOOTSECT.BIN模块中相关代码(用汇编语言编写,用NASM编译)。

    gl_start:                     ;;Start label of the boot code.
        cli                       ;;Mask all maskable interrupts.
        mov ax,DEF_ORG_START
        mov ds,ax
        mov ss,ax
        mov sp,0xfff0
        cld
        mov si,0x0000
        mov ax,DEF_BOOT_START
        mov es,ax
        mov di,0x0000
        mov cx,0x0200              ;;The boot sector's size is 512B
        rep movsb
        mov ax,DEF_BOOT_START       ;;Prepare the execute context.
        mov ds,ax
        mov es,ax
        mov ss,ax
        mov sp,0x0ffe
        jmp DEF_BOOT_START : gl_bootbgn  ;;Jump to the DEF_BOOT_START to
execute.

上述代码是BOOTSECT.BIN模块的开始部分,该部分代码的功能是把BOOTSECT.BIN模块从内存的0x07C0偏移处搬移到0x9F000处(即636KB处),然后跳转到该位置正式开始执行。其中,DEF_ORG_START是预定义的一个宏,定义为0x07C0,即引导扇区被BIOS加载到内存后的地址,而DEF_BOOT_START则被定义为0x9F00,是BOOTSECT.BIN被重新搬移到的位置。

gl_bootbgn标号处的汇编语句打印出一串提示信息,然后调用np_load过程,完成操作系统相关模块(即除BOOTSECT.BIN之外的三个模块)的加载过程,代码如下。

gl_bootbgn:
    call np_printmsg           ;;Print out the process message.
    call np_load
    jmp DEF_RINIT_START / 16 : 0 ;;Jump to the real mode initialize code.

加载完毕之后,使用一个远跳转指令,跳转到REALINIT.BIN模块处开始执行。下面是加载过程np_load的相关代码。

np_load:                      ;;This procedure use the int 13 interrupt
                            ;;call,load the operating system kernal
                            ;;into memory.
    push es
    mov ax,0x0000
    mov es,ax
    mov bx,DEF_RINIT_START
    xor cx,cx
.ll_start:
    mov ah,0x02
    mov al,0x02              ;;Load 2 sector for one time.
                          ;;So,the sector's number of per track,
                          ;;the total sectors of the whole system
                          ;;code must be 2 times.
    mov ch,byte [curr_track]
    mov cl,byte [curr_sector]
    mov dh,byte [curr_head]
    mov dl,0x00
    int 0x013
    jc .ll_error
    dec word [total_sector]
    dec word [total_sector]
    jz .ll_end
    cmp bx,63*1024            ;;If the buffer reachs 64k boundry,we must
                          ;;reinitialize it.
    je .ll_inc_es
    add bx,1024
    jmp .ll_continue1
.ll_inc_es
    mov bx,es
    add bx,4*1024
    mov es,bx                 ;;Update the es register to another 64k b-
                        ;;oundry.
    xor bx,bx
.ll_continue1:
    inc byte [curr_sector]
    inc byte [curr_sector]
    cmp byte [curr_sector],DEF_SECT_PER_TRACK
                                  ;;If we have read one track,
                                  ;;must change the track number.
    jae .ll_inc_track
    jmp .ll_start
.ll_inc_track:
    mov bp,es
    mov word[tmp_word],bp               ;;Print out the process message.
                                  ;;Because of the boring of the
                                  ;;bios call,it use  registers
                                  ;;to pass parameter,so here,
                                  ;;we must save the es register
                                  ;;to a variable.
    pop es
    call np_printprocess
    push es
    mov bp,word [tmp_word]
    mov es,bp
    mov byte [curr_sector],0x01
    inc byte [curr_track]
    cmp byte [curr_track],DEF_TRACK_PER_HEAD
    jae .ll_inc_head
    jmp .ll_start
.ll_inc_head:
    mov byte [curr_track],0x00
    inc byte [curr_head]
    cmp byte [curr_head],0x02
    jae .ll_end
    jmp .ll_start
.ll_error:                    ;;If there is an error,enter a dead loop.
    mov dx,0x03f2
    mov al,0x00
    out dx,al
    pop es
    call np_deadloop
.ll_end:
    mov dx,0x03f2              ;;The following code shut off the FDC.
    mov al,0x00
    out dx,al
    pop es
    ret                        ;;End of the procedure.

这段代码比较长,但功能比较简单,就是完成REALINIT.BIN、MINIKER.BIN和MASTER.BIN三个模块的加载工作。代码之所以较长,是因为这三个模块分布在软盘的一个整面上,跨越了多个磁道和多个扇区,在加载过程中,必须判断是否跨越磁道和盘面。在加载的过程中,每加载两个扇区,就需要打印出一个点,以提示用户加载正在进行。其中,curr_sector、curr_track、curr_head是定义的三个字节变量,用于存储当前正在读写的起始扇区号、磁道号和盘面号。每完成一次读盘操作,np_load过程就递增curr_sector变量(一次递增2),若该变量超过了18(每磁道扇区数),则重新初始化该变量为零,并递增curr_track变量。相应地,若curr_track变量达到了80(每盘面最大磁道数),则重新初始化该变量和curr_sector变量,并递增curr_head变量。

上述代码中,DEF_RINIT_START是一个预定义的宏,定义为0x1000(4K),这也是三个操作系统模块被加载到内存后的初始地址。需要注意的是,为了方便,BOOTSECT.BIN不区分加载的具体模块,而采取一次读取的策略,把磁盘上REALINIT.BIN等三个模块一次性读入内存。这也是为什么MINIKER.BIN实际大小是48KB,而写到磁盘上时,却占用了64KB空间的原因,就是为了满足三个模块在磁盘上的相对位置和内存中的相对位置能够保持一致。

具体的磁盘读写操作所采用的BIOS调用,在此不作赘述,读者可通过查阅BIOS调用手册获取相关信息。