Hello China的V1.75版本,可以支持从FAT32和NTFS格式的硬盘分区上启动,也支持从软盘启动。而且在虚拟机上使用时,使用虚拟软盘启动最为简便。本节将以软盘启动过程为例,详细讲解Hello China的加载过程。之所以以软盘引导方式为例讲解,是因为软盘引导方式相对简单直观,而且无需涉及FAT32和NTFS等文件系统的内容。当然我们也可以以硬盘引导方式为例来解释其加载过程,但这需要至少几十页的篇幅。考虑到系统加载不是本书核心内容(本书核心内容为操作系统内核机制的实现),因此我们从简处理。如果读者对硬盘启动的方式感兴趣,则可以查阅[kernel/arch/sysinit]目录下的hdbs.asm和ntfsbs.asm两个文件,它们分别是FAT32分区和NTFS分区的引导扇区源代码文件。当然,如果读者对FAT32文件系统和NTFS文件系统不熟悉,阅读这两个文件会有一些困难,可先阅读本书第12章,或者通过其他途径对FAT32和NTFS熟悉后,再阅读这两个文件。
目前版本的Hello China操作系统核心由四个二进制模块组成,见表3-1。
表3-1 Hello China各组成模块
上述模块被一个程序FMTLDRF.COM写到一张标准软盘的固定扇区上(BOOTSECT.BIN占据了第一个扇区)。如果是虚拟机,则由VFMaker程序根据上述四个文件,创建一个虚拟软盘文件(/bin/virtualpc/vfloppy.vfd),通过这个虚拟软盘文件引导虚拟机。BOOTSECT.BIN是引导扇区,该模块被BIOS加载到内存之后,会进一步加载剩余的模块(REALINIT.BIN、MINIKER.BIN和MASTER.BIN),完成后,跳转到REALINIT.BIN模块处开始执行。
一张大小为1.44MB的高密度软盘,在格式化的时候被分成了两个盘面,分别对应软驱的两个磁头,每个盘面又进一步被分成了80个磁道,每个磁道又被分成18个扇区,每个扇区的大小是512B。Hello China的每个模块在软盘上的位置(被FMTLDRF.COM写入)见表3-2。
表3-2 各组成模块在引导盘上的布局
之所以把每个模块在软盘上的位置固定,完全是为了引导的方便,这也是预置引导法的一个具体应用。在Hello China的实现中,软盘完全被操作系统模块独占,没有文件系统的概念。
BIOS会把BOOTSECT.BIN(软盘的第一个扇区)文件读入内存,然后执行该文件。BOOTSECT.BIN再根据上述布局,调用BIOS提供的软盘读写调用(中断),把REALINIT.BIN等三个模块依次读入内存。这三个模块在内存中连续分布,其起始地址为0x1000(即4KB偏移处)。下面是BOOTSECT.BIN模块中的相关代码(用汇编语言编写,NASM编译):
(www.xing528.com)
上述代码是BOOTSECT.BIN模块的开始部分,其功能是把自身(BOOTSECT.BIN模块)从内存的0x07C0偏移处搬移到0x9F000处(即636KB处),以腾出空间加载其余三个核心模块。搬迁完成后,跳转到0x9F000处继续执行。其中,DEF_ORG_START是预定义的一个宏,定义为0x07C0,即引导扇区被BIOS加载到内存后的地址,而DEF_BOOT_START则被定义为0x9F00,是BOOTSECT.BIN被重新搬移到的位置。要理解上述汇编代码,需要知道实模式下CPU的寻址方式。在实模式下,CPU是通过段地址加上段内偏移地址,形成最终的物理地址。在与段内偏移地址相加的时候,段地址需要向左移动4比特。反之亦然,在设置段地址寄存器的时候,需要把物理地址向右移动4比特。
gl_bootbgn标号处的汇编语句打印出一串提示信息,然后调用np_load过程,完成操作系统剩余模块(即除BOOTSECT.BIN之外的三个模块)的加载。代码如下:
加载完毕,使用一个远跳转指令,跳转到REALINIT.BIN模块处开始执行。下面是加载函数np_load的相关代码:
这段代码比较长,但功能比较简单,就是完成REALINIT.BIN、MINIKER.BIN和MASTER.BIN三个模块的加载工作。代码之所以较长,是因为这三个模块分布在软盘的一个整面上,跨越了多个磁道和多个扇区,加载过程中必须判断是否跨越磁道和盘面,如果是则需要递增磁道计数器。在加载的过程中,每加载两个扇区,就需要打印出一个点,以提示用户加载正在进行。其中,curr_sector、curr_track、curr_head是定义的三个字节变量,用于存储当前正在读写的起始扇区号、磁道号和盘面号。每完成一次读盘操作,np_load过程就递增curr_sector变量(一次递增2),若该变量超过了18(每磁道扇区数),则重新初始化该变量为0,并递增curr_track变量,相应地,若curr_track变量达到了80(每盘面最大磁道数),则重新初始化该变量和curr_sector变量,并递增curr_head变量。我总感觉这个加载过程有点啰嗦,但考虑到这部分代码的利用率比较低,只是操作系统加载时会用一次,加载完成之后便不用了,因此没有做进一步优化。
上述代码中,DEF_RINIT_START是一个预定义的宏,定义为0x1000(4K),这也是三个操作系统模块被加载到内存后的初始地址。需要注意的是,为了方便,BOOTSECT.BIN不区分加载的具体模块,而采取一次读取的策略,把磁盘上REALINIT.BIN等三个模块一次性读入内存,这也是为什么MINIKER.BIN实际大小是48KB,而写到磁盘上时,却占用了64KB空间的原因,就是为了满足三个模块在磁盘上的相对位置和内存中的相对位置能够保持一致。
具体的磁盘读写操作所采用的BIOS调用,在此不作赘述,读者可通过查阅BIOS调用手册获取相关信息。一旦跳转到0x1000处,加载过程就完成了。剩下的过程是初始化过程,在本章的最后一部分中进行叙述。需要说明的是,不论是从软驱启动还是从硬盘启动,不同的是加载过程。一旦加载完成,都是跳转到0x1000处开始初始化的,即初始化过程是与加载过程完全无关的。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。