一个操作系统的实现(6):加载Loader.bin

一、 FAT12

FAT12是DOS时代就开始使用的文件系统(File System),直到现在仍然在软盘上使用,FAT12软盘的被格式化后为:有两个磁头,每个磁头80个柱面(磁道),每个柱面有18个扇区,每个扇区512个字节空间。所以标准软盘的总空间为:

2 * 80 *18 * 512=1474560B=1440K=1.44M

下面是FAT12的结构图:

一个操作系统的实现(6):加载Loader.bin_第1张图片

1、引导扇区

操作系统之所以认识FAT12格式的磁盘,其秘密就在于逻辑0扇区这512B上。如果这512字节的最后两个字节的内容分别是55和AA(0xAA55低字节在前,高字节在后)的话,BIOS在启动时会将这个扇区读取到0:7C00h-0:7DFFh处,然后跳转到0:7C00h处继续执行指令,操作系统即用此来达到引导系统的目的,而这个磁盘就称为引导磁盘。

操作系统标识FAT12文件系统是因为在逻辑0扇区(即引导扇区)处还存储着一个特定的数据结构,此结构有固定的格式,在操作系统将此磁盘格式化时自动生成,具体数据结构如下表所示:

 

名称

开始字节

长度

内容

参考值

BS_jmpBOOT

0

3

一个短跳转指令

jmp short LABEL_START

nop

BS_OEMName

3

8

厂商名

'ZGH'

BPB_BytesPerSec

11

2

每扇区字节数(Bytes/Sector)

0x200

BPB_SecPerClus

13

1

每簇扇区数(Sector/Cluster)

0x1

BPB_ResvdSecCnt

14

2

Boot记录占用多少扇区

ox1

BPB_NumFATs

16

1

共有多少FAT表

0x2

BPB_RootEntCnt

17

2

根目录区文件最大数

0xE0

BPB_TotSec16

19

2

扇区总数

0xB40

BPB_Media

21

1

介质描述符

0xF0

BPB_FATSz16

22

2

每个FAT表所占扇区数

0x9

BPB_SecPerTrk

24

2

每磁道扇区数(Sector/track)

0x12

BPB_NumHeads

26

2

磁头数(面数)

0x2

BPB_HiddSec

28

4

隐藏扇区数

0

BPB_TotSec32

32

4

如果BPB_TotSec16=0,则由这里给出扇区数

0

BS_DrvNum

36

1

INT 13H的驱动器号

0

BS_Reserved1

37

1

保留,未使用

0

BS_BootSig

38

1

扩展引导标记(29h)

0x29

BS_VolID

39

4

卷序列号

0

BS_VolLab

43

11

卷标

'ZGH'

BS_FileSysType

54

8

文件系统类型

'FAT12'

引导代码及其他内容

62

448

引导代码及其他数据

引导代码(剩余空间用0填充)

结束标志0xAA55

510

2

第510字节为0x55,第511字节为0xAA

0xAA55


下面我们介绍其中的一些变量的含义:

BS_jmpBoot:是跳转指令,偏移0处的跳转指令必须是合法的可执行的基于x86的CPU指令,如:jmp start,这样可以生成3字节长的指令,(加关键字short的短跳转指令的长度是2字节),指向操作系统引导代码部分。Windows和MS-DOS生成的FAT12启动扇区中的跳转指令是短跳转,如:jmp short LABEL_START,然后加一个nop的空指令来保持3字节的长度。

BPB_BytsPerSec:每扇区的字节数,类型是双字节长,标准分区上的每扇区字节数一般是512B, FAT12的格式下设置为512(0x200h)。

BPB_SecPerClus:每簇扇区数,偏移13处,类型是字节,簇是数据存储的最小单位,在FAT12格式下一般为1,即每簇只有1个扇区(512字节)。

BPB_RsvdSecCnt:Boot记录占用多少扇区,即在FAT1之前的 引导扇区,一般情况下,引导扇区占用1个扇区。

BPB_NumFATs:共有多少个FAT表,默认情况下此字段的值为2,也就是有两个FAT表,FAT1和FAT2的内容相同,当FAT1表出错的时候可以使用FAT2来恢复文件分配表。

BPB_RootEntCnt:根目录文件数最大值,默认为224,每个目录条目占用32B的空间,因此根目录的大小为:224*32/512=14,即占用14个扇区。

BPB_TotSec16:扇区总数=0xB40=2880

BPB_FATSz16:每个FAT占用的扇区数=0x9=9,即FAT1占用1—9逻辑扇区,FAT2占用10—18逻辑扇区。

BPB_SecPerTrk:每磁道扇区数=0x12=18,即标准FAT12文件系统中,每个磁道的扇区数就是为18。

BPB_NumHeads:磁头数=0x2=2,该磁盘包括2个磁头,也就是面数是2。

2、FAT表

         FAT1和FAT2是两个完全相同的FAT表,每个FAT占用9个扇区。其中FAT1占用1—9扇区,FAT2占用10—18扇区。具体详细介绍看下面4。

3、根目录区

根目录区的开始扇区号是19,它是由若干个目录条目(Directory Entry)组成,条目最多有BPB_RootEntCnt个,由于根目录区的大小是依赖于BPB_RootEntCnt的,所以长度不固定。

         在本FAT12中,因为BPB_RootEntCnt=0xE0=14*16+0=244,即条目最多为244个,又因为每个条目占用32个字节,故244*32/512=14,即该根目录区占14个扇区,即19—32。

         根目录区中的每个条目占用32字节,它的格式如下图:

一个操作系统的实现(6):加载Loader.bin_第2张图片

这里主要定义了文件的名字,属性,最后写入的时间和日期,文件的开始簇数以及文件大小。

下面我们通过实例来认识这些内容,

1、 首先创建一个虚拟软盘,在这里我们使用WinImage,具体下载地址在我的下载资源中。

打开WinImage:

一个操作系统的实现(6):加载Loader.bin_第3张图片

选择文件—》新建

一个操作系统的实现(6):加载Loader.bin_第4张图片

新建一个虚拟软盘之后,需要向里面添加文件,我们需要提前写好下面几个文件

RIVER.TXT,内容为riverriverriver

FLOWER.TXT,内容为flowerflower………flower,至少要100个flower,使得数据空间大于512个字节,这样该文件将占用两个连续的扇区。

TREE.TXT,内容为treetreetree

再添加一个HOUSE目录,然后在目录\HOUSE下添加两个文本文件:

CAT.TXT,内容为catcatcat

DOG.TXT,内容为dogdogdog

选择映像—》加入,依次加入RIVER.TXT,FLOWER.TXT,TREE.TXT三个文件

一个操作系统的实现(6):加载Loader.bin_第5张图片

一个操作系统的实现(6):加载Loader.bin_第6张图片

映像—》创建文件夹  HOUSE

一个操作系统的实现(6):加载Loader.bin_第7张图片

添加HOUSE目录

一个操作系统的实现(6):加载Loader.bin_第8张图片

双击house,进入house的文件夹内,然后添加CAT.TXT,DOG.TXT两个文件

一个操作系统的实现(6):加载Loader.bin_第9张图片


添加文件完成,然后保存,其中注意事项,保存类型为:虚拟软盘映像(*.vfd),我不知道这个类型与IMG有什么区别,但是我知道这个类型得到的结果是对的。呵呵!文件名为FLOOPY,这样我们就创建了一个虚拟软盘FLOOPY.vfd

一个操作系统的实现(6):加载Loader.bin_第10张图片

然后使用UltraEdit打开FLOOPY.vfd,由于根目录区是从第19扇区开始的,每个扇区512个字节,所以其第一个字节位于偏移19*512=9278=0x2600处,好的,现在就让我们去定位到0x2600处看看到底Directory Entry为何物?

一个操作系统的实现(6):加载Loader.bin_第11张图片

RIVER.TXT的各项值:

一个操作系统的实现(6):加载Loader.bin_第12张图片

在这里,我们只需要关心RIVER.TXT的DIR_FstClus,即文件的开始簇号,由于本FAT12中的BPB_SecPerClus=1,故一个簇为一个扇区,DIR_FstClus=2,意味着该文件的在数据区的起始扇区号为2。在这里需要注意的是,数据区的第一个簇的簇号是2,而不是0或者1,故该文件的数据开始于数据区的第一个簇,也就是第一个扇区。

那么数据区的第一个扇区在哪里呢?

首先计算根目录区所占有的扇区数:

RootDirSectors =((BPB_RootEntCnt*32)+(BPB_BytsPerSec-1))/BPB_BytsPerSec。

之所以分子要加上(BPB_BytsPerSec-1),是为了保证此公式在根目录区无法填满整数扇区时仍然成立。

在本例中,因为BPB_RootEntCnt=224,计算得到根目录区所占有的扇区为14个。所以

数据区开始的扇区号=根目录区开始的扇区号+14=19+14=33。

现在就让我们跳入到第33扇区的偏移量是512*33=16896=0x00004200,让我们看看这里的内容:

一个操作系统的实现(6):加载Loader.bin_第13张图片

果然是riverriverriver。

4、FAT表

在这里,由于RIVER.TXT小于512字节,所以我们不需要FAT表就在数据区中找到了RIVER.TXT的内容,但是对于大于512字节的文件来说,就没有这么简单了,需要使用FAT表来寻找到该文件占用的所有数据区扇区。

下面让我们跳入FAT1的内容,FAT1的开始扇区号是1,故偏移为1*512=512=0x200。

一个操作系统的实现(6):加载Loader.bin_第14张图片

一堆看不懂的符号,好像很多F。其实并不复杂,它有点像是一个位图,其中,每12位成为一个FAT项(FAT Entry),代表一个数据区中的簇。第0个和第1个FAT项始终不使用,第2个FAT项开始表示数据区的每一个簇,也就是说,第2个FAT项表示数据区第一个簇,依次类推。前文说过,数据区的第一个簇的簇号是2,和这里相呼应。

要注意,由于每一个FAT项占12位,包含一个字节和另一个字节的一般,所以觉得特别别扭。具体情况是这样的,假设连续3个字节分别是如图所示:

一个操作系统的实现(6):加载Loader.bin_第15张图片

通常,FAT项的值代表的是文件的下一个簇号,但如果值大于或等于0xFF8,则表示当前簇已经是文件的最后一个簇了。如果值为0xFF7,表示它是一个坏簇。

文件RIVER.TXT的开始簇号为2,对应的FAT表中的值为0xFFF,表示这个簇已经是最后一个。

文件FLOWER.TXT的开始簇号为3,对应的FAT表中的值为0x004,表示文件还没有结束,下一个簇号是0x004,然后我们再看FAT表中第4个簇相对应的FAT值为0xFFF,则表示该是最后一个簇,则文件FLOWER.TXT占用第3、4簇。

如果想使文件内容分存在不连续的扇区内,有一个方法可以做到,就是先将该文件加入到软盘驱动中,然后在进行添加相同的文件,进行覆盖。当然该文件的大小必须大于512个字节。



1、突破512字节的限制

2、加载Loader进入内存

一、突破512字节的限制

一个操作系统从开机到开始运行,大致经历"引导—》加载内核入内存—》跳入保护模式—》开始执行内核"这样一个过程。也就是说,在内核开始执行之前不但要加载内核,还要准备保护模式等一系列工作,如果全部交给引导扇区来做,512字节很可能不够用,所以,不放把这个过程交给另外的模块来完成,我们把这个模块叫做Loader。引导扇区负责把Loader加载如内存并且把控制权交它,其他的工作放心地交给 Loader来做,因为它没有512字节的限制,将会灵活很多。

二、加载Loader进入内存

上一节我们已经详细介绍了FAT12文件系统的数据结构,下面我们需要思考的是两个问题:1、引导扇区通过怎样的步骤才能找到文件;2、如何能够把文件内容全都读出来并加载进入内存。

下面我们先解决第一个问题:

1、  如何读取软盘?

(1)    我们需要使用BIOS中断int 13h来读取软盘。它的用法如下表所示:

一个操作系统的实现(6):加载Loader.bin_第16张图片

在这里我们只介绍了2种工作方式,中断int 13h还有其他的工作方式,如果需要可以自行查看内容。

(2)    由上表我们可以知道:当读取某些扇区时,需要柱面(磁道)号(ch),起始扇区号(cl),磁头号(dh)。我们如何通过计算得到这些数据呢?

一个操作系统的实现(6):加载Loader.bin_第17张图片

(3)    现在万事俱备只欠东风了,下面我们就书写读取软盘扇区的函数ReadSector。

首先我们要知道该函数需要什么参数,这些参数存储在什么位置?

参数1:扇区号,存储在ax中

参数2:要读取扇区的个数,存储在cl中

参数3:数据缓冲区,即读取扇区数据后,将其存储在什么位置,用es:bx指向该缓冲区。

即函数的作用:从第ax个Sector开始,将cl个Sector读入es:bx中。

[html]  view plain copy
  1. <span style="font-size:18px;">;-----------------------------------------------------------------------  
  2. ;函数名:ReadSector  
  3. ;------------------------------------------------------  
  4. ;作用:从第ax个Sector开始,将cl个Sector读入es:bx中  
  5. ReadSector:  
  6.     ;---------------------------------------------  
  7.     ;怎样由扇区号求扇区在磁盘中的位置(扇区号->柱面号,起始扇区,磁头号)  
  8.     ;---------------------------------------------  
  9.     ;设扇区号为x  
  10.     ;                           ┌ 柱面号 = y >> 1  
  11.     ;       x           ┌ 商 y ┤  
  12.     ; -------------- => ┤      └ 磁头号 = y & 1  
  13.     ;  每磁道扇区数     │  
  14.     ;                   └ 余 z => 起始扇区号 = z + 1  
  15.   
  16.     ;辟出两个字节的堆栈区间保存要读取的扇区数:byte[bp-2]  
  17.     push bp  
  18.     mov bp, sp  
  19.     sub esp, 2            
  20.     mov byte[bp-2], cl      ;将参数cl,存储在byte[bp-2],将要读取扇区的个数。  
  21.   
  22.     push bx             ;保存bx,因为下面要使用bx进行计算。  
  23.     mov bl, [BPB_SecPerTrk]     ;bl:除数=18  
  24.   
  25.     ;ax存储的是扇区号,bl是每磁道扇区数,执行ax/bl=al----ah,  
  26.     ;即商y在al中,商z在ah中。  
  27.     div bl                
  28.     inc ah              ;ah(z)++,即起始扇区号=z+1,  
  29.     mov cl, ah          ;将ah值赋值给cl,中断int 13h中,cl保存的恰好是起始扇区号  
  30.   
  31.     mov dh, al          ;将al(y),赋值给dh  
  32.   
  33.     shr al, 1           ;对al(y)进行右移一位,即得到柱面号=y>>1,  
  34.     mov ch, al          ;然后将al赋值给ch,在中断int 13h中,ch保存着柱面(磁道)号  
  35.   
  36.     and dh, 1           ;将dl(y)进行&1运算,即得到磁头号=y&1,在中断int 13h中,dh保存着  
  37.                     ;磁头号  
  38.     pop bx              ;恢复bx值  
  39.     ;到此为止,“柱面(磁道)号(ch),起始扇区号(cl),磁头号(dh),缓冲地址(es:bx)”全部准备就绪  
  40.     mov dl, [BS_DrvNum]     ;在中断int 13中,dl保存着驱动器号。此时dl=[BS_DrvNum]=0  
  41. .GoOnReading:  
  42.     ;下面对ah,al进行赋值,ah=2,al=要读取的扇区数,前面将参数cl存储在byte[bp-2],现在从这里重新获取  
  43.     ;并赋值给al。  
  44.     mov ah, 2  
  45.     mov al, byte[bp-2]  
  46.     ;中断int 13一切准备就绪,然后执行int 13  
  47.     int 13h  
  48.     jc  .GoOnReading        ;如果读取错误,CF会被置为1,这时就不停地读,直到正确为止。  
  49.   
  50.     add esp, 2          ;恢复堆栈  
  51.     pop bp  
  52.     ret</span><span style="font-size:16px;">  
  53. </span>  


2、  如何在软盘中寻找Loader.bin文件

(1)    结合上一节所介绍的FAT12数据结构,从中我们可以知道,要寻找一个文件,首先需要在根目录区中寻找该文件的根目录条目;然后根据根目录条目获取文件开始簇数(也就是在数据区中存储的扇区);最后读取文件内容到内存。

(2)    嗯,是的,下面就让我们来完成第一步-----在根目录区中寻找该文件的根目录条目。

让我们开始思考这个问题,

首先要知道根目录区的开始扇区号是19,也就是说从第19扇区开始,根目录区占用扇区总数为14,也就是说,如果不能发现Loader.bin,需要将14个扇区都进行查找,于是需要一个大的循环在外围,控制着扇区的读取。

紧接着,我们每读取一个扇区,一个扇区是512个字节,一个根目录条目占32个字节,故一个扇区中存在512/32=16个根目录条目,所以需要添加一个循环,控制根目录条目的变化,从0—16进行循环。

最后,针对每一个根目录条目,我们只是要比较文件名,一个根目录条目的文件名占用11个字节,所以需要对每一个字节与"LOADER   BIN"中的每一个字节进行比较,所以还是要添加一个循环,来控制字符的变化,即从0—11.

用C语言来表示该问题就是:

for( i = 根目录区的起始扇区号(19); i < 根目录区占有的扇区数(14);  i++)      {

           for( j = 0;j < 一个扇区内存储的根目录条目个数(512/32=16); j++)    {

                    for(k =0; k < 根目录条目中文件名占用的空间(11个字符); k++)      {

                             if(k=10)jmp LABEL_FILENAMEFOUND

                             if(ds:si= es:di) si++; di++;

                             else  break;

                    }

           }

}

(3)    下面让我们来分析代码:

首先需要介绍下面可能需要用到的几个变量值:

BaseOfLoader           equ  09000h     ;LOADER.BIN被加载到的位置---段地址

OffsetOfLoader        equ  0100h       ;LOADER.BIN被加载到的位置---偏移地址

RootDirSectors         equ  14     ;根目录占用空间(BPB_RootEntCnt*32+511)/512

SectorNoOfRootDirectory        equ  19     ;Root Directory 的第一个扇区号

;变量

wRootDirSizeForLoop      dw    RootDirSectors         ;Root Directory占用的扇区数,在循环中会递减至0

wSectorNo                 dw    0                ;要读取的扇区号

bOdd                           db     0                ;奇数还是偶数

;字符串

LoaderFileName       db     "LOADER  BIN",     0       ;LOADER.BIN之文件名

[html]  view plain copy
  1. <span style="font-size:16px;">  </span><span style="font-size:18px;">;调用中断int 13h,实现软驱复位  
  2.     xor ah, ah  
  3.     xor dl, dl  
  4.     int 13h   
  5.       
  6.     ;下面在A盘的根目录中寻找LOADER.BIN  
  7.       
  8.     ;wSectorNo表示要读取的扇区号,SectorNoOfRootDirectory  
  9.     ;表示根目录区的开始扇区号=19  
  10.     mov word[wSectorNo], SectorNoOfRootDirectory      
  11. LABEL_SEARCH_IN_ROOT_DIR_BEGIN:  
  12.     ;wRootDirSizeForLoop=RootDirSectors,表示根目录占用的扇区数,即表示要读取的扇区数;也就是  
  13.     ;最外部循环中的控制变量(相当于i)。  
  14.     ;判断根目录区所有扇区是不是已经读取完毕,如果读完表示没有找到LOADER.BIN,  
  15.     ;跳入到LABEL_NO_LOADERBIN,否则,减1。  
  16.     cmp word[wRootDirSizeForLoop], 0  
  17.     jz  LABEL_NO_LOADERBIN            
  18.     dec word[wRootDirSizeForLoop]  
  19.   
  20.     ;为ReadSector函数准备参数,从第ax个Sector开始读,将cl个Sector读入es:bx中  
  21.     mov ax, BaseOfLoader  
  22.     mov es, ax      ;es<-BaseOfLoader  
  23.     mov bx, OffsetOfLoader  ;bx<-OffsetOfLoader,于是es:bx=BaseOfLoader:OffsetOfLoader  
  24.   
  25.     mov ax, [wSectorNo] ;ax<-Root Directory中的某Sector号,表示要读取的扇区号  
  26.     mov cl, 1       ;cl表示要读取扇区个数=1  
  27.     call    ReadSector  
  28.     ;调用ReadSector函数之后,es:bx将存储该扇区数据。  
  29.   
  30.     mov si, LoaderFileName  ;ds:si->"LOADER  BIN"  
  31.     mov di, OffsetOfLoader  ;es:di->BaseOfLoader:OffsetOfLoader=es:bx  
  32.                 ;即es:di指向存储的该扇区数据  
  33.     cld  
  34.   
  35.     ;一个扇区是512个字节,一个根目录项占32个字节,故512/32=16,因此需要比较16个根目录项的文件名,  
  36.     ;故赋值dx=16,由dx来控制循环次数  
  37.     mov dx, 10h  
  38. LABEL_SEARCH_FOR_LOADERBIN:  
  39.     ;判断dx是否为0,0意味着这个扇区内的所有根目录项进行比较完毕,然后跳入到下一个扇区,继续进行比较,  
  40.     ;dx=0,则跳入LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR;否则,dx--  
  41.     cmp dx, 0  
  42.     jz  LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR    
  43.     dec dx  
  44.       
  45.     ;一个根目录项的文件名占用11个字节,故必须对其每个字节与"LOADER  BIN"一一对比  
  46.     ;故赋值cx=11,由cx来控制循环次数  
  47.     mov cx, 11  
  48. LABEL_CMP_FILENAME:  
  49.     cmp cx, 0  
  50.     jz LABEL_FILENAME_FOUND ;如果cx=0,意味着11个字符都相等,表示找到,跳转到LABEL_FILENAME_FOUND  
  51.     dec cx          ;否则,cx--  
  52.   
  53.     lodsb           ;ds:si->al,ds:si指向的是字符串"LOADER  BIN"  
  54.     cmp al, byte[es:di] ;进行一个字符的比较,如果相等,则比较下一个字符,  
  55.     jz LABEL_GO_ON      ;跳入到LABEL_GO_ON  
  56.     jmp LABEL_DIFFERENT ;只要发现有一个不相等的字符就表明本Directory Entry不是我们要  
  57.                 ;找的LOADER.BIN,跳转到LABEL_DIFFERENT,进如下一个Directory Entry比较。  
  58. LABEL_GO_ON:              
  59.     inc di          ;将di++,进行一个字符的比较。  
  60.     jmp LABEL_CMP_FILENAME  ;跳转到LABEL_CMP_FILENAME,继续进行文件名比较。  
  61.   
  62. LABEL_DIFFERENT:  
  63.     ;di&=E0是为了让它指向本条目开头,di初始化为某个条目开头,  
  64.     ;在比较过程中,会将它不断增1,当失败之后,必须进行重新初始化  
  65.     ;因为一个条目占用32个字节,故and di,0FFE0h   add di, 20h  
  66.     ;之后,di就指向了下一个条目  
  67.     and di, 0FFE0h                
  68.     add di, 20h   
  69.   
  70.     ;重新初始化si,使其指向"LOADER  BIN"的开始位置  
  71.     mov si, LoaderFileName            
  72.     jmp LABEL_SEARCH_FOR_LOADERBIN  ;跳转到LABEL_SEARCH_FOR_LOADERBIN  
  73.   
  74. LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:   
  75.     add word[wSectorNo], 1      ;将要读取的扇区号+1,进行下一个扇区的比较  
  76.     jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN  ;跳转到LABEL_SEARCH_IN_ROOT_DIR_BEGIN,开始一个扇区的比较  
  77.   
  78.     ;如果最后没有找到"LOADER   BIN",则显示“NO LOADER”字符串来表示。  
  79. LABEL_NO_LOADERBIN:  
  80.     mov dh, 2               ;"NO LOADER"  
  81.     call DispStr                ;显示字符串  
  82. %ifdef  _BOOT_DEBUG_  
  83.     mov dh, 2               ;"NO LOADER"  
  84.     call DispStr                ;显示字符串  
  85.     mov ax, 4C00h  
  86.     int 21h                 ;没有找到LOADER.BIN,返回到DOS  
  87. %else     
  88.     jmp $                   ;没有找到LOADER.BIN,死循环在这里  
  89. %endif  
  90.   
  91.     ;如果找到"LOADER   BIN",则跳转到LABEL_FILENAME_FOUNT,然后进行第二步骤,从  
  92.     ;Directory Entry中读取文件在数据区的开始簇号。  
  93. LABEL_FILENAME_FOUND:</span><span style="font-size:16px;">  
  94. </span>  

(4)    对上面这段代码画出它的简易流程图如下:

一个操作系统的实现(6):加载Loader.bin_第18张图片

3、  如何将Loader.bin文件加载到内存?

现在我们已经有了Loader.bin的起始扇区号,我们需要用这个扇区号来做两件事情:一件是把起始扇区装入内存,另一件则是通过它找到FAT中的项,从而找到Loader占用的其余所有扇区。

此时装入一个扇区对我们来说已经是很轻松的事了,可从FAT中找到一个项还是多少有些麻烦,下面我们就根据扇区号去FAT表中找到相应的项。在这里,将要写一个函数GetFATEntry,函数的输入就是扇区号(ax),输出则是其对应的FAT项的值(ax)。

我们一起来思考这个函数如何去实现,我们知道了扇区号x,然后我们去FAT1中寻找x所对应的FATEntry,我们已经知道一个FAT项占1.5个字节。所以我们用x*3/2=y………z,y为商(偏移量)(字节),相对于FAT1的初始位置的偏移量;Z为余数(0或者1),是判断FATEntry是奇数还是偶数,0表示偶数,1表示奇数。然后我们让y/512=m………n,m为商,n为余数,此时m为FATEntry所在的相对扇区,n为在此扇区内的偏移量(字节)。因为FAT1表前面还有1个引导扇区,所以FATEntry所在的实际扇区号为m+1。然后读取m+1和m+2两个扇区,然后在偏移n个字节处,取出FATEntry,相当于读取两个字节。此时再利用z,如果z为0的话,此FAT项为前一个字节和后一个字节的后4位,如果z为1的话,此FATEntry取前一个字节的前4位和后一个字节。

下面我们实现GetFATEntry函数,函数的输入就是扇区号,输出则是其对应的FATEntry的值。

[html]  view plain copy
  1. <span style="font-size:18px;">;-----------------------------------------------------  
  2. ;函数名:GetFATEntry  
  3. ;-----------------------------------------------------  
  4. ;作用:    找到序号为ax的Sector在FAT中的条目,结果放在ax中,需要注意的是,中间需要读FAT的扇区es:bx处,  
  5. ;   所以函数一开始保存了es和bx  
  6. GetFATEntry:  
  7.     push es  
  8.     push bx  
  9.     push ax  
  10.       
  11.     ;在BaseOfLoader后面留出4K空间用于存放FAT  
  12.     mov ax, BaseOfLoader  
  13.     sub ax,0100h  
  14.     mov es, ax  ;此时es-> (BaseOfLoader - 100h)  
  15.   
  16.     pop ax      ;ax存储的是要读取的扇区号  
  17.     mov byte[bOdd], 0  
  18.     ;ax是要读取的扇区号,如何获得该扇区号在FAT1中的FATEntry  
  19.     ;因为每个FATEntry占有1个半字节,所以计算ax*3/2,找到该FATEntry所在FAT1中的偏移量  
  20.     mov bx, 3  
  21.     mul bx  
  22.     mov bx, 2  
  23.     div bx        
  24.     ;ax*3/2=ax...dx,商为ax表示该FATEntry在FAT1中的偏移量,dx的值为(0或者1),  
  25.     ;0表示该FATEntry为偶数,1表示该FATEntry为奇数,  
  26.     cmp dx, 0  
  27.     jz  LABEL_EVEN  
  28.     ;我们使用byte[bOdd]来保存dx的值,也就是该FATEntry是奇数项还是偶数项。  
  29.     mov byte[bOdd], 1  
  30. LABEL_EVEN:  
  31.     xor dx, dx    
  32.     ;此时ax中保存着FATEntry在FAT1中的偏移量,下面来计算FATEntry  
  33.     ;在哪个个扇区中(FAT1占用9个扇区)。  
  34.     ;ax/BPB_BytsPerSec=ax/512=ax...dx,商ax存储着该FATEntry所在FAT1表的第几个扇区,  
  35.     ;余数dx保存着该FATEntry在该扇区内的偏移量。  
  36.     mov bx, [BPB_BytsPerSec]  
  37.     div bx        
  38.   
  39.     push dx     ;将dx存储在堆栈中.  
  40.     mov bx, 0   ;es:bx=(BaseOfLoader-100):00=(BaseOfLoader-100h)*10h  
  41.     ;我们知道ax是FATEntry所在FAT1中的相对扇区,而FATEntry所在的实际扇区,需要加上  
  42.     ;FAT1表的开始扇区号,即加1,之后ax就是FATEntry所在的实际扇区  
  43.     add ax, SectorNoOfFAT1            
  44.     mov cl, 2  
  45.     ;读取FATEntry所在的扇区,一次读2个,避免在边界发生错误,  
  46.     ;因为一个FATEntry可能跨越两个扇区  
  47.     call ReadSector       
  48.     ;从堆栈中弹出dx,FATEntry所在扇区的偏移量,将其与bx相加,此时es:bx指向的是该FATEntry所占用  
  49.     ;的两个字节空间  
  50.     pop dx  
  51.     add bx, dx  
  52.     ;读取该FATEntry  
  53.     mov ax, [es:bx]  
  54.     ;下面是对bOdd进行判断,如果其为0,则表示FATEntry为偶数,此时需要取byte1和byte2的后4位,  
  55.     ;由于在80x86下,从内存中读取数据之后,byte2在前,byte1在后。  
  56.     ;所以当FATEntry为偶数时,需要将ax&0FFF,将byte2的前4位置0.  
  57.     ;反之,如果bOdd为1,则表示FATEntry为奇数,此时需要取得byte1中的前4位和byte2.  
  58.     ;所以,需要将ax右移4位,将byte1的后四位移除。  
  59.     cmp byte[bOdd], 1  
  60.     jnz LABEL_EVEN_2  
  61.     shr ax, 4  
  62. LABEL_EVEN_2:  
  63.     and ax, 0FFFh  
  64.   
  65.     ;此时ax存储的是FATEntry的值  
  66. LABEL_GET_FAT_ENTRY_OK:  
  67.     pop bx  
  68.     pop es  
  69.     ret  
  70. ;--------------------------------------------------------------------</span><span style="font-size:16px;">  
  71. </span>  

下面我们开始加载Loader.bin进入内存。

首先我们从根目录区中的Loader.bin的条目,获取文件的起始扇区号,然后加上BPB_RsrvSecCnt+BPB_FATSz16*2-2+RootDirSectors=1+(9*2)+14-2=31,,其中DeltaSectorNo=BPB_RsrvSecCnt+BPB_FATSz16*2-2=17。得到的结果才是文件的实际的起始扇区。获得起始扇区后,我们就可以调用ReadSector来读取扇区了。然后从FAT1表中获取FATEntry的值,判断是否为0FFFh,如果是,结束加载;如果不为0FFFh,意味着该文件没有读取完成,需要读取下一个扇区,此时的FATEntry的值,就是下一个扇区号,再将其转换为实际扇区号,再进行读取。

下面是函数的实现和注释

[html]  view plain copy
  1. <span style="font-size:18px;">LABEL_FILENAME_FOUND:         ;找到了LOADER.BIN后便来到这里继续  
  2.     mov ax, RootDirSectors      ;根目录区所占用扇区数=14  
  3.     and di, 0FFE0h          ;di->当前Directory Entry的开始位置  
  4.     add di, 01Ah            ;di->此条目对应的开始簇号,DIR_FstClus  
  5.     mov cx, word[es:di]     ;将开始簇号存储在寄存器cx中  
  6.     push cx             ;将cx入栈  
  7.     ;实现cx+RootDirSectors+DeltaSectorNo之后,此时cx保存着文件的实际开始扇区号,  
  8.     ;即数据区内的扇区  
  9.     add cx, ax            
  10.     add cx, DeltaSectorNo             
  11.   
  12.     mov ax, BaseOfLoader  
  13.     mov es, ax  
  14.     mov bx, OffsetOfLoader      ;es:bx=BaseOfLoader:OffsetOfLoader  
  15.     mov ax, cx          ;ax表示要读取的扇区号  
  16.   
  17. LABEL_GOON_LOADING_FILE:  
  18.     push ax  
  19.     push bx  
  20.     mov ah, 0Eh  
  21.     mov al, '.'  
  22.     mov bl, 0Fh  
  23.     int 10h               
  24.     pop bx  
  25.     pop ax  
  26.     ;每读一个扇区就在”Booting   “后面打一个点,形成这样的效果:Booting......  
  27.   
  28.     ;继续为ReadSector函数的参数做准备,cl=1,表示要读取一个扇区  
  29.     mov cl, 1         
  30.     call ReadSector  
  31.   
  32.     pop ax      ;读完一个扇区之后,然后重新读取此Sector在FAT中的序号  
  33.     call GetFATEntry      
  34.     cmp ax, 0FFFh  
  35.     jz  LABEL_FILE_LOADED     
  36.     ;如果读取的FAT值为FFF,表示该扇区为该文件的最后一个扇区,  
  37.     ;因此结束加载,也就是加载成功  
  38.     ;如果读取的FAT表中的值不是FFF,则表示还有扇区,故保存下一个扇区序号  
  39.     push ax       
  40.                   
  41.     mov dx, RootDirSectors  
  42.     add ax, dx  
  43.     add ax, DeltaSectorNo  
  44.     add bx, [BPB_BytsPerSec]  
  45.     ;为call ReadSector的参数做准备,es:bx表示要缓存的地址,  
  46.     ;ax表示要读取的扇区号=DirEntry中的开始Sector号+根目录占用Sector数目+DeltaSectorNo  
  47.     ;进入下一次循环。  
  48.     jmp LABEL_GOON_LOADING_FILE  
  49.   
  50. LABEL_FILE_LOADED:  
  51.     mov dh, 1               ;"Ready."  
  52.     call DispStr                ;显示字符串  

你可能感兴趣的:(一个操作系统的实现(6):加载Loader.bin)