深入探索MS - DOS编程:段操作、程序结构与中断处理
1. 段覆盖(Segment Overrides)
段覆盖是一个单字节前缀,它能让当前指令在计算有效地址时,使用与
ASSUME
指令指定不同的段寄存器。例如,可用于访问当前与
CS
或
DS
关联段之外的变量:
mov al,cs:varl ; 访问CS指向的段中的变量
mov al,es:var2 ; 访问ES指向的段中的变量
需要注意的是,在实地址模式下,可以将变量放在代码段,但在保护模式下则不行。
若要获取当前未被
DS
或
ES
假定的段中变量的偏移量,可使用如下指令:
mov bx,OFFSET AltSeg:var2
对于变量的多次引用,可通过插入
ASSUME
指令临时更改默认段引用,从而更方便地处理:
ASSUME ds:AltSeg ; 暂时使用AltSeg段
mov ax,AltSeg
mov ds,ax
mov al,varl
ASSUME ds:data ; 恢复使用默认数据段
mov ax,data
mov ds,ax
2. 段组合(Combining Segments)
大型程序应划分为独立模块,以简化编辑和调试。不同模块中的源代码也可组合到同一分段,只需在每个模块中使用相同的段名,并指定
PUBLIC
组合类型。
段对齐类型有以下几种:
-
BYTE
:每个段紧跟前一个段。
-
WORD
:段会在下一个偶数字边界处跟随另一个段。
-
PARA
(默认):每个段会在下一个段落边界处跟随。
以下是一个包含一个代码段(
CSEG
)、一个数据段(
DSEG
)和一个堆栈段(
SSEG
)的双模块程序示例:
主模块(Seg2.asm) :
TITLE Segment Example (main module, Seg2.asm)
EXTRN var2:WORD, subroutine_1:PROC
cseg SEGMENT BYTE PUBLIC 'CODE'
ASSUME cs:cseg,ds:dseg, ss:sseg
main PROC
mov ax,dseg ; 初始化DS
mov ds,ax
mov ax,varl ; 本地变量
mov bx,var2 ; 外部变量
call subroutine_i ; 外部过程
mov ax,4C00h ; 退出到操作系统
int 21h
main ENDP
cseg ENDS
dseg SEGMENT WORD PUBLIC 'DATA' ; 本地数据段
var WORD 1000h
dseg ends
sseg SEGMENT STACK 'STACK' ; 堆栈段
BYTE 100h dup('S')
sseg ENDS
END main
子模块(Seg2a.ASM) :
TITLE Segment Example (submodule, Seg2a.ASM)
PUBLIC subroutine_i, var2
cseg SEGMENT BYTE PUBLIC 'CODE'
ASSUME cs:cseg, ds:dseg
subroutine_i PROC ; 由主程序调用
mov ah,9
mov dx,OFFSET msg
int 21h
ret
subroutine_i ENDP
cseg ENDS
dseg SEGMENT WORD PUBLIC 'DATA'
var2 WORD 2000h ; 被主程序访问
msg BYTE 'Now in Subroutine_i', 0Dh,0Ah,'$'
dseg ENDS
END
链接器生成的MAP文件显示了一个代码段、一个数据段和一个堆栈段:
| Start | Stop | Length | Name | Class |
| ---- | ---- | ---- | ---- | ---- |
| 00000H | 0001BH | 0001CH | CSEG | CODE |
| 0001CH | 00035H | 0001AH | DSEG | DATA |
| 00040H | 013FH | 0100H | SSEG | STACK |
程序入口点位于0000:0000。
3. 运行时程序结构(Runtime Program Structure)
当在MS - DOS提示符下输入命令时,会按以下顺序执行:
1. MS - DOS检查命令是否为内部命令(如
DIR
、
REN
或
DEL
),若是,则由驻留在内存中的MS - DOS例程立即执行。
2. 查找扩展名为
COM
的匹配文件,若在当前目录中找到,则执行该文件。
3. 查找扩展名为
EXE
的匹配文件,若在当前目录中找到,则执行该文件。
4. 查找扩展名为
BAT
的匹配文件,若在当前目录中找到,则执行该文件。
BAT
文件是包含MS - DOS命令的文本文件,执行时就像在控制台输入这些命令一样。
5. 若在当前目录中未找到匹配的
COM
、
EXE
或
BAT
文件,MS - DOS会搜索当前路径中的第一个目录,若未找到匹配项,则继续搜索路径中的下一个目录,直到找到匹配文件或搜索完整个路径。
扩展名为
COM
和
EXE
的应用程序称为临时程序,一般在执行时被加载到内存,执行完毕后释放所占用的内存。若需要,临时程序在退出时可将部分代码留在内存中,这类程序称为内存驻留程序(TSRs)。
3.1 程序段前缀(Program Segment Prefix)
MS - DOS在程序加载到内存时,会在程序开头创建一个特殊的256字节块,称为程序段前缀(PSP),其结构如下表所示:
| Offset | Comments |
| ---- | ---- |
| 00 - 15 | MS - DOS指针和向量地址 |
| 16 - 28 | 由MS - DOS保留 |
| 2C - 2D | 当前环境字符串的段地址 |
| 2E - 58 | 由MS - DOS保留 |
| 5C - 7F | 文件控制块1和2,主要用于MS - DOS 2.0之前的程序 |
| 80 - FF | 默认磁盘传输区域和当前MS - DOS命令尾部的副本 |
3.2 COM程序
COM程序是机器语言程序的未修改二进制映像,由MS - DOS加载到最低可用段地址,在偏移量0处创建PSP。代码、数据和堆栈存储在同一物理(和逻辑)段中,程序最大可达64K,减去PSP的大小和堆栈末尾的两个保留字节。所有段寄存器都设置为PSP的基地址,代码区从偏移量100h开始,数据区紧跟代码区,堆栈区在段末尾,因为MS - DOS将
SP
初始化为FFFEh。
以下是一个简单的COM格式程序示例:
TITLE Hello Program in COM format (HelloCom.asm)
.model tiny
code
org 100h ; 必须在main之前
main PROC
mov ah,9
mov dx,OFFSET hello_message
int 21h
mov ax,4C00h
int 21h
main ENDP
hello_message BYTE 'Hello, world!',0Dh,0Ah,'$'
END main
由于没有单独的数据段,变量通常位于主过程之后。若将数据放在程序顶部,CPU会尝试执行数据。另一种方法是在开头放置一条
JMP
指令,跳过数据到第一条实际指令:
TITLE Hello Program in COM format (HelloCom.asm)
.model tiny
code
org 100h ; 必须在入口点之前
main proc
jmp start ; 跳过数据
hello_message BYTE 'Hello, world!',0Dh,0Ah,'$'
start:
mov ah,9
mov dx,OFFSET hello_message
int 21h
mov ax,4C00h
int 21h
main ENDP
END main
使用Microsoft链接器创建COM文件时,需要使用
,T
参数。COM程序通常比其EXE对应程序小,但在内存中会占用整个64K内存段,且不适合在多任务环境中运行。
3.3 EXE程序
EXE程序存储在磁盘上时,包含一个EXE头和一个包含程序本身的加载模块。程序头实际上不会加载到内存,而是包含MS - DOS用于加载和执行程序的信息。
MS - DOS加载EXE程序时,会在第一个可用地址创建程序段前缀(PSP),程序则放在其上方。解析程序头时,MS - DOS将
DS
和
ES
设置为程序的加载地址(即PSP),
CS
和
IP
设置为程序代码的入口点,
SS
设置为堆栈段的开头,
SP
设置为堆栈大小。
EXE程序可包含多达65,535个段,但通常不会有这么多。若程序有多个数据段,程序员通常需要手动将
DS
或
ES
设置为每个新段。
EXE程序使用的内存量由其程序头指定,特别是代码区之后处理变量和堆栈所需的最小和最大段落数(每个段落16字节)。默认情况下,链接器将最大值设置为65,535个段落,但MS - DOS会自动分配可用的内存。
可在链接程序时使用
/CP
选项设置最大分配量,例如:
link16 /cp:1024 prog1;
使用Microsoft汇编器提供的
exehdr
程序,可在EXE程序编译后修改其头值。例如,将名为
prog1.exe
的程序的最大分配量设置为400h段落(16,384字节)的命令为:
exehdr prog1 /max 400
exehdr
还可显示程序的重要统计信息,以下是一个示例输出:
| PROG1 | (Hex) | (Dec) |
| ---- | ---- | ---- |
| EXE size (bytes) | 876 | 2166 |
| Minimum load size (bytes) | 786 | 1926 |
| Overlay number | 0 | 0 |
| Initial CS:IP | 0000:0010 | 16 |
| Initial SS:SP | 0068:0100 | 256 |
| Minimum allocation (para) | 11 | 17 |
| Maximum allocation (para) | 400 | 1024 |
| Reader size (para) | 20 | 32 |
| Relocation table offset | 1E | 30 |
| Relocation entries | 1 | 1 |
EXE程序的头区包含以下信息:
- 重定位表,包含程序加载时需要计算的地址。
- EXE程序的文件大小,以512字节为单位。
- 最小分配量:程序代码区之后要保留的最小内存段落数,部分存储可用于保存动态数据的运行时堆。
- 最大分配量:程序上方所需的最大段落数。
-
IP
和
SP
寄存器的初始值。
- 堆栈和代码段相对于加载模块开头的位移(以16字节段落为单位)。
- 文件中所有字的校验和,用于在将程序加载到内存时捕获数据错误。
4. 中断处理(Interrupt Handling)
BIOS和MS - DOS包含用于简化输入/输出和基本系统任务的中断处理程序,但操作系统中同样重要的是响应硬件中断的一组中断处理程序。MS - DOS允许用自定义的服务例程替换其中任何一个。
不过,本章介绍的中断处理程序仅在计算机启动到MS - DOS模式时有效,可通过Windows 95和98实现,但Windows NT、2000和XP不支持,因为这些操作系统会对应用程序屏蔽系统硬件,以提高系统稳定性和安全性。
编写中断处理程序可能有多种原因,例如:
- 希望程序在按下热键时激活,即使在用户运行其他应用程序时也能如此。
- 替换MS - DOS的默认中断处理程序,以提供更完整的服务。
- 替换MS - DOS的关键错误处理程序或Ctrl - Break处理程序,使程序能从错误中恢复并让用户继续运行当前应用程序。
- 自定义中断服务例程能比MS - DOS更有效地处理硬件中断。
4.1 中断向量表(Interrupt Vector Table)
MS - DOS的灵活性关键在于位于RAM前1024字节(地址0:0到0:03FF)的中断向量表。表中的每个条目(中断向量)是一个32位段偏移地址,指向现有的服务例程之一。以下是向量表条目的简短示例:
| Interrupt Number | Offset | Interrupt Vectors |
| ---- | ---- | ---- |
| 00 - 03 | 0000 | 02C1:5186 0070:0c67 ODAD:2C1B 0070:0C67 |
| 04 - 07 | 0010 | 0070:0C67 F000:FFS4 F000:8378 F000:837B |
| 08 - 0B | 0020 | 0D70:022C ODAO:2BAD 0070:0325 0070:039F |
| 0C - 0F | 0030 | 0070:0419 0070:0493 0070:050D 0070:0C67 |
| 10 - 13 | 0040 | C000:OCD7 F000:F840 F000:F841 0070:237D |
不同计算机上的向量值会因BIOS和MS - DOS的不同版本而有所差异。每个中断向量对应一个中断号,中断向量的偏移量可通过将其中断号乘以4得到,例如,INT 9h的向量偏移量为9 * 4,即十六进制的0024。
4.2 执行中断处理程序
中断处理程序可通过以下两种方式执行:
1. 包含
INT
指令的应用程序可调用该例程,这称为软件中断。
2. 硬件设备(异步端口、键盘、定时器等)向可编程中断控制器芯片发送信号时,会发生硬件中断。
4.3 硬件中断(Hardware Interrupts)
硬件中断由Intel 8259可编程中断控制器(PIC)生成,它会向CPU发出信号,暂停当前程序的执行,并执行中断服务例程。
在对段寄存器和堆栈执行敏感操作时,程序有时需要禁用硬件中断。
CLI
(清除中断标志)指令可禁用中断,
STI
(设置中断标志)指令可启用中断。
PC上的多个不同设备可触发中断,每个设备根据其中断请求级别(IRQ)有一个优先级,级别0优先级最高,级别15优先级最低。低级别中断不能中断正在进行的高级别中断,多个同时发生的中断请求将根据其优先级级别进行处理,中断调度由8259 PIC处理。
以键盘为例,当按下一个键时,8259 PIC会向CPU发送INTR信号,并传递中断号。若当前未禁用外部中断,CPU将按以下顺序执行操作:
1. 将标志寄存器压入堆栈。
2. 清除中断标志,防止其他硬件中断。
3. 将当前的
CS
和
IP
压入堆栈。
4. 定位INT 9的中断向量表条目,并将该地址放入
CS
和
IP
。
综上所述,MS - DOS编程涉及段操作、程序结构和中断处理等多个方面,理解这些概念和技术对于编写高效、稳定的程序至关重要。无论是段覆盖和组合的灵活运用,还是COM和EXE程序的不同特点,以及中断处理的多种应用场景,都需要开发者深入掌握并根据实际需求进行合理选择和使用。
5. 深入理解中断处理机制
5.1 硬件中断的优先级管理
硬件中断的优先级管理是确保系统稳定运行的关键。不同设备的中断请求级别(IRQ)决定了它们在系统中的优先级顺序。以下是一个常见设备的IRQ级别列表:
| 设备 | IRQ 级别 |
| ---- | ---- |
| 系统定时器 | 0 |
| 键盘控制器 | 1 |
| 级联(用于第二个 8259 PIC) | 2 |
| 串口 2 | 3 |
| 串口 1 | 4 |
| 并口 2 | 5 |
| 软盘控制器 | 6 |
| 并口 1 | 7 |
| 实时时钟 | 8 |
| 通用可用 | 9 |
| 通用可用 | 10 |
| 通用可用 | 11 |
| 鼠标控制器 | 12 |
| 数学协处理器 | 13 |
| 主硬盘控制器 | 14 |
| 从硬盘控制器 | 15 |
从这个列表中可以看出,系统定时器具有最高优先级,这是因为它对于系统的计时和任务调度至关重要。而一些通用可用的IRQ级别则可以根据需要分配给其他设备。
为了更好地理解硬件中断的优先级管理流程,我们可以使用 mermaid 绘制一个流程图:
graph TD;
A[设备产生中断请求] --> B{判断 IRQ 级别};
B -- 高优先级 --> C[立即响应中断];
B -- 低优先级 --> D{是否有高优先级中断正在处理};
D -- 是 --> E[等待高优先级中断处理完成];
D -- 否 --> C;
C --> F[执行中断服务程序];
F --> G[恢复原程序执行];
这个流程图展示了硬件中断请求的处理过程。当设备产生中断请求时,系统首先判断其IRQ级别。如果是高优先级中断,将立即响应;如果是低优先级中断,则需要检查是否有高优先级中断正在处理。只有在没有高优先级中断处理时,低优先级中断才能被响应。
5.2 中断服务程序的编写要点
编写中断服务程序时,需要注意以下几个关键要点:
1.
保存和恢复寄存器状态
:在中断服务程序开始时,需要保存所有可能被修改的寄存器的值,以确保原程序的执行不受影响。在中断服务程序结束时,再恢复这些寄存器的值。例如:
push ax
push bx
; 中断服务程序的主要代码
pop bx
pop ax
- 快速响应和处理 :中断服务程序应该尽可能快速地完成任务,以减少对原程序执行的影响。如果中断服务程序需要处理大量数据或执行复杂操作,可以考虑将部分任务放到后台处理。
-
中断标志的管理
:在中断服务程序中,需要根据需要管理中断标志。如果在处理中断时需要禁止其他中断,可以使用
CLI指令;在处理完成后,使用STI指令恢复中断。
6. 段操作与程序结构的优化策略
6.1 段覆盖的优化使用
段覆盖虽然提供了访问不同段中变量的灵活性,但过多使用段覆盖会增加指令的长度和执行时间。因此,在实际编程中,应该尽量减少段覆盖的使用。可以通过合理的段定义和
ASSUME
指令来减少对段覆盖的依赖。
例如,在一个程序中,如果需要频繁访问多个段中的变量,可以使用
ASSUME
指令临时更改默认段引用,而不是每次都使用段覆盖:
ASSUME ds:AltSeg
; 访问 AltSeg 段中的变量
mov ax, var1
ASSUME ds:data
; 访问 data 段中的变量
mov bx, var2
6.2 COM 和 EXE 程序的选择与优化
COM 和 EXE 程序各有优缺点,在选择时需要根据实际需求进行考虑。
-
COM 程序
:适用于小型、简单的程序,因为它的结构简单,加载速度快。但由于其内存限制和不适合多任务环境的特点,在编写大型程序时需要谨慎使用。为了优化 COM 程序的内存使用,可以尽量减少数据和代码的大小,避免不必要的变量和代码。
-
EXE 程序
:适用于大型、复杂的程序,因为它可以包含多个段,并且可以灵活控制内存分配。在编写 EXE 程序时,可以通过合理设置程序头中的最小和最大分配量,以及使用
exehdr
程序进行优化,来提高内存使用效率。
7. 总结与展望
MS - DOS 编程涉及段操作、程序结构和中断处理等多个方面,这些知识对于深入理解计算机系统和编写高效程序具有重要意义。通过合理运用段覆盖和组合技术,可以实现更灵活的内存管理;了解 COM 和 EXE 程序的特点和优化策略,可以根据不同需求选择合适的程序类型;掌握中断处理机制,则可以提高系统的响应速度和稳定性。
随着计算机技术的不断发展,MS - DOS 已经逐渐退出历史舞台,但其中的许多编程思想和技术仍然具有借鉴价值。例如,中断处理机制在现代操作系统中仍然广泛应用,用于处理各种硬件设备的中断请求。未来,我们可以将这些传统的编程技术与现代编程语言和开发工具相结合,创造出更加高效、稳定的软件系统。
在实际开发中,开发者需要不断学习和实践,深入理解这些技术的原理和应用场景,才能编写出高质量的程序。同时,也需要关注计算机技术的发展趋势,及时掌握新的编程理念和方法,以适应不断变化的市场需求。
超级会员免费看
1366

被折叠的 条评论
为什么被折叠?



