第8章磁 盘
学习编程语言,常常是从基本的输入和输出入手的(正如第5、6和第7章曾介绍的一
样)。到目前为止,我们不仅学习了怎样输入和输出数据,还学习了如何进行数据操作。
在开始编写重要程序之前,需要先了解文件(第9章是有关“目录和文件”的介绍),因
为大多数程序都与不同的类型的文件一起工作。有些则直接与磁盘和目录结构一起工
作。要预先了解第9章的一些基本原理,让我们先看看磁盘。
首先要了解基本磁盘技术的工作方式。软盘和硬盘的容量虽不同,但通过DOS功能
进行访问的途径却是相似的。在任何磁盘上,用来打开或关闭文件、读或写文件,以及访问
目录的功能调用却是相同的,通过这一章的学习,我们会了解磁道、扇区和簇的概念,以及
它们在创建程序过程中的重要性。
然后,用所学的有关磁盘工作的知识,就可创建一个基本的磁道格式化功能,可用来
对磁盘重新格式化,但使用这个功能要格外小心;一不注意就会出错并导致关键磁盘受
损。
8.1磁盘的内部结构
但凡使用过PC机的人都会与磁盘打交道。不论拥有什么样的系统,都要在磁盘上保
存信息。既然磁盘在个人计算机的操作中如此重要,人们似乎应该迫切了解它的工作方式
才是,但是事实并非如此。如果打算在磁盘上工作(即使只是察看磁盘结构或它们所保存
的数据),就应该懂得磁盘的工作方式。
可以将磁盘看作文件的集合—就好像它是一个文件柜一样,而不是放在公文包里
的一支笔。每张磁盘都拥有许多文件,用户可以直接访问任何文件所处的“文件夹”—目
录。
在格式化过程中,操作系统将常见的文件结构装入新磁盘中,DOS产生一个文件索
引(目录),并确弃磁盘上文件地址的方式(文件分配表,FAT)。 DOS记录有关磁盘布局
(引导记录)的信息,其中包括一个启动程序,甚至在没有系统文件的磁盘上也是这样。
基本上,磁盘的每一面都是磁性覆盖的表面。这个表面是由读/写磁头通过旋转磁盘
来得到磁化的。双面磁盘有两个记录表面;单面则只有一个(尽管两面都被磁化,但只有一
面能达到质量标准)。硬盘有典型的2到4个磁盘或盘片,它的两边都有记录表面。
在任何磁盘驱动器中,都有特殊的步进电机驱动读/写磁头沿着磁盘表面移动。这种
电机已精确地定义了停止处叫作步长,这些地方是磁头停住的场所。每个停止点都定义了
160页
一条磁道,数据就是被记录在磁道上,大多数硬盘都有一个多盘片系统,在该系统内磁头
能沿着所有的磁盘移动。我们将对应干步进电机所驱动的每个单步的磁道(在所有磁盘
上)都叫作柱面。
FORMAT程序将磁道划分成512字节大小的扇区,以便产生更便于管理的磁盘片
段;在软盘上每磁道有8个或9个扇区,硬盘上每磁道有17个扇区。
DOS分配给文件的空间是以簇为单位来计算的,每簇包括2到8个扇区,依磁盘的
类型而定。当文件需要另外的磁盘空间时,操作系统就将另外一个或多个簇分配给它。图
8.1显示了一个典型磁盘的布局。 :
图8.1磁盘示意图(图中标明了磁道、扇区和簇)
磁盘划分为以下5个重要区域:
·分区表
·引导记录
·文件分配表(FAT)
·目录
·数据空间
我们将在以后的小节中介绍这些内容。
8.1.1分区表
几乎每个磁盘都有一个主记录保存在柱面(磁道)0、磁头(面)0和扇区1的地方(少
数没有主记录的磁盘可能不具备此状态;但用户不会用它们的)。该主记录负责读取和解
释其结尾处的磁盘分区表。然后控制被传递给目前能引导的硬盘分区的引导记录,正如分
区表所显示的那样。如果磁盘没有这样的主记录,它的位置就会被引导记录所代替,这将
在下节中讨论。
分区表说明了硬盘是怎样划分的。为了能被程序如FDISK识别,分区表必须遵从标
准的布局。在一个硬盘上可以有4个分区,每个分区都有一个相应的选项。图8.2显示了
161页
来自COMPAQ Deskpro 286的主引导记录的信息。注意分区表信息储存在扇区结尾。分
区1的选项为默认值01BEh,分区2为01CEh,分区3为01DEh,而分区4为01EEh。扇区
的最后两个字节(即默认值01FEh处,紧随分区表的字节)是这种情况下扇区的标志字
—AA55h。
图8.2硬盘主引导记录、磁盘分区表
注意图8.2所示的分区表信息中,只有两个选项填满了—该硬盘只有两个分区。分
区表的每个项长16字节。表8.1使用从图8.2的分区表中获得的样本值来说明每个分区
表项的布局。
表8.1硬盘分区表项的分布
字节 域长度 样本值 意 义
00h 字节 80h 引导指示符
00h=不能引导
80h=可以引导
01h 字节 01h 起始磁头
字节 01h 起始扇区(位0~5;位6~7是
02h
柱面值的位“8和9”)
03h 字节 00h 起始柱面(低8位)
162页
字节 域长度 样本值 意 义
04h 字节 04h 系统ID
00h=未知
01h=DOS,12位FAT
04h=DOS,16位FAT
05h=DOS,扩展的磁盘,16位FAT
05h 字节 04h 结束磁头
06h 字节 51h(11) 结束扇区(位0-5;其它的两位是柱面值的位
“8和9”)
07h 字节 E9h(1E9) 结束柱面(低8位)
08h 双字 00000011 第一个分区扇区
0Ch 双字 0000A2A1 分区中的扇区数
注意保存在分区表中的信息含义。信息的大部分是说明每个分区的边界,而两个域、
引导指示符以及系统ID则是我们特别感兴趣的内容。引导指示符是告知分区是否可引
导。4个可能的分区中只有一个能标记为可引导的。系统ID则用来标记分区的类型。表
8.1指出许多可能的系统ID值,但是各种其它的操作系统( 如XENIX、UNIX和Pick)则
需要扩大可能的系统ID表。
因为分区表不仅要被DOS识别,还要被别的操作系统识别,所以它的格式在不同的
DOS版本中不易改变(或者在不同的操作系统中);任何对分区表格式的破坏,都会减少
这种软件的商业销售机会。
在系统引导过程中,BIOS查寻磁盘的第一个扇区以便持续引导过程。对于软盘,这就
是引导扇区(见下一节)。对于硬盘;这就是主记录(在主记录内,分配分区表,并且BIOS
确定(通过引导指示符域)哪个分区是能引导的。安装好要引导的分区之后,就将控制传递
给分区的引导扇区,而且象对于软盘那样的磁盘继续进行引导工作。
磁盘分区技术在硬盘上建立起一系列的逻辑磁盘。每个逻辑磁盘运行起来就象一个
小型的磁盘驱动器(并由磁盘驱动器指定一个驱动器字母)。这样单个的硬盘能将所有的
逻辑驱动器用于一个操作系统,或者每个逻辑驱动器能拥有不同的操作系统。一些系统通
常在一个分区中有MS-DOS 而在另一个分区之中则装有XENIX—就象是有两台计
算机而只需一台的价格。
通常当使用容量大于32M的硬盘时,分区是必须的。 DOS 4.X以前的大多数版本都
限于32M或在某个分区中小于32M。通过使用多个分区,就可以利用160M那么大的磁
盘。一些商业化的实用程序借助特殊的磁盘驱动程序能够有效地消除32M的限制。但是,
因为没有驱动程序时,磁盘通常不能使用,所以如果要运行别的操作系统或从软盘进行引
导的话,这种限制就会带来问题。
DOS 4.0版本的一个重要特性,就是它能在一个硬盘的大小范围内去掉了这种限制。
它允许扇区大小达到32位,并允许FAT达到64个扇区,这样就将磁盘容量限制扩大到
了上吉(千兆)字节区域中。
163页
因此,当使用大的硬盘和DOS 4版本时,不需要进行分区。但是如果用户喜欢传统的
方式来组织系统或者系统中保存了多个操作系统,那么也可以进行分区。
8.1.2引导记录
当系统在为可引导的磁盘分区确定安装引导记录的地址时,BIOS就将引导记录装入
内存中。图8.3显示了软盘的典型引导扇区。注意它们在不同的DOS版本之间所存在的
差异。
在这4个版本中,引导扇区都是以跳到自举装入例程的起始点来开始的,该例程将系
统“自举”而进入操作过程。在装入了小的自举程序后,再回过头来装入较大的操作系统
(见第3章,“动态的DOS”,在那里,更详细地讨论了DOS的装入程序)。
在3个字节的跳转指令后面,紧跟的是8个字节的系统名字域,该域指定用来格式化
磁盘的系统所属的制造厂家(有些制造厂家未在这里放置一个名字)。该域后面紧跟BIOS
参数块(BPB),它提供了表8.2所列举的信息。BPB的格式和内容说明,在不同的引导扇
区之间有些差异;每一个重要的版本改进,都要增加更多的数据。表8.2中的样本值来自
图8.3中的引导扇区(所有这些值都来自360K的双面双密软盘)。
在表8.2中,特别要注意的是,样本值是怎样试图保持自身与旧版本的兼容性的,它
甚至在这点上达到极点:当有可能在每个磁盘上拥有多于65535个扇区时,它为此提供了
两个不同的域以用于扇区总数目这一栏。遗憾的是,并非所有的制造厂商都在旧版本中将
保留区单独放置,所以当安装二个新版本时,磁盘会与不能读取的版本一起被格式化。
(A)2.0版磁盘的引导扇区布局
图8.3软盘的引导扇区
164页
(B)MS-DOS3.2版的引导扇区布局
(C)IBM DOS4.01的引导扇区布局
图8.3(续)
165页
(D)MS-DOS5.0的引导扇区布局
图8.3(续)
表8.2 BIOS参数块(BPB)的布局
字节(偏移值) 域长度 样本值 含义
00h 字 0200 每个扇区内的字节数
02h 字节 02 每个簇内的扇区数
03h 字 0001 保存扇区的数目(从扇区0开始)
05h 字节 02 FAT的数目
06h 字 0070 根目录项的最大数
08h 字 02D0 扇区总数(或在V3中如果大于65535则为0)
0Ah 字节 FD 媒体描述符
08h 字 0002 每个FAT的扇区数
0Dh 字 0009 每个磁道的扇区数
0Fh 字 0002 磁头数
11h 双字 00000000 隐藏扇区的数目
15h 11个字节 — 保留(V3之前)
V3 BPB 扩展
15h 双字 00000000 在08h的字=0时的扇区数
19h 7个字节 — 保留(8P3外部引导记录区)
V4 引导记录扩展
19h 字节 00 物理驱动器号
1Ah 字节 00 保留
166页
字节(偏移值) 域长度 样本值 含义
1Bh 字节 29 扩展引导记录的特征字节
1Ch 双字 203D10CC 卷序列号(来自日期/时间)
20h 11个字节 NONAME 卷标记
2Bh 8个字节 FAT12 保留
在表8.2中,特别要注意的是,样本值是怎样试图保持自身与旧版本的兼容性的,它
甚至在这点上达到极点:当有可能在每个磁盘上拥有多于65535个扇区时,它为此提供了
两个不同的域以用于扇区总数目这一栏。遗憾的是,并非所有的制造厂商都在旧版本中将
保留区单独放置,所以当安装一个新版本时,磁盘会与不能读取的版本一起被格式化。
V2之后的DOS版本,BPB对于自举程序的操作是很关键的,因为该程序必须知道这
些参数,才能去发现并安装操作系统的BIOS和内核。
V3之前,安装者假定BPB的ROM BIOS版本能用于引导,并假定安装了DOS后将
应用媒体码;于是,他们不利用引导扇区中的数据。结果,一些公司(特别是Tandy和
HeathZenith)忽略了用DOS2格式化的软盘所提供的BPB数据。
在V3之前,这些磁盘工作良好,但在新版本中它们变得不能阅读了,因为新的DOS
把自举程序的代码看作正确的磁盘参数而阅读这些参数。因为IBM的V2产生的磁盘的
确遵循BPB的规则,所以这些磁盘能被任意地阅读,这就使许多人相信新版本会在引导
扇区的第三、四和第五字节中寻找“魔术字头”IBM;而实际寻找的却是紧跟第八字节
OEM名字区域后的数据。
从V3到V4的过程中,也有相似的情形出现。但是在这种情况下,DOS的IBM版本
的确是在BPB的起始位置寻找魔术起始字头;如果在那里并不是IBM这几个字母(即使
发现了MS-DOS),它会报告一个未知的媒体出错。不知道这种行为的理由;但只要将第3
~10字节的内容改变成“IBM V2.0”或“IBM V3.0”就能使磁盘被系统接受。
8.1.3文件分配表(FAT)
DOS利用文件分配表(FAT)来管理磁盘的数据区。 FAT向DOS指示每个文件所拥
有的磁盘部分。由于FAT具有关键作用,所以DOS通常在磁盘上依次保存了它的两个拷
贝。初始FAT(第一个)改变以后,DOS会小心地更新第二个拷贝。
在磁盘上,FAT紧跟引导记录。因为引导记录只有一个扇区长(扇区0),所以FAT
从扇区)开始。 FAT的长度(在扇区中)由引导记录BPB指定,FAT的拷贝也是这样。
我们很有兴趣地注意到:DOS本身的命令没有一个使用了FAT的第二个拷贝。如果
原始FAT受到一定程度的损坏,就必须使用某个独立的实用程序(不由DOS提供,甚至
也不是来自Microsoft或IBM),来作用于FAT的第二个拷贝,以便发现损坏的磁盘文件。
但是实际上,能够影响第一个拷贝的话,它同时或稍后也能破坏另一个拷贝,这使第二个
FAT拷贝的实用性值得怀疑。
每个FAT包含一系列项,长12或16位,可记录每个簇在磁盘驱动器上的状态。如果
使用12位项,那么其中的每两个项就会被包裹在表中的3个连续的字节中(即24位含有
167页
两项)。这样可使每个FAT所需的空间最小化。
簇是能够分配使用的磁盘空间的最小位,它总是包括一个或多个连续的逻辑扇区(它
们不必在磁盘的相同表面上,第一磁道的第一表面的第一扇区就是0逻辑扇区,然后扇
区、表面、磁道各自递增来记数)。
在一个簇中扇区数总是2的乘方,这样可以简化簇数与逻辑扇区数之间的转换。软盘
通常使用两个扇区大小的簇(1024个字节);第一个硬盘使用8个扇区的簇,但用户们发
现储存许多小文件时4096个字这个最小的分配单位是很浪费的。V3发表后,大硬盘的簇
大小减小到4个扇区。对于每个磁盘,簇大小是包含在BPB中的一个关键项目(见表
8.2)。
在FAT内,每一表项都与磁盘上的一个簇精确对应。与0簇对应的表项拥有媒体码,
1簇对应的则总是填满1的位(十六进制FFFh或FFFFh)。能用于数据的第一簇的编号
为簇2。
任何能用于分配的簇,它在FAT中对应的项均为0。将第一个簇分配给文件时,它在
FAT中的项就变成FFFh或FFFFh,以便指示出该簇是文件中的最后一个簇。簇号还被
记录在文件的目录项中(第9章讨论“目录和文件”)。当分配一个新簇时,FFFh/FFFFh
项就移向FAT中新簇的项,并且新项的簇号就填上了前面已使用的FFFh/FFFFh值。
以这种方式,FAT将所有分配给文件的簇都链接起来,而不管这些簇碰巧在磁盘的
什么位置上。
特殊码指示某个簇是否遭到损伤,以及若受损伤,则是什么样的。值FF7h~FFEh
(FFF7h至FFFEh是用于16位FAT表的)就用于这个目的。
“32M界限”长期以来一直是DOS的一个著名特性;但在V4中却消失了。在了解这
种改变对FAT编码和其它磁盘参数产生的影响之前,让我们先看看这个界限是在哪里
产生的。
尽管起初选择来作为FAT项的大小是形成这一界限的原因之一,但真正的界限却
是所有I/O例程中使用的最多只有512个字节的扇区大小和16位的扇区号值限制所带
来的结果。因为16不能超出65535,所以在一个卷中,65535就变成了最大的可能的扇区
号。在512个字节的扇区大小情况下,结果是卷大小的最大值变成了33553920个字节,或
32M。
vs之前的DOS版本都限于只使用12位FAT项;磁盘所能包含的最大簇数是2的12次方
(或4096)。4096个可能的FAT项值之中的9个用来表示簇的状态,所以只有4087个簇
号可用。最大的FAT占用了6144个字节,或12512个字节的扇区。
注意这些4087个簇所能代表的实际磁盘大小的最大值完全依赖于所选择的簇大小。对
于大小只有单个扇区的簇,那么就只有2092544(40870512)个字节。这个数字能
满足所有常用的软盘(没有人能解释2个扇区的簇怎样变成了标准的簇)。但它甚
至对于最小的硬盘也是不能用的。
将簇大小增加到8个扇区就会相应地将大小限制提高到16740352个字节,足够用于
原始的XT型号的10兆字节驱动器。将簇大小加倍成每簇16个扇区,它所带来的是8192
168页
个字节的最大分配单位(甚至对于1个字节的文件也会如此)会将FAT容量发展到32M
的界限。但这种空间浪费使用户很不满意。
簇大小的进一步增加能在FAT中扩展该界限,但在卷中却不能。只有扇区大小的增
加(或者一个表面的增加,即第三方驱动程序所采用的方式)能够突破卷大小的限制。扇区
大小还会受到所用的控制卡硬件的限制。
3.0版中浪费问题能被避免,在第3版里允许DOS使用另外编码的FAT。如果磁盘
驱动器足够大,而能产生超过408)个簇,即簇大小为8个扇区(比17兆字节大),DOS V3
就变到4扇区那样的簇大小并使用一个16位的FAT项。16位FAT允许簇号最大值为
65527。FAT中有了这么多簇,就能使用2个扇区的簇而不超出32M。
但是这样大小的FAT对于每个拷贝需要131072个字节,或者说,对于FAT对,在使
用512个字节的标准扇区大小情况下,需要512个扇区。要将系统开销保持到尽可能低的
水平,DOS设计者选择了FAT项的限制数为16384,于是就保持了32个兆字节的限制和
4个扇区的簇,并将FAT空间减少到128个扇区。
随着时间的推移,目前有许多磁盘的容量都大大地超过了32M的界限(本书就是在
一个80M硬盘的系统中写成的,该硬盘被分成3个26M的逻辑驱动器)。于是,在4.0版
中,扇区号可以是16或32位JAT中的字节数可以与16位项一起增大到最大值,这就
将磁盘空量的限制提高到了128M,每个族有4个扇区;或者提高到256M,每个族的大小
仍为8个扇区。但超过300M的驱动器已经在作广告;谁知道将来会出现什么?
磁盘格式化时,FORMAT程序确定使用哪个编码方案。如果磁盘大小指示它能充分
地被12位FAT来表示,那么就使用12位FAT,否则就使用16位的FAT。如果卷的大小
超出32M,那也可以使用32位扇区号(否则,旧的16位扇区号依然保留)。下面让我们来
看看FAT的各个类型。
一、12位FAT
12位FAT所带来的表比16位FAT要小25%。这可能要归因于采用了12位FAT。
在3个字节中拥有2个12位数。图8.4显示了12位FAT的样本扇区。
请注意文件分配表的组成。在这个例子中,头两个FAT项(头3个字节)包含有系统
信息。第0簇和第1簇的数据区就不能被FAT访问。紧跟着的1/2个字节(12位,第2簇
的FAT项)后面是第3簇的FAT项,依此类推。注意默认值0103h上的3个字节,它们是
第2和第3簇的FAT项。用下列公式(所有值都是十六进制的)可将034000分成2个独
立的FAT项:
第1项=((第2字节AND 0F)*1000)+第1字节
第2项=((第3字节*10)+(第2字节ANDF0)/10)
于是第2簇的FAT项如下所示:
第2FAT项=((40 AND OF)* 1000)+03=((0)* 1000)+03
=03
第2FAT项=((40 AND OF)* 1000)+03
169页
图8.4一个FAT样本
第3簇的FAT项如下
第3FAT项=00* 10+((40 AND F0)/10)=0+(40/10)=04
每个FAT项都指向文件所占有的下一个簇。于是,FAT项就形成了一个链;如果该
链中的“链结”放在一起,那么该链就表示某个特定文件所占有的簇。
但是FAT项的一些值并不代表某个后续簇号;相反,这些值代表该簇的一个状态。
表8.3总结了FAT项的可能代码。
表8.3 12位FAT分配字节
分类 代码
自由的可分配的项 0
文件的一部分(下一簇的指针) 2—FF6
坏簇 FF7
簇链的结束 FF8—FFF
使用图8.3所示的12位FAT,让我们来看看一条簇链。文件的目录项指向文件所占
用的第一个簇(第9章讨论目录项)。因此,IBMBIO.COM(长22100个字节)的目录项指
向开始簇号:2。如果看看第2簇的项,会看到第3簇的指针—并且第3簇指向第4簇
(记住你只是在作算术)。第4簇指向第5簇,后者以指向第6簇,依此类推直到第18h簇。
这里,FAT项为FFFh,表明已到簇链的终端。
将第2簇作为起点,可以用下列方法找到下一簇:
1.簇号乘以2并记下结果。
2.在所得到的偏移值上取得一个字。
170页
3、如果原来的簇号(在这种情况下是2)是偶数;就获取该字的低12位;否则就获取
高12位。
二、16位FAT
DOS V3的发表,为较大的硬盘和使用每项16位(2个字节)的FAT提供了支持。图
8.5显示了来自16位FAT的一个样本扇区。
图8.5一个16位的FAT样本
对这个文件分配表的解释要比12位FAT直接多了。开始的两项(4个字节)用于系
统信息;每个后续项占有2个字节。注意这两个字节位于缺省值0104h(即第2簇的FAT
项)。这里的值(0003h)指向第3簇的项。
表8.4 16位FAT分配字节
分类 代码
自由的可分配的项 0
文件的一部分(下一簇的指针) 2—FFF6
坏簇 FFF7
簇链的结尾 FFF8FFFF
与12位的版本一样,它的每个FAT项也指向文件所占有的另一个簇。于是FAT项
便形成了一条链,当链的所有“链结”放在一起时,链就指示某个特定文件所占有的簇。象
12位FAT那样,FAT项的其它值不代表一个后续簇号。它们代表簇的状态。表8.4总结
了FAT项的可能代码。
让我们来看看一条簇链,它使用16位FAT(如图8.5所示)。文件的目录项指向文件
所占有的第一个簇;在图8.5中,IBMBIO.COM的目录项(长度为22100个字节)指向起
始簇号2。第2簇的项指向第3簇。第3簇指向第4簇,后者指向第5簇,依此类推,直到
171页
第0Ch簇。在0Ch簇上FAT项为FFFFh,这指示出已到达簇链结尾。
在4.0版中获得的更大磁盘容量是通过允许FAT达到最大的容量而不是削减它来
得到的。尽管3.0版和4.0版的扇区号大小可能改变,但它们的FAT对策之间的唯一差
异就是如何获得更大磁盘容量的方法。
三、更多的FAT信息
当DOS为文件请求空间时,空间就会以一个或多个簇为单位来分配给这个文件。由
12位和16位FAT的有关讨论可以知道,一个文件的簇是链接在一起的,每个FAT项都
给出了下一个项的簇号(见图8.6)。
图8.6 FAT策链接
FAT保留了第0项和第1项的空间,但未使用它们。 FAT的第一个字节用作磁盘标
识(ID),有助于识别磁盘的格式(见表8.5)。因为为了系统而保留了0簇和第1簇,所以
第2簇是能被分配的第1个簇。
表8.5一些可能的FATID字节值
值 磁盘特征
F0 不能识别
F8 固定的磁盘
F9 双面,15扇区/磁道
F9 双面,9扇区/磁道(720K)
FC 单面,9扇区/磁道
FD 双面,9扇区/磁道(360K)
FE 单面,8扇区/磁道
FF 双面,8扇区/磁道
172页
从FAT的讨论中可知,DOS借助整个的簇来分配文件空间。于是不管文件实际有多
大,一个文件的最小磁盘应用单位就是一个簇。一个字节的文件可能占用512、1024、2048
或者更多字节的磁盘空间,具体决定于每个簇的扇区个数。
由对软盘的BPB编码可知,样本磁盘有2个FAT表。无论何时磁盘操作在磁盘上分
配或释放所分配的空间时,两个FAT都将自动地更新。当磁盘被第一次访问时,DOS会
比较两个FAT,看看它们是否稳定。尽管可以有2个以上的FAT,它们连续存放在磁盘
上,但大多数磁盘只有2个FAT。
最后的FAT到达根目录后,每个项都有32个字节。 BPB给出目录的大小,以便确定
文件区域从哪里开始(紧跟在根目录之后)。
既然已知道从哪里找到磁盘上的内容,那么让我们再来看看DOS提供了哪些功能来
操纵它们。
8.2利用磁盘功能
因为文件系统(包括所有刚刚讨论过的表)是DOS的一个结构,所以没有BIOS功能
能用于DOS文件系统。对于BIOS,磁盘只是一系列的扇区,从0扇区开始,连续递增到最
高数目的扇区。BIOS了解磁道、扇区和磁盘磁头,但不了解文件、FAT和目录。接下来所
有将要使用的功能都是面向DOS的功能。
8.2.1驱动器信息
可利用DOS功能调用来获取磁盘驱动器方面的信息。drvinfo.c程序分析了怎样获得
和显示这个信息(见列表8.1)。注意该程序没有将4.0版的新的32位扇区的可能性考虑
进来;4.0版的《技术参考手册》也没有指出驱动器信息要注意这种变化。
列表8.1
/*dRvinfo.c
Listing 8.1 of DOS Programmer'S Reference*/
#include<Stdio.h>
#include<dOS.h>
#include<Ctype.h>
#include "drvinfo.h"
/*Prototypes*/
void get_drvinfo(char drv,struct drvinfo*info);
unsigned int get_drive(void);
void get_drvspace(char drv,struCt drvinfo*info);
void main()
{
int drive;
drive = get_drive();
printf("\n\n");
printf("Current Drive Code=%u(%c:)\n",drive,'A'+drive);
printf("\n");
get_drvinfo('A'+drive,&info);
173页
printf("Drive %c: infOrmation from funCtion 1Ch\n",'A'+drive);
printf("Number Of clusters=%lu\n",info.cluSters);
printf("SectOrS per Cluster=%lu\n",infO.spc);
printf("Physical sector Size=%lu\n",info.SecSize);
printf("DriVe Size=%lu Kb\n",
(info.clusters*info.spc*info.secsize)/1024);
printf("\n");
get_drvspace('A'+drive,&info);
printf("Drive %c: infOrmatiOn frOm function 36h\n",'A'+drive);
printf("Number of cluSterS=%lu\n",infO.ClUSterS);
printf("SectorS per cluSter=%lu\n",info.Spc);
printf("PhySical sector size=%lu\n",info.SecSize);
printf("Drive siZe=%lu Kb\n",
(info.clusters*info.spc*info.secsize)/1024);
printf("Available cluSters=%lu\n",infO.avail);
printf("Available Space=%lu Kb\n",
(info.avail*info.spc*info.secsize)/1024);
Printf("\n");
}
/*Fetch the current drive code*/
unsigned int get_drive()
{
union REGS regs;
regs.h.ah=0x19;
intdos(®s,®s);
return (regs.h.al);
}
/*Fetch drive information using function 1Ch*/
vOid get_drvinfo(drv,info)
char drv;
Struct drvinfO*infO;
{
union REGS regs;
Struct SREGS segs;
int dn;
/*Converts drive letter to internal representation*/
drv=toupper(drv);
dn=drv-'A'+1;
/*Set up and call DOS*/
regS.h.ah=0x1c;
regS.h.dl=dn;
intdosx(®s,®s,&segs);
infO->Spc=regS.h.al;
info->fatseg=segs.ds;
infO->fatoff=regS.X.bx;
infO->SeCSize=regS.x.cx;
infO->CluSterS=regS.x.dx;
}
/*Fetch drive information using function 36h*/
void get_drvspace(drv,info)
174页
char drv;
Struct drvinfo*info;
{
union REGS regs;
struct SREGS segs;
int dn;
/*Converts drive letter to internal representation*/
drv=toupper(drv);
dn=drv-'A'+1;
/*Set up and make the DOS call*/
regs.h.ah=0x36;
regs.h.dl=dn;
intdosx(&regs,&regs,&segs);
info->spc=regs.x.ax;
info->avail=regs.x.bx;
info->secsiZe=regs.x.cx;
info->clusters=regs.x.dx;
}
为了获取有关驱动器的信息,drvinfo.c调用了下列几个子例程:
get_drive(),获取当前驱动器号
get_drvinfo(),获得驱动器的一般信息
get_drvspace(),获得其它信息
所有这些信息都放在drvinfo结构中,正如drvinfo.h所定义的那样,见列表8.2。
列表8.2
/* drvinfo.h
Listing 8.2 of DOS Programmer's Reference*/
struct drvinfo{
unsigned long spc; /*Sectors per cluster*/
unsigned long avail; /*Available clusters*/
unsigned long fatseg;/*FAT segment of ID byte*/
unsigned long fatoff;/*FAT offset of ID byte*/
unsigned long secsize;/*Physical sector size*/
unsigned long clusters;/*Number of clusters*/
char fatid; /*FAT ID byte*/
}info;
通过定义一个结构来容纳有关磁盘的信息,就可以保证在需要这些信息时,这些信息
总是有逻辑地组织在一起的。
注意列表8.1中的drvinfo.c程序包括两个子例程调用;这些调用说明了同样的信息
可用多种途径来获取。
获取驱动器信息是一个简单的过程——可以调用DOS服务中断(int 21h)。在C语言
中该调用由intdos()函数来执行,在pascal中则由Msdos来完成。DOS服务将驱动器代码
返回到Al寄存器中。
175页
get_drive()函数不解释驱动器代码;它只是将代码返回给drvinfo.c。但是请求特定驱
动器信息的过程要比请求驱动器号的过程复杂一些。信息返回到段寄存器中,就象返回到
通用寄存器一样。要访问段寄存器,必须使用intdosx()函数,正如在get_drvinfo()和get_
drvspace()函数中所做的那样。
在这些函数调用了DOS(intdosx函数调用)之后,该函数就会将从寄存器中返回的信
息保存在函数调用所传递的info结构中。处理象这样结构中的信息,能使没有寄存器或
Dos调用方面知识的人,通过调用该函数的程序来访问它。稍稍再做一点工作,这些函数
就可以包含在那些不知道怎么处理DOS的程序员的函数库之中。
get_drvinfo()和get_drvspace()是编程实践中的很好例子——将执行细节隐藏在函
数之中。这种情况下,用户就通过所熟悉的驱动器字母(A、B、C等)标准格式来确定正确
的驱动器代码。
允许驱动器名作为一个字母来传递,这是一条途径,它可以隐藏如下事实,即在DOS
和BIOS例程中,驱动器标志并不总是稳定的。有些例程使用0来表示A驱动器,但有些则
用0来表示默认的驱动器。
get_drvspace()所返回的信息可能比get_drvinfo()返回的信息在程序中用得更多。例
如,用get_drvspace()可以利用DOS功能36h所返回的下述信息来确定磁盘上有多少自
由空间可用。
寄存器 内含物
AX 每个簇的扇区数
BX 有效扇区数
CX 每扇区的字节数
DX 每个驱动器的簇数
要计算驱动器上的自由空间,可利用下列公式:
BX*AX*CX
下列公式则计算驱动器容量的总数:
DX*AX*CX
如果只想确定磁盘上有多少自由空间,可以编写函数get_free())来仅仅返回这方面
的信息。列表8.3中的free.c程序调用了get_free()来获得这方面的信息,它为得到可用
的和整个的磁盘空间,而使用了驱动器名和指向整数的指针。
列表8.3
/*free.c
Listing 8.3 of DOS Programmer's Reference*/
#include<Stdio.h>
/*Prototypes*/
void get_free(char drv,unsigned long*avail,unsigned long*total);
void main(argc,argv)
int argc;
char*argv[];
176页
{
unsigned long avail,total;
get_free(*argv[1],&avail,&total);
if(*argv[1])
printf("Free diSk space on drive %c: iS %lu Kb Of %lu Kb\n",
*argv[1],avail,total);
else
printf("Free disk space on default drive is %lu Kb of %lu Kb\n",
avail, tOtal);
}
编写这个程序来为驱动器名而检查第一个命令行参数。如果没有提供第一个参数,程
序就假定它应该发现默认驱动器的信息。
get_free()函数确定它应检查哪一个驱动器,然后设置参数并进行对DOS的调用(见
列表8.4)。功能36h用于确定自由空间,但大部分磁盘信息已被舍弃,因为该函数的有限
目的并不需要它们。
列表8.4
/*get_free.c
Listing 8.4 of DOS PrOgrammer's Reference*/
#include