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被正式的载入到运行,中间可以经历这些过程:
比如Linux的启动过程为:
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: