1.5 Loading Processing

[Home]  [Top]  [Previous]  [Next]


 1.5.1 Overview

我们在After Power-On一节已经介绍——当主机被启动之后,会自动将软盘或硬盘的第一个扇区的内容(boot block for floppy, master boot for hard disk)Load到内存0000:7c00H的位置,然后执行它。但OS的内核绝对不仅仅只有一个扇区大小。这就意味着boot block需要进一步的从磁盘(软盘或硬盘)上读取OS的剩余部分。

 

事实上,从机器被Power-On或Reset之后,到OS的Kernel被正式的载入到运行,中间可以经历这些过程:

  1. 由BIOS INT 19中断读入Master boot;
  2. 由Master boot读入并执行Boot Block;
  3. 由Master boot读入并执行Second Booter(Monitor or Setup Program);
  4. 最后由Second booter读入并执行OS Kernel。

比如Linux的启动过程为:

  1. Master boot(LILO)
  2. Boot Block(bootsect.S)
  3. Setup(Setup.S)
  4. Linux Kernel

Loading Processing是Booting阶段的一个重要任务。为了很好的完成这一任务,我们必须清楚的了解磁盘(硬盘和软盘)操作的详细细节,对Loading过程和各个部分内存布局进行详细的规划。


 1.5.2 Memory Layout

在Real mode下,磁盘操作可以通过两种方法进行,BIOS 13H中断,或者通过读写端口。而在Protected Mode下,BIOS中断不可用,只能通过读写端口的方式。而BIOS中断简单易用而且功能强大,同时我们在Booting阶段除了Loading Processing之外,所作的许多工作都需要依赖BIOS中断,所以我们Loading Processing应该发生在Real Mode下,通过BIOS中断调用来完成。

但Real Mode下的一种重要限制是1MB内存的局限,事实上仅有不到640 KB的可用RAM,我们必须把启动阶段所需要的各个程序,以及在启动阶段获取到的各种硬件参数,以及OS Kernel放入到这不到640 KB的RAM中。这样一来,我们如何在整个Booting阶段,合理有效的安排内存布局就显得非常重要。

 

主机在Power-on或Reset后,经过POST,然后读取磁盘的第一扇区之后,常规内存的布局如上图所示。

 

我们在进入保护模式之前BIOS Interrupt Ventors Table, BIOS Data Area和Extend BIOS Data Area都可能会有用,即使进入Protected Mode之后,如果你想让你的OS支持VM8086模式,也就是说如果你想让你的OS上可以运行Real Mode软件,这两个区域也会用得着,所以不要去对这些内容进行修改令人不解的是,为什么BIOS厂商将BIOS Data Area和Extend BIOS Data Area分别放在常规RAM的顶端和底部。在进行内存布局规划的时候,面临着令人非常困扰的选择。这也是为什么Linux对640KB常规内存进行规划的时候,将最底部的64KB保留,而最顶部的64KB中只有低地址的16KB被使用,其它的部分都被保留的原因。

 

由于First Boot Block只有512 bytes的大小,所以它所能做的事情非常有限,如果我们想在Real Mode下作更多的事情,我们必须有一个Second booter,它一方面负责获取各种物理设备参数,这些参数将来要被OS Kernel使用;另一方面,如果OS Kernel运行在Protected Mode下的话,The Second Booter需要为进入Protected Mode做各种准备,并进入Protected Mode。

 

既然The Second Booter没有局促的大小限制——最起码几十K是没有问题的,如果用汇编的话,几十K已经足够了——那么我们就没有必要花费心思让The First Boot Block做太多的工作,我们只需要让The Second Booter来做就可以了。The First Boot Block需要做的只是检测启动设备类型,并将The Second Booter装入RAM。

 

当载入OS Kernel时,你需要仔细决定将其载入到哪个位置。OS Kernel可能会比较大,这时候,你可以将Kernel的Image压缩,然后将其读入到常规内存中;读入之后将其解压,将解压后的Kernel放置到扩展内存中(1M以上的位置)。


How to place big kernel into extend memory?

 

1.Linux大内核方式(bzImage)所用的方法,使用int 15h,每次拷贝64k数据

2.用BIOS所用的方法,临时切换到保护模式, 设置某一个段描述为0~4G,再切换回实模式,引用这个段,就可以自由访问4G空间了

 


 

1.5.3 Floppy System

 

相对于其他大多数子系统,软盘BIOS服务的相关资料要详尽的多。从第一台PC机开始,软盘系统的基本控制器接口就基本保持未变。当然,AT还加入了一些新的BIOS服务,最新的系统还支持2.88M的软盘。

 

软盘的访问要通过适配卡上的硬件和内置于系统主BIOS内的BIOS软件。该组合提供了简单方便的格式化,读和写软盘操作,从AT开始起,BIOS所支持的软盘驱动器的数目从四个减少到两个以内。访问两个以上的软驱需要专门的适配器硬件和驱动程序,比如MS-DOS的Driver.sys。

 

 

上面两图表现的是软盘的逻辑结构,每个软盘分为柱面(Cylinder),磁道(Track),扇区(Sector),最小的组成部分是扇区,通常每个扇区有512 Bytes的数据。多个扇区组成一个磁道。例如,一个1.44 MB的软盘每磁道有18个扇区,其它软盘类型每个磁道有9到36个扇区,这取决于在每英寸介质上可以可靠保存的位数。两个对称的磁道,软盘的一面各一个,定义为一个柱面。软盘有40或80个柱面,这取决于介质类型。

 

事实上,一个扇区的坐标位置由柱面,磁道,和扇区三个因素的坐标共同决定:X-柱面(0-39或0-79),Y-磁道(0-1),Z-扇区(0-9或0-18或0-36)。如下图所示:

下表总结了由512 bytes扇区组成的各种软盘的参数。此表也列出了所使用的传输率,单位是bit/second。它是数据在Driver和Controler之间传输的速率。传输速率取决于软盘的容量、Driver类型以及软盘转动的速率,单位是RPM(Running Per Minute)转每分。软盘转得越快,每线性英寸的介质上被选中的数据就越多,传输速率总是固定的,软件不能对其进行定义。

 

驱动器及软盘类型 每面磁道数 每磁道扇区数 RPM 传输速率
360K,5.25英寸 40 9 300 250Kbps
1.2M,5.25英寸 40 9 360 300Kbps
1.2M,5.25英寸 80 15 360 500Kbps
720K,3.5英寸 80 9 360 250Kbps
1.44M,3.5英寸 80 9 360 250Kbps
1.44M,3.5英寸 80 18 360 500Kbps
2.88M,3.5英寸 80 9 360 250Kbps
2.88M,3.5英寸 80 18 360 500Kbps
2.88M,3.5英寸 80 36 360 1Mbps

表1-5-1 软盘种类表


 

软盘参数表

 

在BIOS的中断向量表中断1Eh的地址0:78h处(参见A.1 BIOS Interrupt Overview),保存着指向存放于系统BIOS ROM中的软盘参数表的远指针(远指针指需要通过segment:offset的方式来制定的指针,它所指向的地址可以跨越不同的64K段;而近指针则只需要通过offset来制定同一段内的另外一个地址)。里面包含了有关驱动器(Driver)的信息。BIOS使用这个表来为软盘控制器编程和指定软驱的控制定时——当某个程序调用13h中断来使用软盘服务时,BIOS将会使用软盘参数表中提供的数据来对软盘控制器进行编程。

 

软盘参数表是一个重要的概念——如果系统上只有一个驱动器类型,正像早期的PC和XT系统一样。参数表在早期的PS/2上工作得也很好,在这里要从两种类型的驱动器中选择一个,比如3.5英寸,1.44M。软盘参数表用于完整的定义驱动器参数。

 

下表为软盘参数表的概要内容:

偏移量 描述
0 软盘控制器(端口3F5h)
1 软盘控制器(端口3F5h)
2 驱动器休眠马达关闭延时
3

每扇区字节数:

0=128 bytes;1=256 bytes;2=512 bytes;3=1024 bytes;

4=2048 bytes;5=4096 bytes;7=16384 bytes;8-FFh=Reserverd

4

每磁道的扇区数:

9=9 sectors;360K和720K

0Fh=15 sectors;1.2M

12h=18 sectors;1.44M

24h=36 sectors;2.88M

5

扇区之间的间隙长度:

1Bh for 1.2M,1.44M and 2.88M floppy

2Ah for 360K and 720K floppy

6 数据长度
7 格式化间隙长度
8 格式化填充字节
9 磁头定位时间
A 等待马达开启时间

从软盘读取数据,不能够跨越磁道,即每次读取,尽管可以同时读取多个扇区,而这些扇区却必须都属于同一个磁道。BIOS 13h服务例程将对此进行验证,如果发现用户所要读取扇区跨越了不同的磁道,则返回调用失败。而验证所依据的数据则是软盘参数表。假如,软盘参数表中所记录的“每磁道的扇区数”(偏移量为4的位置)为9;如果现在一个INT 13h, ax = 2的调用指定:磁头为0,磁道为1,磁道内起始扇区为2,要读取的扇区数目为9;由于磁道内起始扇区+要读取的扇区数-1 = 2+9 -1 = 10 〉9,则调用将会失败。(之所以减去1是因为磁道内起始扇区是从1开始计数的,而不是0)。

但这并不意味着,如果通过了BIOS软盘参数表的验证,而读取就一定能够成功。例如,软盘参数表中所记录的“每磁道的扇区数”为18,而实际的“每磁道的扇区数”为9,如果现在一个INT 13h, ax = 2的调用指定:磁头为0,磁道为1,磁道内起始扇区为2,要读取的扇区数目为9;由于磁道内起始扇区+要读取的扇区数-1 = 2+9 -1 = 10 < 18,通过软盘参数表的验证没有任何问题,但当软盘驱动器真正去读取磁头0,磁道1,第10个扇区的时候,而这个扇区并不存在,调用依然会失败。

所以,为了保证一个需要读取软盘的程序不出错误,我们必须保证:

“要读取的扇区数” + “磁道内起始扇区” - 1 <= 软盘参数表中“每磁道的扇区数”的数值

同时保证:

“要读取的扇区数” + “磁道内起始扇区” - 1 <= 实际的“每磁道的扇区数”

你如果想保证你的程序永远不在读取软盘的时候出此类错误,最稳妥的方法是每次只读取一个扇区,但这样效率很低,真正作软件的人都愿意有效率更高,而又稳妥地方法——所以我们要在提高效率的目标下,寻找一种方法来确保效率。

我们还是看一看上面所列的两个条件,从中我们可以首先得知——我们必须知道实际的“每磁道的扇区数”,否则,我们肯定会出错。那么,在我们知道实际的“每磁道的扇区数”的前提下,软盘参数表中“每磁道的扇区数”的数值不能小于实际的“每磁道的扇区数”,否则,仍然会出错。那么大于呢?由于BIOS对软盘参数表中“每磁道的扇区数”的数值的验证放在前面,如果大于的话,这一级验证即使通过了,还有实际的“每磁道的扇区数”这一关需要通过。所以大于是不会有任何问题的。等于就更不用说了。

由此得知,要想让我们的程序不出此类错误,我们必须保证两点:

1、保证 软盘参数表中“每磁道的扇区数”的数值 大于等于 实际的“每磁道的扇区数”。

2、知道实际的“每磁道的扇区数”;

对于第1点,或许大家会问,难道BIOS厂商不能够来保证这一点吗?答案是,能够。但不幸的是,在现实生活中,由于历史的原因,确确实实有一些BIOS厂商将软盘参数表中“每磁道的扇区数”的数值设置的比较小。这样,我们就必须靠自己来保证这一点。


如何保证软盘参数表中“每磁道的扇区数”大于等于实际的“每磁道的扇区数”? 

我们在前面已经说过,在BIOS的中断向量表中断1Eh的地址0:78h处,放置的仅仅是指向软盘参数表的的指针,而软盘参数表被放在BIOS ROM中,我们无法修改。所以,我们可以在RAM中复制一份软盘参数表,进行修改。然后将BIOS的中断向量表中断1Eh的地址0:78h处的远指针修改为指向RAM中新复制的软盘参数表。

由于到目前为止,最大可能的“每磁道扇区数”为36(参见表1-5-1),所以我们只需要将新的软盘参数表中的“每磁道扇区数”修改为36,即可以保证软盘参数表中“每磁道的扇区数”的数值大于等于实际的“每磁道的扇区数”。

下面是Linux 2.4的相关代码。

# 将0:78h位置所保存的磁盘参数表指针所指向的位置的12格字节拷贝到0x4000-12的
# 位置处,然后将磁盘新参数表的sectors/track值修改为36(2.88M软盘的参数),
# 最后修改0:78h处的指针指向0x4000-12 (es:di)
#
 movw   %cx, %fs            # %fs = 0
 movw   $0x78, %bx       # %fs:%bx is parameter table address
 pushw  %ds
 ldsw     %fs:(%bx), %si    # %ds:%si is source
 movb   $6, %cl                # copy 12 bytes
 pushw  %di                     # %di = 0x4000-12.
 rep                                  # don't worry about cld
 movsw                            # already done above
 popw    %di
 popw    %ds
 movb    $36, 0x4(%di)     # patch sector count
 movw    %di, %fs:(%bx)
 movw    %es, %fs:2(%bx)


如何获取实际的“每磁道的扇区数”?

 

除了去猜测之外,没有什么好的方法能够获取“实际的每磁道扇区数”。所幸的是,实际存在的软盘只有有限的几个种类,而不同的“实际的每磁道扇区数”就更加有限。参见表1-5-1,我们可以得知,“实际的每磁道扇区数”有9,15,18,36四种。于是,我们就可以对一张软盘使用这四个数值从大到小依次猜测。

 

猜测的方法,是读取磁头0,磁道0,当前磁道最大扇区。首先猜测“当前磁道最大扇区”为36,如果实际的“每磁道扇区数”小于36,则读取失败。然后猜测“当前磁道最大扇区”为18,如果失败了,再猜测15……依次类推,直到猜测为9为止,这是最小可能的“每磁道扇区数”。

 

下面是Linux 2.4的相关代码。

 

# Get disk drive parameters, specifically number of sectors/track.

# It seems that there is no BIOS call to get the number of sectors.

# Guess 36 sectors if sector 36 can be read, 18 sectors if sector 18

# can be read, 15 if sector 15 can be read.  Otherwise guess 9.

# Note that %cx = 0 from rep movsw above.

 

        movw   $disksizes, %si   # table of sizes to try

probe_loop:
 lodsb                                # Load byte at address DS:SI to AL (DN)
 cbtw                                  # extend to word (ax = (disksizes))
 movw   %ax, sectors        # sectors = max sectors per track (DN)
 cmpw   $disksizes+4, %si # there is 4 parameters

        jae       got_sectors            # If all else fails, try 9

 

    # 读取第一个软驱的Head 0, Track 0, Max sector

   

        xchgw   %cx, %ax             # %cx = track and sector
 xorw     %dx, %dx             # drive 0, head 0
 movw   $0x0200, %bx      # address = 512, in INITSEG (%es = %cs)
 movw   $0x0201, %ax      # service 2, 1 sector
 int        $0x13
 jc          probe_loop          # try next value

 

got_sectors: