DOS程序员手册(二)

2.6存储设备
    随着DOS的升级,磁盘存储容量也有了很大扩充。表2.4介绍了软盘容量的增加以
    及所支持驱动器型号的数量。
                                      表2.4软盘容量
DOS版本                     软盘                      容量
1.0                        5.25英寸SSDD                       160K
                            5.25英寸DSDD                       320K
2.0                        5.25英寸SSDD                       180K
                            5.25英寸DSDD                       360K
2.1                        5.25英寸DSHD                       1.2M
3.2                        3.5英寸DSDD                        720K
3.3                        3.5英寸DSHD                        1.44M
6.0                        3.5英寸DSHD                        2.88M
2.6.1物理磁盘结构
      磁盘的记录表面,分成了很多同心圆磁道,每个磁道分成多个扇区。磁道和扇区的数
    目随着磁盘的型号(软盘或硬盘;单面或双面;倍密度或高密度;3.5英寸或5.25英寸
 
30页
等。)不同而变化。图2.8介绍了磁道和扇区在磁盘中的安排。
            图2.8磁盘磁道格式      图2.9固定的磁盘(硬盘)
    因为硬盘驱动器(相对于可移动的媒体,也称为“固定磁盘”)包括多个盘片,硬盘空间
又分成柱面。每个柱面包括每个盘片的每个面上的一个磁道。图2.9介绍了磁道怎样组
成了柱面。
    讨论磁盘结构的第8章将详细讨论磁盘的物理格式。第8章还介绍怎样调用控制低
层次磁道格式化操作的内部格式化程序。
2.6.2逻辑磁盘结构
    FORMAT程序不但建立了磁盘的扇区结构,而且还建立了逻辑结构,即专属于DOS
的控制存储在磁盘上数据的一种方式。图2.10介绍了这个逻辑结构。
      引导扇区  FAT1       FAT2       根目录       数据
                                图2.10磁盘的逻辑结构
    FORMAT程序最关键最常用的任务就是格式化磁盘——将物理磁盘分成逻辑磁盘
和扇区并填入初始值。
    在格式化完磁盘后,FORMAT程序在磁盘上建立了三个区域;引导记录、文件分配
表和根目录。磁盘的保留区(上几部分不在这里)是文件的存储区。
      引导记录。
    引导记录总在每个逻辑磁盘的第一扇区中。从DOS 2.0开始,引导记录便包含磁盘
引导程序(仅几百个字节长)和磁盘特性表。当系统启动时,引导程序被调入内存并且引导
程序从磁盘中调用操作系统文件。如果没有发现这些文件,引导程序将显示出错信息。引
导处理将在第3章“动态的DOS”一章中详细介绍。
      文件分配表
    文件分配表(FAT)是磁盘的映象。它跟踪磁盘的每一部分是否被占用,或不能被占
用(例如,因为格式化错误)。磁盘区域是以簇的形式被文件占用的,每个簇的情况反映到
FAT表中。根据磁盘的大小,簇的大小可以在一到八个或更多的扇区范围内变化。为了适
 
31页
应硬盘存储容量的扩充,FAT项(最初每个项12位)现在已扩充为每项16位。FAT项的
大小是DOSV2、V3和V4之间最主要的差异之一。
      要强调的是,FAT是DOS中最重要的关系到磁盘的一个特殊功能。FAT可以建立
大于512字节的文件,任何损害FAT的行为都可引起文件和程序被截断。FAT的详细介
绍和讨论,将在第8章“磁盘”中介绍。
      根目录
      在磁盘上最后一部分的系统信息是根目录,它紧接着FAT表。主要包含可操作文件
的以下信息:
      ·一个8字节的文件名
      ·一个3字节的文件扩展名
      ·文件大小(以字节计)
      ·文件的日期时间印记
      ·文件的起始簇号
      ·文件属性码
      每个项有32字节长,剩余的字节留给将来扩充。对给定的磁盘,根目录的大小是固定
的。在160K单面磁盘中,根目录可以包含64个项;在20M硬盘中,可以保留512个项。目
录区的大小是有限制的,因此DOS可以区分数据区是从什么地方开始的。在DOS V2及
其后版本中,该限制是没有问题的,因为可以通过建立子目录来越过这个限制,实际上并
无大小的限制。
      更详细的关于磁盘结构和目录的讨论,请见第8章“磁盘”和第9章“目录和文件”。
                        2.7软    件
      在物理机器之上,提供的软件在PC或兼容机上建立了另一层虚拟机。软件从BIOS
开始,它为隐去已安装的特定设备而建立了一个机器的标准窗口。建立在BIOS之上的
DOS机器是大家很熟悉的形式(以文件和目录为单元)。
2.7.1BIOS
      在虚拟机上的第一个软件层是BIOS(基本输入/输出系统)。这个软件生成经常涉及
的最低层的机器。
      真正的BIOS是由固化在ROM中的,包含很多基本机器功能的程序组成。包含很多
I/O系统软件的描述也作为BIOS的一部分,这些软件,是从磁盘中调入的,它扩充了
BIOS的功能,使得BIOS能处理所有的系统输入/输出要求。 BIOS的目的就在于使高层
软件和计算机中可能发生的硬件变化隔离开;I/O系统的目的在于作为BIOS和DOS的
缓冲接口。BIOS和I/O系统结合起来,为高层软件提供了一个基本的,定义一致的服务程
序集合。
      每个计算机制造商(包括IBM)都为其机器提供了BIOS和自定义的I/O系统。 Mi- 
 
32页
    crosoft公司提供了一个称为SYSINT的模块(见第3章),来管理系统的初始化和DOS的
    装载。 BIOS和I/O系统的结合必须为适应高层软件的调用而符合一定的标准。例如,Mi-
    crosoft的DOS核心,就使用BIOS服务程序来辅助它自己的很多操作。
        PC兼容机经常带着由制造商提供的BIOS ROM版本。没有人能复制IBM BIOS
    ROM中的编码,但他们能提供与IBM相同的处理的中断结构和使用相同的数据表区域
    的BIOS。尽管编码不同,但它提供的服务也是DOS需要的。
      如果通过定义的BIOS中断,存取第三方的BIOS ROM,则可以超出IBM PC服务程
    序提供的方式进行工作。如果依赖未公开BIOS ROM功能知识来编写某个程序去完成某
    件事,所有赌注将会失去。这种编码的一个常见例子就是在多速度兼容情况下转换速度切
    换的这样一个程序;因为IBM从来没有这个特性的类型,没有适用它的接口标准,因此就
    必须反复试验才行。
      在本书后面“BIOS参考手册”一节中将详细介绍BIOS的功能。所有这些功能都在所
    有的IBM兼容计算机提供的BIOS ROM中。
    2.7.2 DOS核心
      Microsoft提供的DOS核心是基于标准BIOS服务程序之上的程序。 DOS核心提供的
    服务程序与硬件无关,它可以被应用程序在各种系统中调用。读者将和这些服务程序渡过
    很大一部份时间(注意:“DOS” 指的是IBM的DOS版本或非特别说明的MS-DOS)。
      DOS服务程序可以按属性分成以下部分:
        ·字符I/O
        ·目录操作
        ·磁盘控制
        ·动态内存分配
        ·出错处理
        ·文件操作
        ·各种系统功能
      ·网络功能
        ·程序初起和终止
        有两种方式调用DOS服务程序。某些服务程序可能通过软件中断直接调用。然而,大
    多数DOS服务程序则是通过将功能号放在寄存器AH中,然后调用运行Int 21h来实现
    的。在本节后面“DOS参考手册”一节中将详细讲述DOS中断功能。这些中断功能适用于
    所有的PC及其兼容机。这节还将指出系统之间的不同点。
    2.7.3命令处理器
        对大多数工作在PC机上的人来说,命令处理器(或外壳)就是操作系统。这些使用者
    认为提示符>是来自操作系统而不是来自程序。仅在几年之前,交互式的操作系统才开
    始创建这种方式。
 
33页
  今天,外壳的接口已成为标准。外壳不仅改变了处理方式,并且,还因为只改变了操作
系统中的一部分,所以很容易加入新的特性。尽管外壳接口不是UNIX的设计者发明的,
但这种类型的程序的应用(如COMMAND.COM)则是被UNIX操作系统推广开的。
UNIX系统已经有了几种标准的外壳(如csh、ksh和sh等一些名字)。
    COMMAND.COM的结构对它的操作是非常重要的。这个程序有三部分:初始化部
分,驻留部分和暂驻部分。
    当COMMAND.COM启动后,初始化部分和驻留部分首先被从磁盘上调入。初始化
部分设置系统,运行AUTOEXEC.BAT文件,然后调用并把控制传送给驻留部分。正如
其名字所表示的,暂驻部分(按照内存的要求)被调入和调出。“常驻内存”的驻留部分在增
加某些事情时,对重新调入暂驻程序作出相应的反映。
    如果COMMAND.COM的所有功能都编在同一个程序中,这个程序将占相当大的
内存数量(大于20K)。尽管这个内存数量和大多数应用程序所需的内存相比是沧海一粟,
但是,要想利用最后的某些作用来装载文件或将最后一些单元放到离散片中去时,这个数
量就值得考虑了。为了减少COMMAND.COM的内存消耗,COMMAND.COM的常规操
作编码和程序的内部命令的编码放在暂驻部分。因为这个部分有时被其它程序覆盖,
COMMAND.COM的驻留部分查看某个暂驻部分时必须调入,如果需要,则调入该部分。
      COMMAND.COM运行程序可分成三种类型的命令:
      ·内部命令(建立在COMMAND.COM中)
      ·外部命令(存在磁盘上)
      ·批文件(存在磁盘上)
    COMmANd.COM的暂驻部分储存有内部命令的编码。当用户输入一个命令名字
后,COMMAND.COM首先查询是否是其中的一个内部命令。如果COMMAND.COM没
有这个内部命令的名字,则该程序先在当前目录中查找,然后到路径中查找。 COM- 
MAND.COM查找外部命令时应先查.COM扩展名命令,然后再查.EXE扩展名的命
令,如果在给定的目录中都没有发现这些命令,COMMAND.COM则继续查找相应的名
字的批文件(扩展名为.BAT)。
    批文件是命令处理器允许的特殊类型的“程序”。这个文件由描述一组给定顺序执行
的命令组成,并可配以参数替换及判定和跳转等简单的控制。 COMMAND.COM逐行地
执行批文件中的命令。每一行都由将要执行的命令组成:或者是一个可执行的命令,或者
是只允许在批文件中使用的内部控制命令,其实际操作很简单。 COMMAND.COM的暂
用部分从批文件中取出一行,处理任何参数的替换,都用DOS EXEC功能执行该命令。当
每一行结束后,COMMAND.COM取出下一行并接着执行。
2.7.4设备驱动程序
    在软件技巧方面,大多数改进都涉及使底层的硬件消失。例如:高级语言一个优点就
是它们不需要让程序员知道寄存器、位以及字节(不是每个人都同意这个观点)。类似的,
操作系统要改进的主要是:当我们要使用某个设备时不得不为自己的设备写驱动程序的
 
34页
    问题。
      CP/M使用标准的设备来处理终端和打印机。 DOS则已向前走了一步,它使设备有
    了更多的内部可变化性,并在不重编译整个系统的前提下,有可能安装自己的设备。想想
    看!以前,如果要给系统增加一个新的设备,不得不修改操作系统的内部以便使增加的设
    备可以工作。现在,DOS包含了很多灵活的驱动模块允许用户编写设备驱动程序,并在系
    统启动时选择地加入。要理解它是怎样工作的,还得了解设备驱动程序的知识。
      操作系统软件包含一组运行硬件的设备驱动程序(驻留驱动程序)。每个驱动程序按
    照它的特性组织它们的调用接口,因此,DOS在不用知道硬件怎样工作的情况下就可以
    操作硬件设备。
      当启动计算机时,DOS通过标准的初始化入口指针初始化所有的驱动程序(见第3
    章“动态的DOS”)。现在,读者所要知道的操作设备的全部内容是一系列标准入口指针定
    义的功能。需要控制的设备类型决定于入口指针的定义。在第12章“设备驱动程序”中,
    将更详细的了解并实际生成一个简单的设备驱动程序。
      DOS将设备分成字符设备和块设备。字符设备是基于字符到字符操作的设备(例如,
    键盘和显示器);块设备是基于块传送的设备(磁盘和RAM磁盘)。驱动程序的每个类型
    都有相应的处理程序功能的入口指针。
      读者可以编写一个自己的设备驱动程序并把它加到CONFIG.SYS文件中。这个设
    备在下次启动时就被加入到系统中。驱动程序的操作和驻留的驱动程序相同。读者甚至
    可以用全新的编码替换掉已存在的字符驱动程序,例如ANSI.SYS和其它驱动显示器的
    驱动程序。
      可安装驱动程序使用户可以在系统中加入原设计没有考虑到的新的设备(MOUSE
    .SYS加入鼠标;EMM.SYS用来扩充内存)。一句话,DOS已经创造了一个环境,使用户
    安装自己所需的新的设备成为了可能。
                        2.8小      结
      在本章中,读者了解到DOS系统是作为“虚拟计算机”的层次存在的。从最底层硬件
    开始,接着升到BIOS和DOS系统,每个层次都提供了一个兼容的、有补充下一层次计算
    机系统使用的必要功能的逻辑计算机。
      最底层,硬件设备(组成系统的内容的组合)在不同系统之间,可以有很大的差异。在
    下一层,BIOS提供了一个“计算机”,它以其定义的服务程序使用户在不同的系统中可以
    用相同的方式工作。这些基本的输入输出服务程序允许以固定的方式控制系统的设备。
    DOS提供了一个相对于BIOS更高层的服务(高层次的提炼)。 DOS服务程序通常完成一
    些系统操作,例如创建文件和目录等。在这个高层次中,COMMAND.COM提供了一个用
    户接口用以控制不同的服务程序。
      现在,读者已经理解了一些DOS结构的内容,让我们将DOS放到动态环境中,学习
    它是怎样工作的。
 
 
35页
          第3章动态的DOS
在第2章“DOS系统结构”中,描述了DOS的结构,以及它的硬件支持和它的基本软
件模块。这一章将介绍这些元素在动态环境中将怎样操作,我们将看到当系统启动时会发
生什么,它怎样处理命令以及程序怎样运行等。
      然后,本章还介绍系统和程序操作的概述,将更详细地了解在DOS下的中断和内存
管理。这章和第4章将为第5章至第13章的实际编程打下基础。
                    3.1 DOS启动顺序
      当打开或复位基于8086系列微处理器的的系统时,微处理器自动地从地址FFFF:
0000h启动程序运行。这是由处理器设计而并非DOS控制的。ROM BIOS在FFFF:
0000h提供了一个跳转指令,跳转到硬件测试程序和ROM引导码的开始(在下面讨论的
ROM BIOS适用于PC及其兼容机)。
      当打开(冷启动)系统时,系统将对一系列硬件进行开机自检(POST),它检测已安装
的内存数量,测试可用和可操作的外部设备。在启动过程中,大多数机器将快速显示变化
的内存大小指数和报告串行口及并行口等等。
      如果系统是热启动(特别是通过按Ctrl-Alt-Del组合键),POST就跳过去不执行了。
计算机通过存储在0040:0072h中的有效值来判定是冷启动或是热启动。如果值是
1234h,则是热启动,如果是其它值时,则引起冷启动。这个特殊值。是由Ctrl-Alt-Del的中
断处理程序放入的。
      当冷启动或热启动完成后,控制接着传给ROM引导初始化程序。
      引导初始化程序在低端内存中设置中断向量表(特别是由POST定位的硬件向量
表)。该程序还初始化在内存0400:0000h位置上的ROM BIOS的表,它还完成很多硬件
设置,如启动动态内存刷新等。该程序然后搜索从A000:0000h到F000:0000h的内存
区域以便查找其它的扩展ROM;这些扩展程序有一个单一的字节来标识它们为ROM扩
展程序。(典型的ROM扩展程序为EGA或VGA图型ROM,SCSI控制程序以及其它的
使用ROM空间的插补控制程序。)引导程序初始化任何被发现的ROM扩展程序。在初始
化完成后,ROM引导码启动系统本身。
      ROM引导程序接着从磁盘的第一个扇区(引导扇区)读出磁盘引导码。这个引导码
是一个响应系统启动和运行的一个微服务程序。 ROM引导程序检查所有有引导盘的驱
动器,用驱动器A的引导扇区启动。如果发现有这样可使用的软件,就首先使用它;这种
 
36页
尽可能从备份软盘启动的特性是为了当硬件出现故障时系统还能够启动。如果没有发现
    引导扇区、IBM PC将控制传给ROM BASIC作为无盘系统的启动;PC兼容机则提示用
    户插入系统盘,然后等待用户键入,随制造厂商ROM的不同,实际的启动过程也是有区
    别的。
    当发现DOS被调入的引导扇区之后,ROM引导程序将它调入高端内存(地址为
    007C:0000h),并将控制传送给磁盘引导程序。
      当磁盘引导码被调入并得到控制后,它将查看磁盘去查找IO.SYS和MSDOS.SYS
    文件。
    注意在IBM PC 和很多兼容机上,这些文件命名为IBMBIO.COM和IBMDOS.COM。
          在这里讨论的MSDOS.SYS对应两个模块(MSDOS.SYS和IBMDOS.COM),IO.         
SYS对应两个其它模块(IO.SYS 和 IBMBIO.COM)。除非特别指定,否则所有的
          操作都适合这些模块集。
      磁盘引导程序并不知道文件系统或磁盘结构,实际上它也做不到,因为所有详细的磁
盘文件结构都放在MSDOS.SYS文件中,同时,由于该文件还未被调入和初始化,数据还
不能使用。因此,以下需求要强加于引导盘上:
    1. IO.SYS必须作为根目录的第一个文件入口。
      2. MSDOS.SYS必须作为根目录的第二个文件入口。
      3. IO.SYS文件本身也要求必须放在第一个文件处,并且要以正确的顺序存储在连
 续的簇中。原来第三个文件要求接着第一个文件,也存放在连续的簇中;然而,从
        版本3开始,它可以被放在磁盘的任何地方并且可以分片。这使得从版本2可以
        很容易地升级到增大的版本3(这与版本1和版本2之间容易的升级所考虑的问
          题有很大的不同)。
        现在我们已经就知道为什么当SYS.COM程序认为所要生成的引导盘上有内容时,
    会产生“故障”。生成引导盘必须是当该盘空的时候(或者像是空的,即第一个目录入口由
包含00h的字节开始的,表示它从来没有被使用过)。在其它情况下,SYS.COM将不工
    作。
        如果磁盘引导程序不认识文件系统,它是怎样认识目录入口和在哪儿找到文件的呢?
 这是从BIOS参数块(BPB)了解到这些信息的,BPB放在引导扇区的从0Bh到17h的区
    域中(见图3.1)。
                  0               0Bh                  17h              511
         JMP至Bootstrap           BPM                 Bootstrap(磁盘引导程序)
                                            图3.1引导扇区
   BPB告诉引导程序足以使之找到目录开始和文件空间的磁盘结构。找到文件后,引
导程序将IO.SYS(IBMBIO.COM)复制到BIOS表之上的低内存中。然后,根据所使用的
系统,或由引导程序或由IO.SYS初始化程序将MSDOS.SYS复制到了IO.SYS之上的
 
37页
内存中。
注意在版本3之前,尽管版本2推出了BPB,但没有用。因为这一点,很多非IBM制造
       商在他们的引导扇区中舍弃了BPB。在这样DOstk本之下的格式化的磁盘不能被
        版本3或以后的版本读取。但可以通过从版本3的磁盘中以相同标志覆盖原始磁
        盘的引导扇区继而复制来增加BPB。这种不兼容造成已升级的版本3的不完全兼
        容,但它是违反了DOS标准的变化版本2造成的。
      IO.SYS有两部分。制造厂商(IBM或其它厂家)支持第一部分一BiOS。 BIOS包括
驻留的设备驱动程序,它当BIOS被调入时运行特定硬件的初始码。 Microsoft支持第二部
分,称为sYSINIT模块。
      当IO.SYS得到计算机的控制时,它运行系统需要的任何硬件特定的初始化操作。在
初始化过程中,IO.SYS检查低内存区的BIOS表(由前面ROM BIOS初始化程序设置
的)以查看使用了哪个硬件。不需要的驱动程序在这里就被去掉了。然而,大多数版本的
DOS在IO.SYS中只提供最基本的不作改变的驱动程序。然后,控制传送给SYSINIT模
块。
      SYSINIT检查可用的内存,并且自己重新定位于高端内存。没有永久内存空间分配
SYSINIT。因为它是一个仅供短暂使用的临时模块。 SYSINIT从定位于高端内存后允
许它的模块提供相应的服务,在完成任务后,所有低端内存就允许DOS使用。
      SYSINIT的高端内存备份将MSDOS.SYS复制到IO.SYS初始码之上(这意味着
SYSINIT的原始备份也复制上去)。这个操作使得DOS的所有内存让系统初始化使用成
为可能。接下来,使用DOS的Int 21h的文件服务程序(当MSDOS.SYS被复制到RAM
之后,它就可用了),SYSINIT打开CONFIG.SYS文件(如果它存在的话)。整个文件都被
调入内存,所有的字符被转成大写,并且CONFIG.SYS被解释成(每次一行)系统的配置
信息。
      内存被分配络文件表、磁盘缓冲区和文件控制块。在CONFIG.SYS不存在或没有通
FILES、BUFFERS和FCBS指定特定值时,系统自动指定隐含值。如果CONFIG.SYS
指定DOS被调入高端,DOS的核心被重新分配到内存高端,并且标明已经被使用。另外,
所有在CONFIG.SYS中指定的驱动程序也将被调用,初始化和连接到由系统管理的驱
动程序列表的前面(如果新的字符驱动程序和已驻留的驱动程序同名,当需要存取该驱动
程序时;新的字符驱动程序总是首先被找到,这样有效地替换了已存在的驱动程序)。每个
设备驱动程序的初始化功能检查驱动程序的状态,初始化硬件,设置由该驱动程序提供的
所有中断,然后释放所有在调入驱动程序时使用的多余内存。
      当配置完成后,SYSINIT调用MSDOS.SYS的初始化代码。这个初始化代码设置内
部表和中断向量,然后初始化并驻留在IO.SYS中的原始驱动程序中。
      MSDOS.SYS还确定有多少个磁盘驱动器被连接到系统中,并对应于所有这些磁盘
驱动器所能使用的最大磁盘扇区的大小来确定BIOS参数块。MSDOS.SYS使用这个值
来设置由系统使用的磁盘扇区的缓冲区。最后,MSDOS.SYS显示DOS的版权信息,并将
控制还给SYSINIT。
                 
38页
    在初始化处理完成后,SYSINIT关闭所有的文件句柄,打开控制台设备(cON)作为
标准输入、标准输出和标准错误显示设备、打印机设备(PRN)作为标准列表设备、辅助设
备(AuX)作为标准的辅助设备。最后,SYSINIT调用DOS的EXEC功能调入并运行
COMMAND.COM或由CONFIG.SYS指定的外壳程序(EXEC功能将在本章后面“命令
处理”一节中介绍)。
      当控制传给外壳后,SYSINIT就没有长久存在的必要了。因此当COMMAND.COM
初始化后就被覆盖了。
      当COMMAND.COM一旦被调入,它就立即将它自己的一部分重新定位于内存高
端。COMMAND.COM的低端内存部分(代码的驻留部分)包含为重新启动COMMAND.
 COM所需的基本代码,以使它在重新得到控制后能较好的处理三个中断:Int 22h(终止
地址)、Int 23(Ctrl-c)和Int 24h(关键错误)。COMMAND.COM高端内存部分(暂驻部
分)保持对内部命令和批文件处理所必须的代码。通过将它自己分成两部分,cOM- 
MAND.COM努力将所有时候都必须放在内存的功能所占用的内存数量尽可能地降到
最小。
      当COMMAND.COM第一次被SYSINIT调入时,它为中断22h到24h设置向量。
COMMAND.COM然后运行AUTOEXEC.BAT(如果存在)。当该步骤完成后,控制被转
COMMAND.COM的暂驻部分,并显示DOS提示符,系统进入待命状态。
      现在所有的对计算机运行所必须的软件都被调入和初始化了,下面让我们看看
COMMAND.COM怎样处理命令。
                      3.2命令处理
      COMMAND.COM是一个用来控制存取系统资源和为用户提供一个工作环境的外
壳程序。这个工作环境由为用户可以在系统中查找和运行相应功能而定义的方式组成。当
要求COMMAND.COM运行一个命令时,程序按照以下规则查找命令:
      1.在内部命令列表中查找对应的操作(像DIR、COPY和DEL)。
      2.作为外部命令,在当前目录的可运行的文件(以.COM和.EXE或.BAT为扩展名
        的文件)中查找。
      3.在PATH环境允许的目录列表中查找。
      当COMMAND.COM查找一个目录时,它首先查看以.COM为扩展名的命令文件,
然后是.EXE扩展名,最后为.BAT扩展名,因此,运行程序的类型顺序是设定的。如果两
个程序在同一目录,有相同的基本名字而扩展名不同(例如,FORMAT.COM和FOR- 
 MAT.EXE),DOS总是运行COM文件、扩展名则被忽略掉了。即使输入 FORMAT
EXE,DOS仍运行COM文件。(一个较为普遍的错误,在其它一些章节中也重复出现,即
希望输入扩展名来“强制“DOs运行所选择的文件。这是做不到的!然而,通过将COM文
件改成其它扩展名再将EXE文件改成COM文件这种方法可以强制EXE文件首先被运
行;调用者当它确定文件类型后会忽略扩展名而替代依靠包含在文件中的信息。
39页
    当一个程序标志成cOM或EXE文件时,COMMAND.COM调用DOS EXEC功能
来运行该程序。 EXEC功能处理以下工作:
    1.检查是否有足够的内存调入该程序。如果有足够的内存,请求分配内存。如果内存
      不够,EXEC发出一个错误信息,并且不运行该程序。
    2,在分配的内存区域的底部(见图3.2)建立一个程序段前缀(PSP)(PsP更详细的
      讨论将在等10章“程序和内存管理”中介绍。)
    3.将程序调到PSP之上的内存空间里,由程序的前两个字节确定是COM或EXE
      程序。COM程序只是作为内存映像的拷贝;而EXE程序则按照头上的调入信息
        调入程序。
    4.将控制传给程序的入口点。 COM文件永远只有一个口点100h(只是在PSP之
      后),EXE文件的入口点则在EXE程序头中指定。
    5.当程序结束后,返回控制到COMMAND.COM的驻留部分。COMMAND.COM
      驻留部分对暂驻部分区域进行检查。如果检查OK,计算机控制将传给暂驻部分,
      并且重新显示系统提示符,其它情况下,驻留部分将从磁盘中调入暂驻部分并交
      出控制。不管任何原因,当需要调入暂驻部分而又不能进行时,系统将显示错误信
      息来指示COMMAND是“bad or missing”(损坏或丢失),并且中止操作。在这种
      情况下需要重新启动系统。
                                  O                     80h
                                                  DTA
                                                 命令尾
                                  图3.2程序段前缀(PsP)
    打算代替COMMAND.COM的外壳程序必须提供中断22h到24h的处理;需要的
话,该外壳程序可以提供其它特性。在很多系统中,COMMAND.COM或被对用户更友好
的简单外壳所替换,或被比COMMAND.COM有更多特性的外壳(例如4DOS,一个共享
件的命令处理器即是这种类型)所代替。
                  3.3 DOS下的程序
    在DOS下的应用程序采用的是两个基本而重要的不同形式:COM文件(COM程序)
EXE文件(EXE程序)。要在系统中开展工作就必须理解它们的不同。
3.3.1 COM程序
    COM程序是最小最简单的程序。在磁盘上,它以内存映像的方式存储;在内存中,它
们被调入到单个64k内存段中。尽管COM程序假设完整的存在于一个段中,但它可以自
己偏离出去。例如,存取显示内存的COM程序就必须直接存取离开它指定的64K内存区
域外的内存。
    PC及其兼容机的早期程序经常要完成更多的工作。常常,因为所占用的内存被指定
40页
给它使用,程序需要设置表,调用覆盖程序或以任何方式控制该功能所需的额外的内存。
一些相对好的程序采用这种方式(在版本1中,一些程序甚至通过直接操作磁盘做它自己
的临时文件管理而越过整个DOS文件结构)。
      如果DOS假定COM程序只用已分配的内存,由DOS运行的其它程序就可以很容易
地搞乱已经存在那里的程序。为了避免这个问题,DOS在COM程序开始运行后,将所有
的自由内存都交给它。在本章后面(同时在第10章中),我们将更详细地学习这种内存分
配方法。
      早期的COM程序可以超过假定的数据区域的界限。它直接修改处理器的段寄存器
指向程序需要存取的内存区域。例如,通过修改数据段(DS)寄存器,COM程序可以管理
内存中任何区域的数据。这个能力对要打算直接修改屏幕内存而不用DOS服务程序实现
的程序来说是绝对必要的,因为显示内存是不能放在程序的数据段中的。
      尽管这种存取所有系统内存是一个很强的功能,但这使得在DOS下所有的程序都可
以修改任何事情成为可能——因此,没有什么是“安全”的。为了解决这个问题,DOS对超
出给程序分配的所有内存之外的部分采取了安全措施(技术上说这句话只是部分正确,详
细的解释见本章结束时的“内存分配和管理”一节)。
      如果所写的COM程序使用了DOS的EXEC功能(程序和覆盖调入)来运行其它程
序,COM提供的内存管理功能可以用来为程序使用的内存进行分配,删除分配以及改变
内存块大小等操作。这些功能将在这章最后和在本书的最后的“DOS参考手册”中详细介
    绍(见DOS功能48h到4Ah)。
      因为高级语言自动处理内存管理,因此,不需考虑有关的内存管理,除非在汇编语言
中编程或编写像TSR程序这样的关键内存的应用(将在第11章中讨论)。
      列表3.1是一个以汇编语言编写的COM简单程序。它使用了标准DOS功能(09h)
将一字符串输出到屏幕上。
      列表3.1
          ;Sample COM Program
                          name        dosbook 
          ;DEMOCM.ASM. .COM file version of simple print
          ; Prints "DOS Programmer's Reference”
                ;......Proceduce BoOk......
               ;puRPOSE:    To illustrate programming for a .COM
          ;             file, Displays a string on the screen
          ;USES:       Dx,AX
          ; RETURNS:   Nothing
                      .model tiny
                      .COde
                      .startup
            bOOk      proc
                      mov        dx,offset msg           ;Get message location
                      mov        ah,9                     ; 0utput characten string
                      int       21h
                      mov        al,0                    ; Exit cOde
                        .exit
              book     endp
         
41页
        ;······End of procedure BOOk ····
        msg        db       'DOS Programmer',027h,'s Reference',0dh,0ah,'$'
                      end
    下面我们一行行地过一遍程序,看它做了些什么。
    在初始注释段之后,MASM简化的段指令(.model,.code和.startup)说明汇编程序
将把这个程序作为COM文件设置。尽管.startup指令使程序定位于100h开始,但这主要
是由.model tiny指令实现的。对于COM文件,由100h开始是强制性的,它为了在内存段
中留出程序的PSP的空间。
    随着程序的建立,下一步是创建该程序的唯一过程(book)。因为创建的是COM文
件,MASM隐含地假定这个过程为NEAR(近)过程(所有处理都在一个单一段内)。程序
的主体就是它自己本身,由以下步骤组成:
    1.将信息的偏移量地址放进DX寄存器。
    2,将AH寄存器改为9(写一个字符串到屏幕上的DOS功能代码)。
    3.运行Int 21h来请求DOS中断处理程序。
    4.将AL寄存器改为0(程序的返回码),做好程序结束的准备。
    5.用简化的MASM,exit指令通过DOS功能4Ch(DOS程序终止调用的功能号)来
      退出程序。
    在这个过程的最后,要打印的数据串用特定的ASCII字符27h(撇号),0Dh(回车)和
0Ah(换行)定义在内存中。 DOS功能要求这个字串要以美元符号(“$”)结尾。
    当汇编这个程序时,要用UL命令。这个步骤将从汇编语言的源代码中产生目标文件
并且链接生成COM文件。目标文件没有什么,但它是源文件到CPU可接受的机器代码
之间的直接翻译。COM文件则由链接程序生成。
    正如我们在图3.3中看到的,它显示了一个.COM程序存于磁盘中的对应内容,只有
程序代码存在文件中。这个程序使用了很少的磁盘空间(文件体积只有42字节)。
                                    图3.3 COM程序内容
3.3.2EXE程序
    EXE程序比COM程序更复杂,适应性更强。与限制在单一内存段的COM程序不
同,EXE程序可以自己占用几个段,并且可以包含超过一个段的代码和数据。EXE程序文
件有一个特殊的起始区域,以便让DOS EXE功能来调用它。这个起始块包括DOS用来
重新定位文件和确定内存需求所要求的信息。程序起始是程序操作最重要的内容,因为和
COM程序作为绝对内存映像的存储方式不同,EXE程序是作为可从定位内存映像存储
的。系统可能调整这个映像来覆盖空间并且可由DOS确定所需使用的内存量。
 
42页
      EXE程序比COM有更强的适应性并且有时比它更大(扩展了内存的限制)。它在内
存中也能很好的共存,因为它可以拆散放在离散的段中并指定到可用的空闲空间中。共存
性在单用户、单任务环境的PC系统中用处不大。然而,当你转入到多任务系统(如win- 
dows或DESQview)或OS/2时,共存性变得问题越来越大。程序忽略共处性的话有可能
就不能运行。
      在列表3.1中的COM样本程序,可以改写成EXE程序。如列表3.2所示。
    列表3.2
       ;Sample EXE Program
                        name     dosbook
            ;DEMOEX.ASM. EXE file version Of simple print
            ;PrintS "DOS Programmer'S Reference"
            ;······PrOcedure BOOk·····
          ;PURPOSE:     To illustrate prOgramning for an .EXE
          ;               file, DiSplayS a String on the Screen
          ;USES:         DX , AX
          ;RETURNS:      Nothing
                        .model    small
                        .stack
                        .data
            msg         db          'DOS Programmer', 027h,'s Reference',0dh,0ah,'$
                        .Code
                        .startup
            bOOk        proc
                        mov          dx,offset msg            ; Get message location
                        mov          ah,g                      ;OUtput character string
                        int       21h
                        mOv         a1,0                     ;Exit cOde
                        .exit
            book         endp
                        end
    你马上就可以看出,在列表3.2程序的EXE版本在结构上与列表3.1的COM程序
不同,这个不同造成程序更加复杂。在两个版本中的程序代码完成的工作是一样的,但是,
我们可以发现,在EXE版本中很多部分是相同的,但也可以看到重要的不同。最大的不同
在于使用了MASM指令中定义数据的方法。
    在EXE程序中,必须将描述的数据段作为程序的单独部分。这由.data指令完成(在
这个程序中,数据段包含了一个字符串——和COM版本中使用的字符串相同。)还必须
描述堆栈段;这个由.stack指令完成。这个指令表示MASM为程序生成一个隐含为1K
的堆栈。
    图3.4显示了这个EXE程序的磁盘映象全部的卸出内容。因为它包括EXE程序起
始区和重定位表,这个程序比COM版本大很多。这个EXE版本需要占用更多的磁盘空
间(577字节),所以调入时要比COM程序慢。
43页
                          图3.4 ExE程序内容
                          表3.1一个EXE程序头的肝码结果
    偏移量               典型值                      含    义
      00h                4Dh                  连接程序的EXE文件标记
                         5Ah
      02h                E0h                  映象长度
                         00h
      04h                02h                  以512字节计的文件页长度(2)
                         00h
      06h                01h                  重定位表项的数目(1)
                         00h
      08h                20h                  以节计的段长度(32)
                         00h
      0Ah                00h                  所需的最小段数(MINALLOC)
                         00h
           
44页
                                                                                        (续)
        偏移量              典型值                        含    义
        0Ch                  FFh                    所需的最大段数(65535)(MAXALLOC)
                             FFh
        0Eh                  04h.                  堆栈段以段计的大小(偏移量)
                             00h
        10h                  A0h                   SP寄存器中的偏移值
                             00h
        12h                  9Ah                  字检查和
                             CFh
        14h                  00h                   IP寄存器偏移值
                             00h
        16h                  00h                  代码段的大小(偏移量)
                             00h
        18h                  1Eh                  第一个重定位项的偏移量
                             00h
·      1Ah                  00h                  覆盖数(驻留码=0)
                             00h
    表3.1是EXE起始区内容解码(所有的表的入口都是2字节的字)。注意:打算运行
Windows下的程序也是EXE文件,但使用的是完全不同的起始区调度表。这样的程序
已经超出了本书的范围。
    EXE程序的起始区为EXEC模块提供了控制调入程序到正确的指定段所需的信息。
每个入口由一个2字节数据字组成。它以低位在前的方式存放。表3.1列出了每个入口
的含义。
    (1)在偏移量00h,一对特定的字节(4Dh 5Ah一ASCII码为MZ,代表DO5的主要
设计者Mark Zbikowski)作为EXE文件的标志。当DOS EXEC功能看见以这两个字母开
头的文件后,功能自动将它做为EXE文件处理。这的确和文件的扩展名没有关系。只有
MZ标志用来决定文件类型,所有COM文件都没有包含这个标志。
    (2)在偏移量02h,存储着文件的长度(模512)。这个值加上04和08h偏移量中的
值,用来确定程序的大小。
    (3)在偏移量04h,存储着512字节页中文件的长度。如果文件小于512字节,这个值
可以为零;Microsoft的实用程序决不生成少干512字节的EXE文件,这个结果主要由其
它一些卖主产生的。
    (4)偏移量06h存有重定位表中项目的数量(重定位表定位在偏移量18h)。
    (5)偏移量08h存放的是在段中(每段16字节)EXE程序起始段的大小。
    (6)偏移量0Ah存储着要运行这个程序所必须的最小段数(MINALLOC)。如果不能
满足这么多内存,该程序就不运行。除非被LINK命令行开关覆盖,链接程序将它设置为
零。
    (7)偏移量0Ch存储的是该程序要求的最大段数。除非被LINK命令行开关覆盖,链
    
45页
接程序将它设置为FFFFh(1M)。如果可用内存少于这个数,该程序就申请它所能获得的
所有内存。
    (8)偏移量0Eh中存储的是在段中,从程序开始的难栈偏移。
    (9)偏移量10h中存储的是当程序启动时的SP寄存器值。
    (10)偏移量12h存放的是由EXEC功能在运行时使用的程序检查和。(在大多数的
DOS版本中,这个检查和没有明显作用。)
    (11)偏移量14h中存储的是当程序启动时的IP寄存器的初始值。
    (12)偏移量16h中存储的是程序代码段的段偏移量。
    (13)偏移量18h中存放的是重定位表第一个入口(在EXE文件中的)偏移量。
    (14)偏移量1Ah中存放覆盖程序数,对于一个程序,这个值为零。
    紧跟在起始区之后有一小段保留空间,是程序的重定位表。在重定位表中的项目是每
次读入一项送入工作区域。每个项目的段值都和程序的起始段值相加,它是由EXE调入
程序从存储在程序起始区偏移量04h,08h和02h中的值计算出来的。由计算出的段值结
果和指向程序字的项目偏移量值,计算出要写入的那个位置的段值。
    重定位表后紧跟着另一小段保留空间。每个保留空间的大小可以不同)。然后就是程
序本身,接着是堆栈段。
                3.4一些高级语言的例子
    目前,我们已经了解了汇编语言程序作为COM和EXE程序的例子,下面让我们看
一下使用一些高级语言完成同样功能的例子。在高级语言中,编写源程序是非常简单的。
这是主要的优点。随着程序复杂程序的增加,源程序代码简单将使程序的维护变得容易。
    一般的,高级语言版本的可运行程序要比同一汇编语言版本的程序长。这是因为高级
语言要适应各种各样的应用。例如,在C中的printf并不只是用在我们简单例子程序中。
    通过对几种语言(不同的高级语言)的编译器完成一节提供的汇编语言程序的功能的
编译程序的基本比较,我们可以看出,高级语言版本的可运行程序的调入和在磁盘上存储
都比汇编语言大得多。
    尽管对这些程序很难定其运行速度的快慢,但我们可以很容易地设置表达式来测试
他们的速度。但这一节内容节只注意程序本身,而不考虑它的速度或大小。
注意为了保持大小的可比性,程序中的注释在讨论文本大小时被忽略了。这种情况不适
      用于用户自己的程序。
3.4.1一个Turbo Pascal程序
    很多人和Turbo Pascal相比喜欢工作在BASIC下。例如,要在Turbo Pascal中只打印
一行,就必须在程序语言层用高层结构描述程序。必须将程序代码标识为一个程序模块并
Begin-End(起始)来设置程序的可运行代码(见列表3.3)。尽管在如此平常的层次使用
这么详细的结构看起来有些可笑,可以看出,随着程序的扩充,程序员的工作也要扩充。然
 
46页
而,高层结构对保护预防出错的设备驱动程序用的也越来越多。
    列表3.3
          {DEMOTP.PAS                             } 
          {Listing 3.3 in DOS Programmer's Reference}
          {********************}
          Program Demo;
          Begin
              writeIn('DOS Programmer's Reference');
            End.
    可以看出,Pascal编译器控制着很多的扩展计算机内存。因为要在编译时确定使用多
少内存,程序运行和内存管理等功能所需内存需事先考虑一个合理的数量。
3.4.2一个编译的C程序
    C程序语言主要考虑在设计系统层程序时使用。因为在C语言中,控制系统资源要比
Pascal或BASIC中容易得多。C一般是在编制使用DOS扩充功能或BIOS功能时的程序
时使用的。Pascal的以后版本也有改变,但这种看法已经形成了。
  C版本的程序见列表3.4。和Pascal版本一样,在大的、复杂的程序中,C的开销问题
是次要的,程序的可理解性则是主要的。
    列表3.4
          /* demo.c
              listing 3.4 of DOS Programmer's Reference*/
          #include <stdio.h>
          main()
          {
                printf("DOS Programmer's Reference\n");
          }
    C编译器生成的程序代码更直接的控制着机器操作。这个能力是特意设计的,即C编
程语言是设计用来写操作系统的。
    在提供紧凑的机器控制的同时,还提供了类似于在Pascal中提供的高级语言功能和
控制结构,这使得在很多应用中都选择了C语言。
在本书的大多数应用程序都是用c写的。包含的BAsic和Pascal例子主要介绍在其
它语言中怎样使用同样的技术(限于篇幅,不能用所有语言讲述所有例子)。
3.4.3比较不同版本的程序
    在表3.2中,展示了从不同程序的版本中编译的模块的比较结果。这个表既不反映各
个编译器的完整测试也不是它的性能的测试,它只简单展示了编译过的模块的大小(以字
节计)。对于同样文件,和用户自己生成的文件比也有可能不同。这和使用的语言版本,使
用的选项,编译的方法等都有关系。
 
47页
3.2编译后的模块大小(以字节计)
语言                    翻文件            目标文件          可执行文件
汇编(COM)                   741               162               42(COM)
汇编(EXE)                   738               215               577(EXE)
Turbo Pascal 6.0               222               N/A              1936(EXE)
Turbo Pascal7.0               222               N/A              2208(EXE)
BorlandC++2.0               148                443              5584(COM)
MicrosoftC/C++7.0          148               329               6695(EXE)
      要注意的是,任何编译程序的比较要依靠问题的不同方面和用户的需要。速度是最经
常作为衡量的标准,在一些程序中是非常重要的,但有些就不是。例如,如果一个程序向打
印机传送字符,其速度本身已大于打印机的速度,且必须等待的话,速度快又有什么用呢?
      这个例子能让我们理解所选择的语言是会影响程序的大小和操作的,并影响到我们
工作的难易。要记住,不管高级语言可能会有什么优点,如果需要挤出更多的可用空间和
处理速度的话,没有比汇编语言手工编程更好的了。
COM和EXE程序比较
      我们是否应该并值得不值得花时间将程序转成COM格式呢?或者是保留EXE格
式?要决定这个问题,应该考虑以下因素:
      ·因为COM程序是直接的内存映象,所以它调入和启动较快。
      · COM程序包括数据和程序代码最大限制在64K(然而,通过管理段寄存器,可以
      增加这个限制值)。
      · COM程序过多的获取所有内存,但在为了执行其他程序,又不得不释放内存(这
个内容仅对共处程序非常重要,但对在单用户、单任务系统中运行的程序并不重
        要)。
      · COM程序在磁盘上占用了较小的空间,因为它不包括EXE程序的起始区和重定
        位表。在一些情况下,就要考虑这个区别,因为重定位表可能是程序的很大部分。
      ·COM程序运行较快,这是因为它不能使用FAR(远)调用,FAR调用比NEAR
        (近)调用稍慢些。在大多数程序中,这个区别是不重要的,除非每秒中有很多这
        样的调用被执行。
      当需要在有限空间中挤进尽可能多的程序时,COM程序是很有用的。例如,如果要在
软盘中填进一个简单的实用程序,COM文件就是答案。然而,对大多数程序而言。建立
COM程序是没有意义的。 EXE程序可以很好的完成任务,并且在未来DOS扩充时还能
保证较好的兼容性。一个好的EXE程序可以限制这些需要跨越段边界的FAR调用。并
且,通过对大多数重复工作的NEAR调用,EXE程序可以抵消COM程序在速度上的优
点。
      如果使用高级语言工作,可能没什么选择。大多数编译程序既可生成COM也可生成
EXE程序,其它产品也是如此。根本上,是否应该将EXE程序转换到COM程序,在很大
 
48页
程度。上由实际情况的需要来确定。
      现在,让我们放下基础工作,看看中断。中断影响着DOS、BIOS以及整个系统的工
作。
                        3.5中    断
      老式的计算机系统,在一段时间内只运行一个程序,它的原理、设计、操作都很简单。
早期的系统工作就以穿孔卡片作为输入、行打印机作为输出。大的数据集存放在五个磁带
机中的一个上(磁盘还不存在),内存是32K磁芯。当用户在该系统编程时,必须知道这个
计算机的所有控制。当程序需要磁带中的一些数据时,程序就简单的等待。当需要从控制
台输入时(甚至没有键盘,只有开关!),它也只有再次等。计算机的工作是绝对不饱满的。
但却需要等待很多时间。
      中断是消除等待的一种方法。当计算机需要像读硬盘这样的硬件服务时,它以以下三
种方式中的一种等待结果:
        ·它一直等到操作结束。
        ·它继续其它任务并定时检查该操作是否结束。
        ·它继续其它任务,当操作结束后由操作系统通报它。
      每种方法都有它的优点和缺点,例如,考虑串行通讯。
      当计算机通过电话线和其它计算机通讯时,字符以随机的间隔到达计算机的串行口。
如果必须从线上读出所有字符并将它存储到缓冲区,可以简单的等待字符的到达,读取它
并写入缓冲区,然后接着等待。这种紧凑的循环处理类型通常称为“忙等待”。
这种方法只有在计算机只处理计算机之间的通讯,而不处理其它事情时是可行的。但
你需要计算机提醒你注意。如果你要计算机对你和串行线路有所提示的话,就必须使用其
它方法。
      例如,可以写一个程序,重复的检查要做什么事情,如果出现有事情要做的话,就完成
它。我们可以按照以下处理步骤写一个简单程序:
      1.如果字符是在键盘上,将它写到串行口上。
      2.如果字符在串行口上,将它写到屏幕上。
      3.转到第1步。
      (在第7章中,将设计一个将这样的用来演示串行通讯的程序。)
      然而,当通讯速率在(2000波待率(每秒1200位)时,每秒将有120字符到达。串行串
8.33毫秒就有一个新的字符到达。在8.33毫秒中能做些什么呢?在计算机术语中,8.33
毫秒是一个相当长的时间,但如果做以下工作就不是很长了:
        ·将字符放到屏幕上。
        ·如果字符在屏幕的结束位置,向上滚动屏幕,清空底行。
        ·如果字符是屏幕控制序列的一部分,则实行它。
        ·确定怎样处理不可打印字符。
 
49页
  在执行这些任务的同时,还要准备处理不时在键盘上输入的字符,这是我们会发现,
即使通过DOS和BIOS功能直接从串行口中读取,仍没有足够时间直接完成所有的事
情。实际上,这种直接途径在每次CRT滚动一行时会丢失3个已到达的字符(在1200波
特率下)。
      唯一的选择是使用中断系统。有些人害怕中断,因为编写中断程序好象是非常艰深的
技术,似乎只有很强的技术好手才能完成此项工作。但事实上并不是那么可怕。在懂得了
一些基本的中断操作知识后,任何人同样也能编写一个中断处理程序。(在第11章里要写
出好几个来)。
      可以将中断想像成门铃。如果在家中没有安装门铃的话,就要定时去查看是否有什么
人在你的门前。这种重复的,检查或者轮询(polling)是没有效率和浪费时间的,在计算机环
境中,浪费的是计算机的时间。只是当门铃响时,才走去开门才是有效率的。中断在计算
机中就像门铃一样工作。
      当一个程序正在运行时,象在串行线路上到达字符这样的事件引起中断。当中断发生
后,系统中止用户的程序,将程序的状态(C5、IP和标志寄存器)存入堆栈,转到那个中断
的处理程序。实际上,中断处理即是另外一个程序(将在第11章发现),尽管中断处理程序
在进行处理工作时会有些限制(第11章将探讨这一技术)。
      无论是否认识这点,系统使用中断可以使工作更有规律,例如,时钟中断每秒发生
18.2次。一些程序调用这个中断以在屏幕上显示时钟。
      另一个很重要的中断就是当使用者击键时所产生的中断。这就是键盘中断,它处理击
键。用户所看到的全部东西,只不过是显示在屏幕上的字符,但中断却控制了所有这一切。
      每台PC上可有256个中断例程。访问这些例程可用中断向量来实现。中断向量表位
于内存的0000:0000h至0000:03FFh(此地址已由CPU芯片内部确立,任何软件都不
能改变它)。表中每一个4字节的项,都对应于一个中断例程;此四字节便是该中断例程地
址的入口点。为了了解中断,首先要明白它们是如何分类的。
      8086处理器系列共有四个基本类型的中断:
      ·内部中断
      ·非屏蔽中断
      ·硬件中断(也称为可屏蔽中断)
      ·软件中断
      让我们简单地介绍一下各个类型。
3.5.1内部中断
      8086微处理系列可产生许多能被CPU直接感知的中断。例如,对被零除中断,当处
理器检测到一个被零除的错误时,就会自动地调用相应的中断请求。
3.5.2非屏蔽中断
      非屏蔽中断(NMI)直接与处理器芯片的特殊NMI针直接关联。当某种类型的严重系
 
50页
统故障发生时,非屏蔽中断会强迫处理器立即进行处理(我们可以把NMI理解为,“立即就
    办”的中断)。
    3.5.3硬件(可屏蔽)中断
      由外部设备所产生的中断称为可屏蔽中断。它们与CPU的一个针相连接,因而可以
告诉处理器可以暂时地忽略它。在一台pc上,中断来自8259可编程中断控制器芯片,并
且可以分别将它们屏蔽掉(第11章会更多讨论到)。在标志寄存器中的中断标志状态,可
确定是否让处理器注意到一个可屏蔽的中断。为了屏蔽掉一个中断,我们可利用一条清除
中断指令(CLI)来清除该标志(有关各个标志以及标志寄存器的讨论,可参见第2章)。
可屏蔽中断是经常发生的和不可预知的。因为它们来自外部的硬件,因为当一个中断
将要出现时,某个程序可以预见到它。可屏蔽中断必须迅速得到处理。以使程序能继续运
行。中断处理程序必须进行优化,执行那些最必需的操作。
3.5.4软件中断
      软件中断是由程序产生的,而不是由硬件产生的。所有的DOS和BIOS功能都是通
过软件中断来访问的。这些中断也不必知道系统是如何通过各种各样的灵活方式来让程
    序访问系统资源的。
      正常情况下,软件中断是与用户的程序操作同步的。这些中断的处理程序经常提供一
些特殊的功能,如鼠标控制和文件处理。尽管软件中断不如硬件中断那么苛刻,但因为经
    常被调用,因而必须是高效率的。
      在第11章里对TSR(终止并驻留程序)的讨论中,读者会发现,某些软件中断会触发
一些特殊的程序,从而有可能完全地控制计算机(Borland公司的SideKictr就是这类程序
的一个最好的例子)。在效率里的各个指标中,更关注的是尽量少占空间,其优先性重于操
作速度,尽管速度依然很重要。为了保持整个系统不致于慢吞吞,其它的服务(如口键盘字符
的处理程序或一个时钟处理程序,它们都能更新屏幕显示)则必须极富高效率。
      中断服务例程(interrupt service routine),或称为处理程序(handler),在设计时必须考
虑到应尽量地减少与其它程序的交互,除非此交互是为了满足特定处理程序的需要。这种
情况的一个例子就是对Int 24h的要求(Int 24h是关键出错处理程序)。仅当DOS在试图
执行输入或输出时,遇到了不可预知的问题时,它才会与运行的程序进行交互。
      为了明白Int 24h如何影响堆栈,需要深入地了解DOS是如何使用堆栈的。当Int
21h功能被调用时,Int 24h处理程序将所有的CPU寄存器压入堆栈。 DOS随后会因为自
己的目的而使用内部堆栈。如果一个关键错误出现了,就会再一次选择用户堆栈,但由Int
21h所压入的寄存器却留在了用户堆栈中。然后调用Int 24h,并且出现了结果堆栈(参
    见图3.5)。
      当所有这些寄存器都可以使用时,我们能很容易地确定在错误出现之前所处的位置。
关键错误处理程序是极其重要的。我们对它所给出的有特点的信息确实都很熟悉:
        Abort, Retry, Ignore?
 
51页
              图3.5在调用Int 24h(关键错误处理程序)入口处的堆栈。
      (注意:DOS 3.3版使此信息变成了“Abort, Retry, Fail?”。)
    此错误信息通常是告诉用户,软盘驱动器未准备好。默认的处理程序以一种简单的直
截了当的方式来处理这种情况。如果按I来忽略此错误,控制就会返回到程序,并继续进
行下一步的处理工作。如果按R(重试),处理程序就会再试一次此功能,看看此问题是否
已被解决了。当按A(退出中止)时,处理程序就会中止正处理的过程而返回到DOS。从
DOS 3.3版开始,有一个Fail(失败)选项,它强制DOS给调用程序返回一个出错原因值,
而替代掉了原来的Ignore选项。
    必须时刻提防DOS的关键错误。如果在一个程序的执行期间出现了一个关键错误,
而该程序创建了它自己的通用中断处理程序(如某个终端程序创建了一个用于串行口的
中断处理程序),此程序也可能终止而不会将它的中断处理程序恢复成它们以前的状态。
结果就是混乱一片。如果已检测到了一个影响此处理程序的中断在发生了此情况后出现
了,系统就有可能锁死。因为没有用于此处理数据的中断处理程序。新加进的Fail选项使
得有可能避免出现这样的情况,但不能担保它们不会发生。
    当读者充分地理解了中断,特别是能够用中断处理程序来应付各种情况时,读者也许
希望写出自己的中断处理程序。
    中断处理程序并不局限于只处理一个外部事件或处理某个硬件功能;它们也完全可
以做一些纯粹的软件工作,如排序和搜索。构造一个系统所需的带有各种标准实用程序的
软件包,有许多优越之处。例如,可以写一个系统所需的标准菜单显示、命令输入控制或搜
索过程。如果此系统所包含的是几个独立的程序,则可以将各种功能放入一个软件包中,
并通过一个系统中断来访问。以后所有的程序就可以访问同一个例程。
    这种途径有许多独特的优点。首先,因为各个公用的例程都与一个中断相连,而不是
 
52页
编译到每个程序中,于是程序变得更小一些。第二,因为这些功能是通过一个公用的中断
来访问的,因此当需要在库中改变各个功能时,不必重新连接各个独立的程序。
      大量的商业程序,包括Novell的NetWare以及Btrieve文件管理系统,使用的正是这
种途径。在这些软件中,有许多都已连接进了标准的DOS功能中断(Int 21h),并增加了其
它的附加代码。有关这类软件中这部分代码的讨论,已超出了本书的范围;但如果读者浏
览某些软件时,发现它们在Int 21h上加进了大量的功能,那么它们便属于这类系统中的
一部分。
      DOS功能中断(Int 21h)是一组标准功能的集合,所有的程序在需要时都可以使用这
些功能。鼠标功能(Int 33h)和扩充内存功能(Int 67h)都是这类功能集合的例子。以这种
方式来提供这些功能,人们就不必在需要改变这些功能时,去改变有关的程序——它们可
以继续使用原来的同一个程序。
      有了装入到内存中的一个标准的运行时刻的软件包,就可以和其它各种程序一道来
共享它的资源。当改变此软件包时,所有的程序都可以利用这种改变后的程序,而不必重
新连接它们。可以将中断处理程序放入一个驻留内存的软件包中,而不必担心一些关键错
误会导致程序的崩溃。因而处理程序总会在正确的地方,而且,因为内存驻留的运行时刻
例程不会添加到一个编译后的模块上,编译后的程序将会更小更易于使用。对库中此类软
件包有一个苛刻的要求,那就是它们不能有任何错误,因为中断库中的错误是很难发现
的。
      大多数程序不使用这一技巧,而采用的是覆盖技术:公用例程位于主覆盖块内,总是
驻留于内存内(哪怕是该程序不使用它)。如果读者有一个鼠标,对此一定会有一些体会。
在已装入鼠标驱动程序后是不能将其UNLOAD(撤去)的,因为内存已被占用。
      所有这些技巧都供使用者选择,根据需要可挑选一种最合适的,但必须是熟练地掌握
过这些技巧。
      最后,让我们来看看另一个重要的主题:内存管理。
                    3.6内存分配与管理
      将操作系统装入后,运行了一些程序。这时,内存分配就变得极其重要。除非有够用
的内存,否则就不能装入新的程序。要想了解内存管理如何影响到用户的程序,就应该先
了解一些如何管理内存的知识。
      DOS把所有可用的内存当作一个内存块的缓冲区,从底向上链接在一起。该链(或称
“内存场”)中,含有所有可用的内存。每一个内存块(或“场项”)由一个16字节(一段)的
内存控制块及由它控制的内存组成。表3.3显示了控制块的组织方式。
                                      表3.3内在控制块
      字节                          含    义
00h                            90h(Z),如果是最后一个块
                              77MM)为其它情况(是Mark Zbikowski的标记?)
53页
字节                              含    义
    01h~02h                     0表示此块未分配
                                否则为拥有此内存进程的进程ID号
    03h~04h                    以段为单位的大小
    05h~07h                    未用(DOS;05h~15h在以前版本中未使用)
    08h~0Fh                    拥有程序的文件名,仅在DOS V4中使用,用于表示通过
                                EXEC运行的程序;在其它情况下未使用。
      内存块被组织成链(或称为链表)的形式。链表中的每个内存控制块代表了位于其上
的一块连续的内存区域。每个控制块都指向链中的下一个控制块(第3~4字节留作存放
此指针)。图3.6显示了这种内存分配链的概念性示意图。链的开始由一个DOS指针,指
向第一个内存控制块,每个块在记录它自己块的大小时,也就给出下一个块的偏移值。如
果有两个空块碰到了一起,DOS就会将它们组织成一个单一的内存块。当它们被一个用
于其它目的块分开时(参见图3.6),就不能再重新分配其它信息和重新指定块的位置。
      一个块可以小到一个段(16字节),也可以大到全部的可用内存。DOS根据需要,以块
的方式给程序指定内存。
      当DOS收到一个要求分配内存的请求时,它查找整个内存块链,以找到一个足够大
的块来满足此请求。如果DOS发现了链中的一个断点,或遇到了其它阻碍,它就返回一个
错误。例如,如果请求者是COMMAND.COM,并试图执行一个程序,DOS显示了Memo- 
ry allocation error(内存分配错误),然后让系统挂起。这时就必须重新引导系统来恢复。
      当一个内存请求能满足时,DOS就会使用下列内存分配策略之一来返回一个块:
      · First fit(最先匹配)DOS   在链中找到了第一个足够大的能满足请求的块后
                          就分配此块。
      · Best fit(最佳匹配))DOS分配最小的但能满足请求的内存块
      · Last fit(最后匹配)DOS将最高的能满足请求的最高内存块分配给程序。
      最先匹配策略,能够最大限度地减少搜索一个内存块的时间,它的分配速度最快,但
却有可能生成内存碎片。当一个块被指定时,它被分成了两个块:一个精确地匹配请求,余
下的形成一个新块(返回到链中留作以后使用)。
      无论使用的是哪一个分配策略,如果在某一个给定时刻有不止一个块在使用,则内存
块就变成了碎片,形成了越来越小的块。某个程序在申请或释放内存时,就有可能生成一
堆围绕着正使用块的自由块。当一个程序已形成了多个小块后,不能够重构一个单一的能
满足请求的大块时,内存分配就会失败。
      假设某个程序分配了一组16K的内存块,然后又释放了一个这样的块给操作系统,
各个块相互间并不相邻。这样,尽管有160K的可用内存,但当请求一个32K的内存块时,
却会失败,因为没有一个块足够大到能满足此请求。
      在某些系统里,内存碎片可能是一个很严重的问题, DOS不执行任何清除工作(通常
称为“垃圾收集”)来移动已分配的内存块,从而恢复空间。如果此类问题已成为系统中的
 
54页
    重要问题之一,那么应找一本有关算法或数据
    结构方面的好书,寻求有关垃圾收集技巧的信
    息。
        DOS提供了访问下列三种基本内存分配
    的功能:
        ·分配一个内存块。该功能请求指定一个
    内存块以满足程序的请求。
        ·释放内存块。该功能在程序已用完内存
          后,将一个以前已分配的内存块返回到
          内存区中。
        ·重新确定内存块的大小。此功能让一个
          以前已分配的内存块变得更大或更小。
          调用此程序,通常是将程序的内存块按
          需要压缩到尽可能小的大小。
        从DOSV3.2开始,还可以使用另一个附
    加的功能来确定内存分配策略。 DOS默认时使
    用最先匹配策略,但此新功能允许选择其它的
    匹配策略。
        最明显使用分配功能的地方就是在程序执
    行期间。当程序启动时,它就会收到用于操作所
    需的内存。读者也许能回忆起我们在本章前面
    所讨论的内容,一个COM程序会要求所有的
    内存,而一个EXE程序则只得到了所要求的内
    存。让我们来看看在内存分配方案里是如何安
    排这些工作的。
    图3.6一个内在块链
      当一个COM程序被执行时,它就会将第一个足够大的所有可用内存分配出来,用于
存放程序、PSP和堆栈(至少两个字节)。尽管所有内存并不是直接分配给COM程序,但
结果却是这样的。通常,在程序执行期间只有一个块位于链内,而块正好是整个可用的内
存。
      另一方面,EXE程序则只分配所请求数量的内存,此数量由程序头的MAXALLOC
字段(分配给该程序的最大内存数),当然要有足够数量的内存。如果在MINALLOC字段
(此程序运行时所需的最小内存数)中的内存数得不到满足,该程序就不能执行。
      当一个程序被连接时,连接程序自动地将MAXALLOC字设置成FFFFh(1M),除非
它被一个命令行开关所请求的一个更小的内存数量值所覆盖。
      所以,一个EXE程序像一个COM程序那样,在进入时就已分配到了所有需要的内
存。但是,我们可以覆盖掉这一分配,并使用命令行开关来请求小于1M的内存(但对
COM程序却做不到这一点)。尽管大多数程序员并不愿惹此麻烦,但他们可以这么做。
 
55页
  如果程序是用C写成的,则程序的启动代码(由编译程序提供的)通常会释放未用的
内存,而不用程序自己去操心。Pascal和BASIC将所有的内存用于它们的数据结构,但从
4.0版开始,Turbo Pascal也提供了一个选项,让用户指定要使用多少内存。
      当用Pascal工作时,有一部分内存已被Pascal留在一边,专门用作一个区域(称为
堆)。此内存区被编译器用于分配动态变量(由一些特定的程序块指定),还有一部分内存
则分配给了标准Pascal例程。如果在使用Pascal或BASIC时,想把可用的内存用于运行
其它的程序,则必须告诉编译器留下一些可用的空间。
      这种内存分配方式一直工作得很成功。因为程序经常占用最高的可用内存(父进程常
常位于低内存地址处,尽管TSR不会这么干),此分配方式的通常结果是:所有可用内存
都放在一个块中—最大的(实际上是唯一的)内存块中。但是,这种方案也有一个好处,
那就是能够将内存分配给多个并发执行的任务。
                      3.7小    结
      本章介绍了有关动态DOS的知识。介绍了系统中的硬件和软件部分,如何在实际操
作中,动态地相互作用,相互配合。这些操作包括引导序列、命令处理以及程序执行。我们
介绍了当PC机的CPU上电后,ROM中的引导代码会自动地执行。启动代码然后装入和
传送对引导记录的控制,引导记录再装入IO.SYS和MSDOS.SYS(或它们的PC DOS类
似体,即IBMBIOS.COM和IBMDOS.COM)。这些程序执行各项能使系统工作和运行的
其它必需任务。
      命令处理是DOS外壳—COMMAND.COM的一个功能。COMMAND.COM在解
释用户输入的命令时,先搜索它内部的命令表,看是否与用户的使用匹配;如果未找到匹
配,COMMAND.COM就会搜索执行文件的名字,来匹配用户的命令,然后该命令才得以
运行。
      本章还讨论了COM和EXE程序之间的不同之处。两者各有利弊:COM程序易于编
程、装入速度更快,但要求把数据放入代码段内,EXE程序更复杂,并在装入时要稍慢一
些,但却给编程者提供了更多的控制,如控制内存分配的方式以及其它参数。
      DOS技术中另一个重要的特性是中断,在这章里我们也讨论了这一内容。我们将计
算机的中断驱动的控制与其它方法进行了比较,它的优越之处是很明显的。中断有四个主
要类型:内部中断、不可屏蔽的中断、硬件中断(或可屏蔽的中断)和软件中断。软件中断是
DOS编程的主要接口之一。
      最后,我们还介绍了DOS的内存管理功能。 DOS把内存管理成一组内存块。每个内
存块都含有一个内存控制块,其中指明了此块是否已分配给了某个程序,并告知了下一个
块的位置。 DOS提供了三个内存分配策略,提供了用于分配,释放及调整内存块大小的功
能,并且(在3.2版及更高版本中)还提供了一个用于确定内存分配策略的功能。
    现在,我们已了解了DOS系统中动态交互操作部分中的一些基础知识。在下一章里,
我们将学习如何在自己的程序里使用DOS的各种功能。
 (未完待续)

你可能感兴趣的:(dos)