DOS程序员参考手册内容解说
DOS程序员参考手册内容解说
12页
第2章DOS系统结构
DOS结构涉及硬件之上的整个机器。它不只是操作系统,并且包括整个计算机。如果
想要对使用什么功能或怎样使用它们作出最好的决定,就必须了解DOS的结构。
2.1“虚机”概念
认识DOS的一个有用的方法是,将它看成是分布在子系统中的一种体系结构。该体
系中每层都提供了一个完整地定义的服务程序集合,以使更高一层可以使用它。因此,每
个层次都成了一个虚拟的机器,它便构成下一个更高层次的计算机。图2.1说明了这个关
于DOS系统的概念。
图2.1虚拟机器的层次结构
物理的机器或硬件,是该体系的最底层。系统之间的很多区别就在于这一层。
围绕着机器,常见的主要组成包括以下几个部分:
·来自Intel 8086家族的处理器:8086、8088、80186、80286、80386和80486
·在系统中相似的物理设备映象(换句话说,相似的指定中断和地址)。
·一个有限数量的总线设计。
13页
DOS的一个主要目的就是隐藏各机器之间的差异,以便在编程和用户层都以标准的
方法来使用计算机提供的各种性能。DOS成功地在各种IBM兼容计算机所选择的操作系
统上实现了一致。
2.2物理机器
机器之间最大的差异就在硬件层,在这个层次一般会发现其“兼容性”是否真的兼容。
当有重大的硬件差异时,在这个层次操作的程序就不能工作了。
物理的计算机系统(见图2.2)可以分为几个主要部分:
·中央处理器(CPU),完成计算机系统的操作。
·ROM和RAM,保留程序和数据。
·输入通道,为计算机输送信息。
·输出通道,将信息送给用户。
·存储设备(软盘和硬盘),临时地或永久地保留数据。
图2.2基本计算机的框图
理解计算机系统的所有这些部分,对成功地开发出高质量的软件是很有必要的,特别
是使用DOS和BIOS尤为重要。下面将讨论这些部分。
2.3处理器
在PC或兼容机上使用的中央处理器(CPU)是Intel公司8086系列处理器芯片的成
员。在使用的计算机中经常看到8086、8088、80186、80286、80386或80486芯片。Intel新
近已经发布的芯片,主要版本有80386sx、80386DX、80486SX、80486DX、80486DX2等。
在Intel系列中的每个芯片,不仅有区别于已有芯片的特有功能,而且也和早期版本保持
了兼容性。例如,80386(SX或DX)就可以完成80286或8088可以完成的所有工作,并加
上了它自己特有的功能。
本书不详细研究芯片之间的特殊差异,但要知道DOS的主要版本是基于8086和
8088芯片的能力而设计的。在讨论DOS、BIOS和编程时,所有例子都是运行在8086或
8088上的(没有包括新芯片扩充的特有性能)。在本书中,所有的引用都是针对8086的,
但都适用于整个intel处理器系列。对DOS的一些新的附加和扩充,需要不同CPU芯片
14页
知识,我们在例子中将清楚地注明所需知识的类型。
CPU是完成最简单操作的基本处理器。因为必须了解一些这个基本处理器的知识,
所以这节提供了一个8086家族处理器的概述。如果读者已熟悉这个题目,可以跳过此节。
如果要详细了解8086家族的编程,请看本书最后的参考书目。
在本节的后面,将讨论作为一个程序员可以存取的CPU寄存器。然而,因为内存寻址
是在DOS层完成很多程序操作的基础,所以首先必须了解8086怎样对内存寻址。让我们
先看以下部分的内容。
2.3.1 8086内存寻址
一些程序员批评8086的分段内存寻址方式。内存分段限制着数据项的大小,并且指
针结构也很复杂。然而,分段式内存对于DOS程序员来说至今仍还存在。它对解决8086
的固有设计问题是一个明智的方法,也是用8086的16位寄存器来表示乡8086的20位地
址值的最好的查找方式。 8086有20根地址线,它允许在一个内存区域内有2的20次方(即
1048576或1M)字节的寻址能力,但它的寄存器只有16位宽(寄存器的使用将在本章后
面“8086寄存器集”一节中讨论)。
解决这个问题的方法就是将整个内存地址分成可以在16位寄存器中被独立存储的
“片”。因此,需要用两个寄存器来表示一个地址:一个寄存器存储基地址(或称作段地址),
另一个存储相对于基地址的偏移量。该方法理论上可以寻址2的32次方(大于40亿字节)的连续
地址。但要实现这个寻址区间,微处理器需要32条地址线,因此它大大地超过了8086的
能力。下面我们用例子说明该寻址区域是非常有用的。
图2.3示出了包含100000000h(4294967296)或2的32次方个单元的4千兆内存空间区域。每
一行是一个区或段(segment),包含10000h(65,536或2的16次方)个单元。在选定的每个段内的
地址时,可以用相对于该段开始单元的偏移量表示,第一个单元的偏移量为0。书写时,采
用段:偏移量(segment : offset)的形式来表示地址,而且各地址值均采用十六进制形式。
第一段的最后一个字节(位于0000: FFFFh)后面,紧跟的是下一段的第一个字节
(位于(0001 : 0000h)。每个内存单元的绝对地址——即它的本来位置,应从内存区的第
一个字节算起。它的计算公式如下:
实际地址=(段地址*段间隔)+偏移量
图2.3显示了这一计算过程。当段间隔等于10000h(64K)时,段号便形成了8位绝对
地址的最高4位值。类似地,偏移地址则可以当作绝对地址的最低4位值。
实际上,基地址和绝对地址之间的关系并不像图2.3所显示的那样简单。采用了先进
技术的硬件设备可以快速地处理地址转换,以取代人们从段和偏移量计算出绝对地址的
工作。
尽管通过这个例子对理解是有帮助的,但它还留下了一个疑问:8086使用的是20位
(而非32位)地址。因此,这个例子必须修改成实际的工作方式。Intel开发的寻址方式是
将段寄存器的内容当成是整个地址的高16位,低4位指定为零。换句话说,段寄存器包含
了20位绝对地址的高4位16进制数,并且最低位(最右边一位)为零。再加上要计算的地
15页
围2.3间隔值为10000h字节的各内存段
(图中所有数字都为十六进制,除了后缀为“d” 的外,它表示该数为十进制。)
址偏移量寄存器中的地址值,就可得到所要的绝对地址了。图2.4显示了Intel寻址方式
中怎样从段—偏移量地址得到绝对地址。
图2.4如何计算一个绝对地址
现在,我们已有了基于8086内存寻址概念,下面让我们再来讨论8086的寄存器集。
8086用段。偏移量来寻址内存会导致一个反常现象;实际使用中的绝对地址可以以
多个组合方式来寻址。例如,下面所有的段。偏移量对表示的地址都对应的是同一个绝对
内存地址。
0101:FFF0 1000:1000
03F1:D0F0 1001:0FF0
0900:8000 1002:0FE0
0CB7:4490 1003:0FD0
0FFF:1010 1100:0000
16页
所有这些分段式的地址都对应于同一个绝对地址:011000h。注意,每增加或减少此
地址的一个段位置,会对应地增加或减少偏移量中的10h位置。正如所看到的那样,内存
段可以用很多方式来覆盖。
2.3.2 8086寄存器集
8086系列使用了14个独立的16位可组合的寄存器,按用途分组,可分为四类:
·通用寄存器
·段寄存器
·偏移量寄存器
·标志寄存器
表2.1列出了各个寄存器和它们的分类。
表2.1 8086寄存器集
寄存器 类别 用途
AX 通用
BX 通用
CX 通用
DX 通用
CS 段 代码段
DS 段 数据段
ES 段 附加段
SS 段 堆栈段
SP 偏移量 堆栈指针
BP 偏移量 基地址指针
SI 偏移量 源索引地址
DI 偏移量 目的索引地址
IP 偏移量 指令指针
Flags 标志 状态标志
当面对各个寄存器时,你就明白了在硬件层是如何直接对CPU进行操作的。要注意
的是,这些操作尽管都由汇编语言来完成,但像BASIC、C和Pascal这样的高级语言也有
自己的方法来调用这些寄存器。(有关这方面的技术将在第4章“DOS和BIOS接口”中讨
论)。
正如上面所提到的,8086寄存器集可以按照使用目的,分成四类,让我们对此分别介
绍。
通用寄存器
通用寄存器,正如其名字所提示的,用于存储最新结果或其它临时需要的通用目的。
当使用DOS或BIOS功能时,就需要用所需的值装入这些寄存器来完成各种功能。总要
在某个寄存器中包含一个值来对应指定的功能,并按所需附加一些其它参数。当从DOS
或BIOS功能返回时,程序中要使用的返回值也包含在寄存器中。
通用寄存器是AX、BX、CX和DX。为了方便地使用8位或16位值,每个16位的寄存
器可以作为一对8位寄存器来寻址。寄存器命名为AL、AH、BL、BH等,分别用来寻址低
8位或高8位(L和H分别指示低和高)。图2.5表示了各个寄存器之间的关系。
这些寄存器的使用非常广泛。无论是使用汇编语言,还是使用高级语言,只要调用访
问DOS或BIOS的例程,都常用到它们。
17页
图2.5 16位的寄存器可以当作一对8位寄存器来寻址
段寄存器
段寄存器在8086的内存寻址方式中起着很重要的作用。它存储了16位代表64K内
存段的基地址的值。读者可能回忆起来:这些值对应于20位基地址的高16位;低4位被
指定为零。 8086的内存寻址硬件将这些基地址和存储在CPU偏移量寄存器中的值进行
组合,得到实际应访问的地址。
下面列出了所使用的各个段寄存器:
·CS(代码段)寄存器
·DS(数据段)寄存器
·ES(扩充段)寄存器
·SS(堆栈段)寄存器
每个段寄存器指示不同的段。作为程序员,可以用所选的任何方法在一定范围内使用
这些段寄存器。在第3章“动态的DOS” 中,读者将看到怎样编程以及怎样使用段寄存器。
段寄存器是为用于以下工作场合而设计的:
· CS保存包含正在运行程序代码段的基地址。
· DS保存包含程序数据段的基地址。
· ES是对DS寄存器的补充,用于保存“扩充”段的基地址,经常用于数据。
·SS保存程序堆栈的基地址,用来临时存放数据。
前面提到的使用段寄存器的限制包括对使用CS和SS寄存器的限制。为了便于操
作,8086期望CS寄存器永远指向正在运行程序的代码段,同样SS寄存器永远指向当前
的堆栈段(对8086操作是必须的)。
堆栈
8086系列的处理器用一个名叫堆栈的结构来跟踪函数调用和其它操作中产生的信
息。每当一个子程序被调用时,处理器将各寄存器推入堆栈(PUSH操作),当从子程序返
回时,又将它们弹出(POP操作)。每次PUSH应将堆栈指针指向前一个低地址;POP则恢
复这个“移动的”指针。这些在SP上的操作是86芯片系列内置的操作,并且不能被改变;
事实上,正如编程所希望的,SP总是被初始化为指向堆栈存储空间的栈顶而不是栈底。
程序员使用堆栈来存放计算的中间结果,或者向子程序传送参数值。各种编程语言为
18页
同样的目的而广泛地使用了堆栈。
堆栈的工作很像自助餐馆的盘子堆,当一个项目加入到(PUSH进)堆上时,堆会变
大。当东西拿走时(POP出),最后一个加入堆的项目被第一个弹出。这个结构被称为是后
进先出(LIFO)结构。
偏移量寄存器
偏移量寄存器,正如其名字所表示的那样,一般用来作为内存地址的偏移量部分。地
址的段部分一般存在段寄存器中。
因为地址被分成段寄存器和偏移量寄存器两部分,所以每个偏移量寄存器被隐含地
和指定的包含地址“其它”部分的段寄存器配对。这种配对是自动的,除非用特定的命令取
消这种默认的配对。
在以下列表中,给出了5个偏移量寄存器和隐含的段寄存器之间的配对关系:
·SP(堆栈指针)寄存器(和SS配对)
·BN(基础指针)寄存器(和SS配对)
·SI(源索引)寄存器(和DS配对)
·DI(目的索引)寄存器(和DS配对)
·IP(指令指针)寄存器(和CS配对)
根据这组寄存器在一般情况下所起的作用,可以将它们分成两类:指针寄存器和索引
寄存器。
指针寄存器
指针寄存器提供了一个在段中读取数值的常规方法。 SP永远指向当前堆栈的顶部,
并且自动地被各种汇编语言指令修改。其它指针寄存器,特别是BP,常在索引操作中用来
作为基本的(或参考)指针。例如,一些程序用BP指向堆栈中的一个固定位置。这个位置
以后可作为恢复子程序调用前放在堆栈中的变量的参考指针。在高级语言编译器中,BP
寄存器的使用便具备了存取参数的标准含意。
指令指针(IP)保留下一条将被CPU执行的指令的地址偏移量。当IP和代码段(CS)
寄存器结合时,它们指向指令的绝对地址(CS : IP寄存器对永远用于这个用途)。IP的值
在每个指令从当前的代码段取出后,由CPU自动地增加。
索引寄存器
索引寄存器SI和DI,是特定的偏移量寄存器。特别是当SI和DI与DS和ES段寄存-
器合用时。例如,在字符串操作时,将使用DS:SI指向源串的地址,ES:DI指向目的串。
在非串操作中,程序员通常用SI和DI作为源操作数和目的数据的索引(偏移量),正象其
名字表示的那样。
标志寄存器
8086标志寄存器使用16位中的9位作为标志,表示处理器的状态或控制处理器的
操作方式。这些标志被分为两类:状态标志和控制标志。列出如下:
19页
状态标志:
·CF(进位标志)
·PF(奇偶标志)
·AF(辅助进位标志)
·ZF(零标志)
.OF(溢出标志)
·SF(符号标志)
这些标志报告最后一个被运行的指令的状态。例如,如果最后一条指令产生的值是
零,则零标志就被设置。状态标志的设置和清除是自动的,但程序也可以设置和清除标志。
很多DOS和BIOS程序就使用进位标志来指示错误。
控制标志是:
·DF(方向标志)
·TF(跟踪标志)
·IF(中断标志)
方向标志控制着8086的指令在内存拷贝的区域内指定方向。跟踪标志将CPU置成
“单步”方式(调试器用来控制程序的执行)。中断标志允许或禁止硬件中断响应。
2.3.3 80286及其更高档的处理器
从80286开始,使打破由8088/8086分段式结构设置的1M内存的限制成为可能。
CPU可以在实地址方式下运行,即运行与8086和8088的能力一样的程序。程序员可以
在实地址方式下编程,也可以在保护模式下编程。
在保护模式下,建立了一个描述符表。这些表包括以前段寄存器信息——段的基地
址。同时加上一些信息,如,是否可以被写入这个段的信息。段寄器则是现在的段选择符,
它是描述符表的索引。
在80286保护模式中,一个描述符表项包含有24位基地址。当80286在保护模式下
运行时,它可以存取多达16M的内存。
80386和80486继续扩充了内存寻址能力。像80286一样,它们可以在保护模式下编
程;然而,现在的描述表项有32位基地址,它允许寻址多达40亿字节(46字节)的内存。
为适应这个寻址能力的跳跃,通用、偏移量和标志寄存器现在也有了32位版本:EAX、
EBX、ECX、EDX、ESP、EBP、ESI、EIP和EFLAGS。老的16位寄存器(例如AX和BX)仍
然存在,只是作为32位寄存器的低16位。
不仅CPU可以存取4G字节的内存,一个段也可以扩充到全部4G内存中。事实上现
在32位结构可以和16位结构一样容易处理,在DOS环境中的80386-/80486- 特定软件
已经形成了市场,其中包括两个相互竞争的保护模式环境。 DOS保护模式接口(DPMI)和
虚拟控制程序接口(VCPI)。
另外,还有一个DOS扩充程序,该程序允许专门针对80386-/80486的软件,在允许
存取实地址模式下DOS和BIOS功能的同时,在保护模式下进行操作。 DOS扩充程序允
20页
许应用程序在使用CPU的32位扩充能力的同时,仍能使用DOS和BIOS提供的服务。这
类程序常常比用等同的16位并且对DOS内存没有强制限制开发出的程序运行得要快。
2.3.4 CPU芯片的识别
要使用80286、80386和80486 CPU的扩充功能,软件必须知道它运行在其中一个芯
片上,并且要知道在什么芯片上运行。有三种解决确定当前芯片问题的方法。第一种解决
方法是基于80386和80486在加电时用DH寄存器的10(标志)字节(3或4)来区分,它是
什么芯片。第二种方法是询问用户,他使用的是哪种芯片。第三种方法是从已知的芯片之
间的差异来推断出所使用的芯片是哪一种。
第一种方法必须包含有已重编程的BIOS芯片,它超出了大多数程序员的能力,对用
户而言则更是苛刻。它也不能区分80286到8086之间的芯片。第二种方法假设用户知道
其机器是什么CPU;在很多情况下,这种假设是无效的。第三种方法需要做的工作比第二
种多,但比第一种少,并且是可靠的。
列表2.1中的编码演示了怎样确定当前的CPU。它首先测试8088、8086和80286及
其之上芯片的不同之处。在8088中,一个值被推进堆栈后,堆栈指针在写入堆栈之前减
少。从80286开始,则是先写值,然后堆栈指针再减少。通过压入堆栈指针,可以检查写入
堆栈的值,来确定堆栈指针是在值写入之前还是在之后减少。如果要确定当前使用的是
80286、80386或80486,可以尝试设置80286没有使用的标志寄存器位、80286不允许使
用这些位,但80386和80486却允许。如果可以改变这些位的值,则是80386或80486。同
样的方法也可用来区别80386和80486。注意,使用66h大小的覆盖前缀去强制32位标
志寄存器被压入和弹出。这个方法是完全安全的,因为在这时,已经知道它至少是个
80386芯片。
列表2.1
page 6o,132
;checkcpu.asm
; Determines whether the CPU in use is an 8088/8086, an 80286, an
;80386, or an 80486.Print the CPU and return an errorlevel Of 0,
;2, 3,or4 for 8088/8086, 80286,80386,or 80486, respectively.
.model small
.Stack
. data
say86 db "8088 or 8086$"
say286 db " 80286$"
say386 db "80386$"
say486 db "80486$"
.code
.startup
checkcpu proc
; The first step is to determine whether the chip is an 8088/8086.
;The key difference is based-on what the CPU does when it executes
;the PUSH instruction.The 8088/8086 decrements the stack
21页
; pointer first and then writes the saved value to the stack. The
; 80286, 80386, and 80486 write the value to the stack and then
; decrement the stack pointer. Thus, when SP is pushed and the
; pushed value is popped off, the value popped off equals the
; current stack pointer, unless the chip is an 8088 or 8086.
push sp
pop ax
cmp ax,sp ; if values are not the same,
jne is_86 , it is 8088/8086
; The second step is to detecmine whether the chip is an 80286.
; The key diffecence is the IOPL bits in the flags cegister;
; the 80386 and 80486 have them, and the 80286 does not. The
; 80286 does not let them be Set; the 80386 and 80486 do.
pushf
pop ax ; get flags in ax .
or ax,03000h ; set IOPL bits
push ax ; stuff them back
popf , pop flags--this is where the 80286
; will put them back the way they were
pushf
pop ax ; get flags in ax
test ax,03000h ; if the IOPL bits are reset, the chip
jz iS_286 ; is an 80286
; The third Step is to determine Whether the chip iS an 80386.
; The key difference iS the alignment check bit in the flags
; register; the 80486 has one, and the 80386 does not. The 80386
; does not let you set that bit, but the 80486 does.
db 66h ; (32 bit instruction)
pushf
pop ax ; read low word of flags
and ax,00FFFh ; clear IOPL bits- -level zero
pop dx , read high word
or dx,00004h ; set alignment check bit
push dx ; push flags back
push ax
db 66h ; (32-bit instruction)
popf ; pop flags register- -this is where
; the 80386 undoes your work
db 66h ; (32-bit instruction)
pushf ; push flags back
pop ax ; read what you did
pop dx ; find out if the CPU reset the
test dx,4 ; alignment check bit
jz iS_386 ; if it did, the chip iS an 80386
is_486:
mov dx,offset say486
mov al,4 ; errorlevel 4
jmp sayso
is_386:
mov dx, offset say386
mov al,3 ; errorlevel 3
jmp sayso
iS_286:
mov dx,offset say286
mov al,2 , errorlevel 2
22页
jmp saySo
is_86:
mov dx ,offset say86
mov al,0 ; errorlevel 0
sayso:
push ax ;save errorlevel
mov ah,9 ;call DOS wPite string function
int 21h
pop ax ;retrieve errorlevel
mov ah , 04Ch ; terminate process with return code
int 21h
checkcpu endp
end
2.3.5数学协处理器
Intel 80x86系列的处理器,从8088到80386只能处理整数运算。对很多应用程序,有
整数运算就已足够。对于需要浮点运算的应用计算必须由已编好的特定的程序来处理。对
大多数应用程序来说,用户不需留意软件计算处理的开销。然而,对于浮点运算较多的数
学应用,开销变成了一个问题,这时的数学协处理器也变成必不可少的了;一些应用系统
甚至没有协处理器就不能运行。
数学协处理器可以像处理器计算整数那样容易地计算浮点数。不仅如此,它还能和处
理器并行地处理所进行的计算工作。只有当数据被调入协处理器或从协处理器中读出数
据时,或者激活协处理器期间,才会需要处理器的配合,而在协处理器完成其功能期间,处
理器可以去做另外的属于它自己的工作。
2.3.6数学协处理器的识别
Intel公司共有三种可以和主处理器一起工作的协处理器:8087、80287和80387,但
没有80187。因为8087也可以和80186和80188一起工作。也没有80487,因为在80486
内有内置的80387的等价物。要识别数学协处理器并不简单。表面上不匹配的处理器和
协处理器可以结合。事实上,8086 CPU和80287就可以组合在一起工作。
与识别不同CPU的方法相比,识别协处理器的技术要利用不同代的协处理器之间的
细微差别。分辨系统中使用的是何种芯片,会由于在系统中根本未使用协处理器而复杂化
(数学协处理器决没有那么便宜——需要使用协处理器的应用也不普遍——使得卖方会
自动地将它们放入系统。另一方面,很少有卖方想把它们的产品放在失去和应用程序确定
的协处理器百分之一百兼容的地位上。折衷的方法是,在主板上放了一个协处理器的插
座,由用户选择是否要安装一个协处理器。)
要确定当前协处理芯片是哪一种,可将一个位模式写进内存,试图初始化协处理器芯
片。然后数学协处理芯片运行一个将协处理器状态字写入内存的指令。如果拥有该芯片,
则有一个新值写入内存,如果没有该芯片,则写入的是位模式,而不是有效的协处理器状
态字。
当知道有协处理芯片存在后,可以用通过区别协处理器中断和读控制的方式来区分
23页
8087和80287以及80387。这个过程对8087有效,但对80287或80387无效。要区别
80287和80387,可以创建出一个正的无穷大的值(正1除以0),再创建一个负的无穷大
的值,然后用协处理器来比较这两个值,因为80387在两个值之间有区别,而80287则没
有区别。
在列表2.2中的过程可以被调用来确定当前使用的是哪个协处理器,如果它确实存
在的话。
列表2.2
page 60, 132
;CheCktpu. asm
;Determine the type Of math coprocessor(fpu)installed.
.model small
.stack
.data
ScratCh dW (?) ;have the math chip store data here
Saynone db " NO math coprocessor$"
say87 db " 8087$"
say287 db " 80287$"
Say387 db " 80387$"
.Code
.startup
·checkfpu proc
fninit ; initialize the fpu
mov scratch , 055AAh;the fpu will not write this
fnstsw Scratch ; have the fpu write its status word
cmp byte ptr scratch,0;check lSb--it shbuld be 0
jne no math ; if not, retyrn
fnstcw scratch ; now have the fpu write its control word
mov ax , Sccatch ; read the value
and ax ,0103Fh ;mask expected bits
cmp ax ,0003Fh ; this is what you should see
jne no_math ; if different , no math chip
;Now that you know that you have an fpu , which one is it?
and Scratch 0FF7Fh ; clear interrupt bit
fldcw scratch ; load the control word
fdisi ; disable interrupts
fstcw Scratch ; write the control word back
test Sccatch , 00080h ; any effect on the word?
jnz found_8087 ; if so , it is an 8087
;The chip is not an 8087 , so it is an 80287 or 80387.
finit ;reinitialize the chip
fld1 ; push +1.0 onto the chip's stack
fldz ; push 0.0 onto the chip's stack
fdiv ; produce positive infinity
fld st ; produce negative infinity
fcompp ;compare
fstsw scratch ;write the status word
mov ax , scratch
24页
sahf ; copy AH into the flags register
je found_ 80287 ; if Z bit set (equal) , the
; coprocessor found positive and
; negative infinity to be equal
;The chip is an 80387
mov dx , offset say387
mnv al,3 ; errorlevel 3
jmp sayso
no_math:
mov dX,offset saynone
mov al,0 ; errorlevel 0
jmp sayso
found_8087:
mov dx,offset say87
mov al,1 ; errorlevel 1
jmp sayso
found_80287:
mov dx,offset say287
mov al,2 ; errorlevel 2
sayso :
push ax ; save errorlevel
mov ah,9 ; call DOS write string function
int 21h
pop ax ; retrieve errorlevel
mov ah , 04Ch ; terminate Process with return code
int 21h
checkfpu endp
end
2.4内 存
PC及其兼容机中有四种类型的内存:
·ROM(只读内存)是安装在计算机中的永久内存,它通常保留特定的机器的
BIOS部分。
· RAM(随机存取内存)非永久地保留程序代码和数据。
·扩展内存(extended memory)(超过1M的内存)可以被80286处理器在保护模
式下存龋
·扩充内存(expanded memory)加入到系统但不是直接被处理器映射的内存部
分。这部分内存通过特定的扩充内存驱动系统来存龋
读者可能已经听说过多种ROM,如PROM(可编程只读存储器)或EPROM(可擦写
可编程只读存储器),所有这些都属于ROM类型。尽管有人反对这样归类,但从DOS系
统程序的标准来看,PROM和其它一系列ROM都表示永久存储器。
图2.6显示了在基本系统内存中内存的映射图。
在第10章“程序和内存管理”中,将要详细讨论内存的分配和使用方法,并学习在程
序中怎样控制内存以及怎样使用内存。
25页
图2.6带有1兆内存机器的内存图
2.5I/O通道
PC及其兼容机上的标准输入/输出(I/O)设备是键盘、视频监视器和打印机(见第5
章“输出设备”和第6章“输入设备”)。除了这些标准设备以外,计算机还常配有鼠标以及
一个或多个串行接口(见第6章和第7章“串行设备”)。
可以增加诸如触觉感应屏幕(触摸屏)以及各种类型的传感器等一类的用户设备到
PC系统中。尽管讨论这些特殊设备已经超出了本书的范围,但在第12章“设备驱动程序”
中,将针对特定的设备,介绍怎样编写自己的驱动程序。
2.5.1键盘
PC键盘从不知道从键盘上键入的内容。键盘不解释所击的键,只是直接告诉计算机
特定的键被按下或松开。键盘并不指定每个键的含意。但它指定每个键有一个唯一的数
值(扫描码)。这个扫描码被BIOS传送到计算机去解释。图2.7介绍了原始84键版本的
26页
键盘的扫描码。后来的键盘增加了更多的键,它将在第6章里介绍。
图2.7键盘扫描码
当使用者按下某一个键时,键盘通知计算机(通过Int 09h)那个键已被按下或松开。
当处理器执行Int 09h时,BIOS取得计算机瞬时的控制权,读取该键的扫描码。BIOS首先
检查像Shift和NumLock这样的双态键。如果双态键被按下或松开,BIOS修改在内存地
址0417h-0418h上的键盘状态位。接着,BIOS检查一些特定的组合键(如Ctrl-Alt-Del),
如果需要,就运行它们对应的特定的处理程序。
如果扫描码还没有当作特殊用途的键(像NumLock,Ctrl-Alt-Del,Shift或者Ctrl等)
被“清除”的话,BIOS将它转换成等值的ASCII码。如果对该键没有正确的ASCII字符相
对应,它就给出值为零的ASCII码。然后,该ASCII字符,加上它的初始扫描码,就被保存
到键盘缓冲区中。这个缓冲区足以存放15个字符和它们的扫描码。如果缓冲区已满的话,
BIOS就会发出蜂鸣声(以表示键盘缓冲区满)并且去掉此键的扫描码的值。
在字符到达键盘缓冲区后,就允许运行着的程序(包括DOS)使用它了。因为计算机
通常在几分之一秒中就响应,所以填满键盘缓冲区的机会是很小的,除非计算机忙于处理
其它任务。
这里只能对键盘进行粗略的介绍,更详细的讨论键盘的编程请阅读第6章“输入设
备”。
2.5.2显示器屏幕
PC支持多个视频接口卡的型号,并且每个显示卡都可工作在多个文本或图形模式
下,然而,编写一个适应各种显示的程序并没有想象的那么困难,因为DOS提供了确定显
示卡的种类以及当前工作模式的工具。
第6章将详细讨论这些内容,这一节只介绍一些典型的显示卡类型。
显示卡的类型
对于大多数编程人员来说,至少应熟悉6种类型的显示卡。当然也有其它类型的显示
卡,但通常只用在一些特定的应用中。
初始的“标准”显示器是单色显示卡(MDA)。这个系统以其明快的、清晰的字符以及
专业化的外观在计算机的商业应用中得到了很高的评价。其它视频卡(CGA、EGA、HGA、
27页
MCGA和VGA)也开始在不同的显示场合能被用户使用。表2.2列出了这些显示卡以及
它们开始使用的年代。
表2.2显示卡和使用的年代
显示卡 推出的年份
MDA 1981
CGA 1982
HGA 1982
EGA 1984
MCGA 1987
VGA 1987
在对单色显示卡的扩展中,彩色显示卡(CGA)使显示彩色成为可能,这一点是非常
重要的。CGA显示卡可以显示彩色和图形,但是显示字符不如MDA的清晰。这个不同的
原因在于产生每个字符的点阵象素数。 MDA使用9*14的字符框产生字符,而CGA使
用8*8的框。因为密度的不同,CGA的字符看起来要比MDA的字符“蠢”。
大力神(Hercules)图形显示卡(HGA),结合单色屏幕的清晰的字符以及彩色图形卡
显示的图形产生了高分辨率的单色显示效果,因此它很快成为结合文本和图形的标准。
HGA不能产生彩色,但这个不足对它并不是特别不利。
随着增强图形卡(EGA)彩色图形系统的推出,人们(和商业市场)开始发现,彩色使
他们的工作更加丰富多彩,更加有高度了。仅高亮度不足以在屏幕上显示各种变化,但可
以用彩色来强调很多事情。
虽然只是少量的改进,使用也不广,但随着IBM PS/2型号25和30的多彩色图形阵
列(MCGA)和IBM PS/2型号50、60、80的视频图型阵列(VGA)的推出,显示的标准再一
次被重新确定。 MCGA类似于CGA,但它的分辨率更高。 MCGA分辨率是320*400;
CGA的分辨率是320*200。 VGA的分辨率(640*480)是EGA(640*350)适当扩充。两
种显示器中主要的改进则是所有的显示都使用的是模拟的而非数字的监视器。在模拟信
号下工作,新的视频系统可以显示256色的调色板(允许产生多达262144种颜色)。
内存映射与显示卡
在IBM系列中的视频显示卡都使用内存映射。换句话说,即将屏幕上所见的内容直
接映象到被显示卡控制的内存区域中。字符的实现很简单,它被直接写入显示内存,然后
显示卡从显示内存中读出该字符并将它显示到屏幕上。在图形模式下,显示卡将视频内存
的数据当作一个在屏幕上控制点的分离着的位的阵列。内存区域的使用按照显示模式以
及不同的显示卡有着很大的区别。表2.3详细列出了每个显示卡的视频缓存的起始位置
和长度。
表2.3各显示卡的内存配置
显示器类型 显示模式 缓冲区段地址 缓冲区长度 显示页数
MDA 文本 B000h 4K 1
CGA 文本 B800h 16K 4/8
图形 B800h 16K 1
HGA 图形 B000h 64K 1
28页
(续)
显示器类型 显示模式 缓冲区段地址 缓冲区长度 显示页数
EGA 单色 B000h 可变 可变
文本 B800h 可变 可变
图形 A000h 可变 可变
MCGA 文本 B800h 32K 8
图形 A000h 64K 1
VGA 单色 B000h 可变 可变
文本 B800h 可变 可变
图形 A000h 可变 可变
尽管本节给出了显示卡功能的概述,但如果要了解更详细的信息,还是请参阅第5章
“输出设备”。第5章提供了一些关于显示卡怎样解释视频内存以及怎样用BIOS和DOS
功能去显示信息的内容。
2.5.3打印机
在这本书中,名词打印机一般指连接在并行打印机上的打印机,而不指连在串行口上
的打印机(串行口将在下一节和第7章“串行设备”中讨论)。通过并行打印机接口,可以向
打印机传送一个初始信息,并且查询打印机当前状态,例如,打印纸是否用完等。这里特指
的是在DOS层用打印机可以做些什么。而打印机的使用则超出了本书的范围。
在第5章“输出设备”中,读者将学到怎样编写一个程序,用BIOS和DOS功能直接访
问打