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调用手册获取相关信息。