MS-DOS操作与应用程序结构解析
立即解锁
发布时间: 2025-08-24 00:02:20 阅读量: 1 订阅数: 4 

# MS-DOS 操作与应用程序结构解析
## 1. MS-DOS 结构概述
MS-DOS 被划分为多个层次,这些层次将操作系统的内核逻辑以及用户对系统的感知与运行它的硬件隔离开来。主要层次包括:
- **BIOS(基本输入/输出系统)**:特定于单个计算机系统,由系统制造商提供。包含以下设备的默认常驻硬件相关驱动程序:
- 控制台显示和键盘(CON)
- 行式打印机(PRN)
- 辅助设备(AUX)
- 日期和时间(CLOCKS)
- 引导磁盘设备(块设备)
- 在系统初始化期间,BIOS 作为名为 IO.SYS 的文件的一部分被读入随机存取存储器(RAM)。在 PC - DOS 中,该文件名为 IBMBIO.COM,且标记有隐藏和系统属性。
- **DOS 内核**:实现了应用程序所看到的 MS - DOS。它是微软公司提供的专有程序,提供了一组独立于硬件的服务,称为系统功能,包括:
- 文件和记录管理
- 内存管理
- 字符设备输入/输出
- 生成其他程序
- 访问实时时钟
- 程序可以通过用特定于功能的参数加载寄存器,然后通过软件中断转移到操作系统来访问系统功能。在系统初始化期间,DOS 内核从引导磁盘上的 MSDOS.SYS 文件读入内存。在 PC - DOS 中,该文件名为 IBMDOS.COM,同样标记有隐藏和系统属性。
- **命令处理器(shell)**:是用户与操作系统的接口,负责解析和执行用户命令,包括从磁盘或其他大容量存储设备加载和执行其他程序。默认的 shell 位于名为 COMMAND.COM 的文件中。不过,COMMAND.COM 并非操作系统,只是在 MS - DOS 控制下运行的一类特殊程序。可以通过在系统启动磁盘的系统配置文件(CONFIG.SYS)中添加 SHELL 指令,用程序员自己设计的 shell 替换它。例如,ESP Systems 的 COMMAND - PLUS 就是这样一种替代 shell。
### 1.1 COMMAND.COM 详解
COMMAND.COM 分为三个部分:
- **常驻部分**:加载在较低内存中,位于 DOS 内核及其缓冲区和表之上。包含处理 Ctrl - C 和 Ctrl - Break、严重错误以及其他临时程序终止(最终退出)的例程。会发出错误消息,并负责显示熟悉的提示信息 “Abort, Retry, Ignore?”。还包含在必要时重新加载 COMMAND.COM 临时部分的代码。
- **初始化部分**:系统启动时加载在常驻部分之上。如果存在 AUTOEXEC.BAT 批处理文件(用户在系统启动时要执行的命令列表),则处理该文件,然后被丢弃。
- **临时部分**:加载在内存的高端,其内存也可由应用程序用于其他目的。发出用户提示,从键盘或批处理文件读取命令并执行。当应用程序终止时,COMMAND.COM 的常驻部分会对临时模块进行校验和检查,以确定其是否已损坏,必要时从磁盘获取新副本。
用户输入的命令被 COMMAND.COM 接受后可分为三类:
- **内部命令**:有时也称为固有命令,由嵌入在 COMMAND.COM 本身中的代码执行,如 COPY、REN(AME)、DIR(ECTORY) 和 DEL(ETE) 等。内部命令的例程包含在 COMMAND.COM 的临时部分。
- **外部命令**:有时称为外部命令或临时程序,是存储在磁盘文件中的程序名称。在执行这些程序之前,必须将它们从磁盘加载到内存的临时程序区域(TPA)。常见的外部命令有 CHKDSK、BACKUP 和 RESTORE 等。外部命令完成工作后,会从内存中丢弃,因此每次调用时都必须从磁盘重新加载。
- **批处理文件**:是包含其他固有、外部或批处理命令列表的文本文件。由内置在 COMMAND.COM 临时部分的特殊解释器处理,解释器逐行读取批处理文件并按顺序执行每个指定的操作。
为了解释用户的命令,COMMAND.COM 首先检查用户是否输入了它可以直接执行的内置(固有)命令的名称。如果不是,则搜索同名的外部命令(可执行程序文件)或批处理文件。搜索首先在当前磁盘驱动器的当前目录中进行,然后在最近的 PATH 命令指定的每个目录中进行。在检查的每个目录中,COMMAND.COM 首先尝试查找扩展名为.COM 的文件,然后是.EXE,最后是.BAT。如果在所有可能的位置对这三种文件类型的搜索都失败,COMMAND.COM 会显示熟悉的消息 “Bad command or file name”。如果找到.COM 文件或.EXE 文件,COMMAND.COM 使用 MS - DOS 的 EXEC 函数加载并执行它。EXEC 函数在 COMMAND.COM 常驻部分上方的临时程序区域中构建一个名为程序段前缀(PSP)的特殊数据结构,PSP 包含应用程序所需的各种链接和指针。接着,EXEC 函数加载程序本身,位于 PSP 上方,并执行可能需要的重定位。最后,适当地设置寄存器并将控制权转移到程序的入口点。
## 2. MS-DOS 加载过程
当系统启动或重置时,程序执行从地址 OFFFFOH 开始,这是 8086/8088 系列微处理器的特性,与 MS - DOS 无关。基于这些处理器的系统设计为,地址 OFFFFOH 位于 ROM 区域内,并包含一条跳转机器指令,用于将控制权转移到系统测试代码和 ROM 引导例程。
```mermaid
graph LR
A[系统启动或重置] --> B[执行从 OFFFFOH 开始]
B --> C[跳转到 ROM 引导例程]
C --> D[ROM 引导例程读取磁盘引导例程]
D --> E[磁盘引导例程检查 MS - DOS 文件]
E --> |找到文件| F[读取 IO.SYS 和 MSDOS.SYS 到内存]
E --> |未找到文件| G[提示用户更换磁盘并重试]
F --> H[IO.SYS 包含 BIOS 和 SYSINIT]
H --> I[SYSINIT 确定内存并重新定位]
I --> J[初始化 DOS 内核]
J --> K[打开 CONFIG.SYS 文件(如果存在)]
K --> L[处理 CONFIG.SYS 中的命令]
L --> M[加载可安装设备驱动程序]
M --> N[重新打开标准设备]
N --> O[加载命令解释器(shell)]
O --> P[显示提示并等待用户输入命令]
```
具体加载步骤如下:
1. **ROM 引导例程**:从系统启动磁盘的第一个扇区(引导扇区)将磁盘引导例程读入内存中的某个任意地址,然后将控制权转移给它。引导扇区还包含有关磁盘格式的信息表。
2. **磁盘引导例程**:检查磁盘是否包含 MS - DOS 的副本,通过读取根目录的第一个扇区,确定前两个文件是否为 IO.SYS 和 MSDOS.SYS(或 IBMBIO.COM 和 IBMDOS.COM)。如果这些文件不存在,提示用户更换磁盘并按任意键重试。
3. **加载系统文件**:如果找到两个系统文件,磁盘引导程序将它们读入内存,并将控制权转移到 IO.SYS 的初始入口点。在某些实现中,磁盘引导程序仅将 IO.SYS 读入内存,然后由 IO.SYS 加载 MSDOS.SYS 文件。
4. **IO.SYS 模块**:从磁盘加载的 IO.SYS 文件实际上由两个独立的模块组成。第一个是 BIOS,包含用于控制台、辅助端口、打印机、块和时钟设备的一组链接的常驻设备驱动程序,以及仅在系统启动时运行的一些特定于硬件的初始化代码。第二个模块 SYSINIT 由微软提供,并由计算机制造商与 BIOS 一起链接到 IO.SYS 文件中。
5. **SYSINIT 操作**:由制造商的 BIOS 初始化代码调用,确定系统中连续内存的数量,然后将自身重新定位到高端内存。接着,将 DOS 内核 MSDOS.SYS 从其原始加载位置移动到最终内存位置,覆盖 IO.SYS 文件中原来的 SYSINIT 代码和任何其他可消耗的初始化代码。
6. **DOS 内核初始化**:SYSINIT 调用 MSDOS.SYS 中的初始化代码,DOS 内核初始化其内部表和工作区域,设置中断向量 20H 到 2FH,并遍历常驻设备驱动程序的链表,调用每个驱动程序的初始化函数。这些驱动程序函数确定设备状态,执行必要的硬件初始化,并设置驱动程序将服务的任何外部硬件中断的向量。
7. **配置文件处理**:作为初始化序列的一部分,DOS 内核检查常驻块设备驱动程序返回的磁盘参数块,确定系统中将使用的最大扇区大小,构建一些驱动器参数块,并分配一个磁盘扇区缓冲区。然后控制权返回给 SYSINIT。当 DOS 内核初始化完成且所有常驻设备驱动程序可用时,SYSINIT 可以调用正常的 MS - DOS 文件服务来打开 CONFIG.SYS 文件。该可选文件可以包含各种命令,使用户能够自定义 MS - DOS 环境,例如指定额外的硬件设备驱动程序、磁盘缓冲区的数量、一次可以打开的最大文件数以及命令处理器(shell)的文件名。如果找到该文件,将整个 CONFIG.SYS 文件加载到内存中进行处理,将所有小写字符转换为大写,并逐行解释文件以处理命令。为磁盘缓冲区缓存和处理文件和记录系统功能使用的内部文件控制块分配内存。将 CONFIG.SYS 文件中指示的任何设备驱动程序顺序加载到内存中,通过调用其 init 模块进行初始化,并链接到设备驱动程序列表中。每个驱动程序的 init 函数会告诉 SYSINIT 为该驱动程序保留多少内存。
8. **重新打开标准设备**:所有可安装设备驱动程序加载完成后,SYSINIT 关闭所有文件句柄,并将控制台(CON)、打印机(PRN)和辅助(AUX)设备重新打开为标准输入、标准输出、标准错误、标准列表和标准辅助设备。这允许用户安装的字符设备驱动程序覆盖 BIOS 中标准设备的常驻驱动程序。
9. **加载命令解释器**:最后,SYSINIT 调用 MS - DOS 的 EXEC 函数加载命令解释器(shell)。默认的 shell 是 COMMAND.COM,但可以通过 CONFIG.SYS 文件替换为其他 shell。一旦 shell 加载完成,它会显示一个提示并等待用户输入命令,此时 MS - DOS 准备好运行,SYSINIT 模块被丢弃。
## 3. MS-DOS 应用程序结构
在 MS - DOS 下运行的程序主要有两种类型:
- **.COM 程序**:最大大小约为 64 KB。在 Intel 8086 术语中,.COM 程序符合微型模型,其中所有段寄存器包含相同的值,即代码和数据混合在一起。以绝对内存映像的形式存储在扩展名为.COM 的磁盘文件中,文件没有头或任何其他内部标识信息。
- **.EXE 程序**:大小可以达到可用内存的上限。符合小、中或大模型,其中段寄存器包含不同的值,即代码、数据和堆栈位于不同的段中。.EXE 程序可以有多个代码和数据段,分别通过长调用和操作数据段(DS)寄存器进行寻址。以特殊类型的文件形式存储在磁盘上,具有独特的头、重定位映射、校验和等信息,这些信息被 MS - DOS 使用。
### 3.1 程序段前缀(PSP)
PSP 是一个 256 字节长的保留区域,由 MS - DOS 在分配给临时程序的内存块的底部设置。它包含一些与 MS - DOS 的链接,可供临时程序使用;一些 MS - DOS 为自身保存的信息;以及一些 MS - DOS 传递给临时程序的信息,程序可根据需要使用或不使用。
| 偏移量 | 描述 |
| ---- | ---- |
| 0000H | Int 20H |
| 0002H | 分配块结束的段地址 |
| 0004H | 保留 |
| 0005H | 长调用到 MS - DOS 函数调度程序 |
| 000AH | 终止处理程序中断向量(Int 22H)的先前内容 |
| 000EH | Ctrl - C 中断向量(Int 23H)的先前内容 |
| 0012H | 严重错误处理程序中断向量(Int 24H)的先前内容 |
| 0016H | 保留 |
| 002CH | 环境块的段地址 |
| 002EH | 保留 |
| 005CH | 默认文件控制块 #1 |
| 006CH | 默认文件控制块 #2(如果 FCB #1 打开则被覆盖) |
| 0080H | 命令尾部和默认磁盘传输区域(缓冲区) |
在早期的 MS - DOS 版本中,PSP 的设计与 Digital Research 的 CP/M 操作系统下临时程序下方构建的控制区域兼容,以便程序可以在不进行大量逻辑更改的情况下移植到 MS - DOS。尽管 MS - DOS 自早期以来已经有了很大的发展,但 PSP 的结构仍然与 CP/M 中的等效结构相似。例如,PSP 中的偏移量 0000H 包含与 MS - DOS 进程终止处理程序的链接,该处理程序在程序完成工作并进行最终退出后进行清理。同样,PSP 中的偏移量 0005H 包含与 MS - DOS 函数调度程序的链接,该调度程序根据临时程序的请求执行磁盘操作、控制台输入/输出等服务。不过,这些链接并非获取这些服务的 “批准” 方式。
### 3.2 .COM 程序介绍
.COM 程序存储在磁盘文件中,保存着要执行的机器指令的绝对映像。由于文件不包含重定位信息,它们比等效的.EXE 文件更紧凑,加载执行速度也稍快。MS - DOS 不会尝试确定.COM 文件是否实际包含可执行代码(不像.EXE 文件有签名或校验和),只是将任何扩展名为.COM 的文件加载到内存并跳转到它。
.COM 程序必须始终以 0100H 为起始地址,因为它们加载在程序段前缀的正上方,且没有可以指定其他入口点的头。位置 0100H 必须包含可执行指令。.COM 程序的最大长度为 65,536 字节,减去 PSP 的长度(256 字节)和一个必需的堆栈字(2 字节)。
当控制权从 MS - DOS 转移到.COM 程序时,所有段寄存器都指向 PSP。如果内存允许,堆栈指针(SP)寄存器包含 OFFFEH;否则,将其设置为内存中尽可能高的位置减去 2 字节。(MS - DOS 在进入前会在堆栈上压入一个零字)。
虽然可执行.COM 文件的大小不能超过 64 KB,但当前版本的 MS - DOS 在加载.COM 程序时会将所有临时程序区域分配给它们。因为许多此类程序可以追溯到 MS - DOS 的早期,在内存管理方面不一定 “表现良好”,所以操作系统只是做最坏情况的假设,将可用的所有内存都分配给.COM 程序。如果.COM 程序想使用 EXEC 函数调用另一个进程,必须首先将其内存分配缩小到继续运行所需的最小内存,同时注意保护其堆栈。
.COM 程序执行完毕后,可以通过多种方式将控制权返回给 MS - DOS。首选方法是使用 Int 21H 功能 4CH,这允许程序将返回代码传递给调用它的程序、shell 或批处理文件。不过,如果程序在 MS - DOS 版本 1 下运行,必须通过 Int 20H、Int 21H 功能 0 或近返回(NEAR RETURN)退出。(因为进入时在堆栈上压入了一个零字,近返回会导致转移到 PSP:0000,其中包含一条 Int 20H 指令)。
### 3.3 .COM 程序示例:HELLO.COM
```asm
1: name hello
2: page 55,132
3: title HELLO.COM--print hello on terminal
4: HELLO.COM: demonstrates various components
5: of a functional.COM-type assembly-
6: language program, and an MS-DOS function call.
7: Ray Duncan, May 1988
8: stdin equ 0 ; standard input handle
9: stdout equ 1 ; standard output handle
10: stdderr equ 2 ; standard error handle
11: cr equ Odh ; ASCII carriage return
12: lf equ Oah ; ASCII line feed
13: _TEXT segment word public 'CODE'
14: org 100h ; .COM files always have an origin of 100h
15: assume cs:_TEXT,ds:_TEXT,es:_TEXT,ss:_TEXT
16: print proc near ; entry point from MS-DOS
17: mov ah,40h ; function 40h - write
18: mov bx,stdout ; standard output handle
19: mov cx,msg_len ; length of message
20: mov dx,offset msg ; address of message
21: int 21h ; transfer to MS-DOS
22: mov ax,4c00h ; exit, return code - 0
23: int 21h ; transfer to MS-DOS
24: print endp
25: msg db cr,lf ; message to display
26: db 'Hello World!',cr,lf
27: msg_len equ $-msg ; length of message
28: _TEXT ends
29: end print ; defines entry point
```
该程序展示了一个简单的汇编语言程序的结构,该程序将成为一个.COM 文件。以下是对代码的详细解释:
- **NAME 语句**:为链接过程提供模块名称,有助于理解链接器生成的映射。在 MASM 5.0 及更高版本中,模块名称始终与文件名相同,NAME 语句会被忽略。
- **PAGE 命令**:当使用两个操作数时,如第 2 行,定义页面的长度和宽度,默认分别为 66 行和 80 个字符。如果不使用操作数,会向打印机发送换页符并打印标题。在较大的程序中,应大量使用 PAGE 命令,将每个子程序放在单独的页面上以便于阅读。
- **TITLE 命令**:在第 3 行,指定要在每页左上角打印的文本字符串(限制为 60 个字符)。TITLE 命令是可选的,并且在每个汇编语言源文件中最多只能使用一次。
- **代码段声明**:从第 13 行的 SEGMENT 命令开始,到第 28 行的 ENDS 命令结束。标签 _TEXT 为代码段命名,操作数赋予段属性 WORD、PUBLIC 和 ‘CODE’。
- **ORG 语句**:在第 14 行,将程序的起始地址设置为 0100H,因为.COM 文件必须始终以该地址为起始。
- **ASSUME 语句**:在第 15 行,告知汇编器将使用哪些段寄存器指向程序的各个段,以便汇编器在必要时提供段覆盖。但它不会将段寄存器加载正确的值,只是通知汇编器程序内部的意图。在.COM 程序中,MS - DOS 在进入前会将所有段寄存器初始化为指向 PSP。
- **PROC 和 ENDP 指令**:在第 16 行和第 24 行,声明了一个名为 print 的过程。在.COM 程序中,所有过程都具有 NEAR 属性,因为所有可执行代码都位于一个段中。
- **print 过程**:调用 MS - DOS Int 21H 功能 40H 将消息 “Hello World!” 发送到视频屏幕,然后调用 Int 21H 功能 4CH 终止程序。
- **END 语句**:在第 29 行,告诉汇编器已到达源文件的末尾,并指定程序的入口点。如果入口点不是位于偏移量 0100H 的标签,则该源程序汇编和链接后生成的.EXE 文件无法转换为.COM 文件。
### 3.4 .EXE 程序介绍
虽然.COM 程序结构简单,适合快速编写实用程序,但存在一些明显的缺点,因此大多数为 MS - DOS 编写的严肃汇编语言程序都被转换为.EXE 文件。
.EXE 程序的大小实际上没有限制(可达计算机可用内存的上限),并且将代码、数据和堆栈放在文件的不同部分。尽管正常的 MS - DOS 程序加载器不会利用.EXE 文件的这一特性,但在多任务环境(如 Microsoft Windows)中,将大型程序的不同部分加载到多个独立的内存片段中,以及指定程序中可以由多个任务共享的 “纯” 代码部分的能力非常重要。
MS - DOS 加载器总是将.EXE 程序加载到程序段前缀的正上方,不过代码、数据和堆栈段的顺序可能会有所不同。.EXE 文件有一个头或控制信息块,具有特定的格式。头的大小根据加载时需要重定位的程序指令数量而变化,但始终是 512 字节的倍数。
在 MS - DOS 将控制权转移给程序之前,代码段(CS)寄存器和指令指针(IP)寄存器的初始值根据.EXE 文件头中的入口点信息和程序的加载地址计算得出。这些信息来自程序某个模块的源代码中的 END 语句。数据段(DS)和额外段(ES)寄存器指向 PSP,以便程序可以访问其中包含的环境块指针、命令尾部和其他有用信息。
堆栈段(SS)和堆栈指针(SP)寄存器的初始内容来自头。这些信息来自程序源代码中某个具有 STACK 属性的段的声明。为堆栈分配的内存空间可以是初始化的或未初始化的,具体取决于堆栈段的定义。许多程序员喜欢用可识别的数据模式初始化堆栈内存,以便检查内存转储并确定程序实际使用了多少堆栈空间。
.EXE 程序处理完成后,应通过 Int 21H 功能 4CH 将控制权返回给 MS - DOS。虽然还有其他方法,但它们没有优势,而且相当不方便(因为通常需要 CS 寄存器指向 PSP)。
### 3.5 .EXE 程序示例:HELLO.EXE
```asm
7: name hello
8: page 55,132
9: title HELLO.EXE--print Hello on terminal
10: HELLO.EXE: demonstrates various components
11: of a functional.EXE-type assembly-
12: language program, use of segments,
13: and an MS-DOS function call.
14: Ray Duncan, May 1988
15: stdin equ 0 ; standard input handle
16: stdout equ 1 ; standard output handle
17: stderr equ 2 ; standard error handle
18: cr equ Odh ; ASCII carriage return
19: lf equ Oah ; ASCII line feed
20: _TEXT segment word public 'CODE'
21: assume cs:_TEXT,ds:_DATA,ss:STACK
22: print proc far ; entry point from MS-DOS
23: mov ax,_DATA ; make our data segment
24: mov ds,ax ; addressable...
25: mov ah,40h ; function 40h - write
26: mov bx,stdout ; standard output handle
27: mov cx,msg_len ; length of message
28: mov dx,offset msg ; address of message
29: int 21h ; transfer to MS-DOS
30: mov ax,4c00h ; exit, return code - 0
31: int 21h ; transfer to MS-DOS
32: print endp
33: _TEXT ends
34: _DATA segment word public 'DATA'
35: msg db cr,lf ; message to display
36: db 'Hello World!',cr,lf
37: msg_len equ $-msg ; length of message
38: _DATA ends
39: STACK segment para stack 'STACK'
40: db 128 dup (?)
41: STACK ends
42: end print ; defines entry point
```
该程序展示了一个汇编语言程序的基本结构,该程序将成为一个.EXE 文件。以下是对代码的详细解释:
- **基本指令**:NAME、TITLE 和 PAGE 指令与 HELLO.COM 示例程序中的用法相同。
- **代码段声明**:从第 20 行的 SEGMENT 命令开始,到第 33 行的 ENDS 命令结束。标签 _TEXT 为代码段命名,操作数赋予段属性 WORD、PUBLIC 和 ‘CODE’。
- **ASSUME 语句**:在第 21 行,指定了几个不同的段名称,告知汇编器将使用哪些段寄存器指向程序的各个段。
- **print 过程**:在第 22 行声明,具有 FAR 属性。首先将 DS 寄存器初始化为指向数据段,然后调用 MS - DOS Int 21H 功能 40H 显示消息 “Hello World!”,最后调用 Int 21H 功能 4CH 退出程序,返回代码为零。
- **数据段声明**:从第 34 行到第 38 行,声明了一个名为 _DATA 的数据段,包含程序将使用的变量和常量。如果程序的各个模块包含多个同名的数据段,链接器会将它们收集并放置在同一个物理内存段中。
- **堆栈段声明**:从第 39 行到第 41 行,建立了一个堆栈段。PUSH 和 POP 指令将访问这个临时内存区域。在 MS - DOS 将控制权转移给.EXE 程序之前,会根据声明的堆栈段的大小和位置设置 SS 和 SP 寄存器。务必为运行时可能出现的最大堆栈深度留出足够的空间,以及在 MS - DOS 服务调用期间压入堆栈的寄存器所需的额外字。如果堆栈溢出,可能会损坏其他代码和数据段,导致程序行为异常甚至崩溃。
- **END 语句**:在第 42 行,告诉汇编器已到达源文件的末尾,并提供程序从 MS - DOS 进入的入口点的标签。
### 3.6 .COM 和 .EXE 程序的区别
| 比较项 | .COM 程序 | .EXE 程序 |
| ---- | ---- | ---- |
| 最大大小 | 65,536 字节减去 256 字节(PSP)和 2 字节(堆栈) | 无限制 |
| 入口点 | PSP:0100H | 由 END 语句定义 |
| AL 入口值 | 如果默认 FCB #1 驱动器有效为 00H,无效为 OFFH | 相同 |
| AH 入口值 | 如果默认 FCB #2 驱动器有效为 00H,无效为 OFFH | 相同 |
| CS 入口值 | PSP | 包含入口点模块的段 |
| IP 入口值 | 0100H | 入口点在其段内的偏移量 |
| DS 入口值 | PSP | PSP |
| ES 入口值 | PSP | PSP |
| SS 入口值 | PSP | 具有 STACK 属性的段 |
| SP 入口值 | OFFFEH 或可用内存中的最高字,取较低者 | 由 STACK 属性段定义的大小 |
| 堆栈入口 | 零字 | 初始化或未初始化 |
| 堆栈大小 | 65,536 字节减去 256 字节(PSP)和可执行代码及数据的大小 | 由 STACK 属性段定义 |
| 子程序调用 | 通常为近调用(NEAR) | 近调用或远调用(NEAR 或 FAR) |
| 退出方法 | 首选 Int 21H 功能 4CH,如果是 MS - DOS 版本 1 则为近返回(NEAR RET) | 首选 Int 21H 功能 4CH |
| 文件大小 | 程序的确切大小 | 程序大小加上头(512 字节的倍数) |
## 4. 汇编语言程序的更多信息
### 4.1 汇编语言程序的结构层次
基于 Microsoft Macro Assembler(MASM),汇编语言程序可以看作具有三个结构层次:
- **模块级别**:源代码的块,可以独立维护和汇编。每个源文件由汇编器翻译成可重定位的目标模块。目标模块可以单独存在于一个文件中,也可以与许多其他目标模块一起存在于一个常用或相关例程的目标模块库中。Microsoft Object Linker(LINK)将目标模块文件与从库中提取的额外目标模块组合成可执行程序文件。使用模块和目标模块库可以减小应用程序源文件的大小,提高生产力,因为这些文件不必包含与其他程序共有的例程的源代码。此外,维护例程也更容易,因为只需要在一个地方修改其源代码的一个副本。当改进或修复其中一个例程时,只需重新汇编它,将其目标模块放回库中,重新链接所有使用该例程的程序即可实现即时升级。
- **段级别**:“段” 指两个不同的编程概念:物理段和逻辑段。
- **物理段**:是 64 KB 的内存块。Intel 8086/8088 和 80286 微处理器有四个段寄存器,本质上用作指向这些块的指针。(80386 有六个段寄存器,是 8086/8088 和 80286 上的段寄存器的超集)。每个段寄存器可以指向不同的 64 KB 内存区域的底部。因此,程序可以通过适当操作段寄存器来寻址内存中的任何位置,但同时可寻址的最大内存量为 256 KB。.COM 程序假设所有四个段寄存器始终指向程序的底部,因此它们的最大大小限制为 64 KB。而.EXE 程序可以寻址许多不同的物理段,并在需要时重置段寄存器以指向每个段。因此,.EXE 程序大小的唯一实际限制是可用内存量。
- **逻辑段**:是程序的组件。任何.EXE 程序至少必须声明三个逻辑段:代码段、数据段和堆栈段。代码或数据超过 64 KB 的程序有多个代码或数据段。最常用的例程或数据放在主代码和数据段中以提高速度,不太常用的例程或数据放在辅助代码和数据段中。段使用 SEGMENT 和 ENDS 指令声明,格式如下:
```asm
name SEGMENT attributes
name ENDS
```
段的属性包括对齐类型(BYTE、WORD 或 PARA)、组合类型(PUBLIC、PRIVATE、COMMON 或 STACK)和类类型。链接器在组合逻辑段以创建可执行程序的物理段时使用这些段属性。大多数情况下,使用一小部分属性以一种典型的方式就可以正常工作。不过,如果想使用完整的属性范围,可能需要阅读 MASM 手册中的详细解释。
- **过程级别**:程序结构的过程级别部分是实际的,部分是概念性的。过程本质上是子程序的一种花哨形式。程序中的过程使用 PROC 和 ENDP 指令声明,格式如下:
```asm
name PROC attribute
RET
name ENDP
```
PROC 声明携带的属性(NEAR 或 FAR)告诉汇编器期望使用哪种类型的调用进入过程,即过程是将从同一段中的其他例程调用,还是从其他段中的例程调用。当汇编器在过程中遇到 RET 指令时,会使用属性信息生成近(段内)或远(段间)返回的正确操作码。
每个程序应该有一个主过程,从 MS - DOS 接收控制权。通过在程序的某个源文件的 END 语句中包含主过程的名称来指定程序的入口点。主过程的属性(NEAR 或 FAR)实际上不太重要,因为程序通过函数调用而不是 RET 指令将控制权返回给 MS - DOS。不过,按照惯例,大多数程序员还是将主过程赋予 FAR 属性。
应该以有序的方式将程序的其余部分分解为过程,每个过程执行一个明确定义的单一功能,将结果返回给调用者,并避免在程序中产生全局影响的操作。理想情况下,过程仅通过 CALL 指令相互调用,只有一个入口点和一个出口点,并且始终通过 RET 指令退出,而不是跳转到程序中的其他位置。为了便于理解和维护,一个过程不应超过一页(约 60 行);如果超过一页,可能过于复杂,应该将其部分功能委托给一个或多个辅助过程。应该在每个过程的源代码前加上详细的注释,说明过程的调用序列、返回结果、受影响的寄存器以及访问或修改的任何数据项。在使过程紧凑、简洁、灵活和文档完善方面所投入的努力,在将这些过程重用于其他程序时将得到多次回报。
### 4.2 内存模型
程序根据其代码和数据段的数量被分类为不同的内存模型。汇编语言程序最常用的内存模型是小模型,它有一个代码段和一个数据段,但也可以使用中、紧凑和大模型。
| 模型 | 代码段 | 数据段 |
| ---- | ---- | ---- |
| 小模型 | 一个 | 一个 |
| 中模型 | 多个 | 一个 |
| 紧凑模型 | 一个 | 多个 |
| 大模型 | 多个 | 多个 |
对于每个内存模型,微软为其所有高级语言编译器建立了特定的段和类名称。由于段名称是任意的,不妨采用微软的约定。使用这些约定将使你更容易将汇编语言例程集成到用 C 等语言编写的程序中,或者在汇编语言程序中使用高级语言库中的例程。
另一个重要的微软高级语言约定是使用 GROUP 指令将近数据段(程序期望使用 DS 寄存器的偏移量寻址的段)和堆栈段命名为 DGROUP(自动数据组)的成员,DGROUP 是链接器以及 Microsoft Windows 和 Microsoft OS/2 中的程序加载器识别的特殊名称。GROUP 指令使不同名称的逻辑段组合成一个物理段,以便可以使用相同的段基地址进行寻址。在 C 程序中,DGROUP 还包含局部堆,用于 C 运行时库动态分配少量内存。
对于将在 MS - DOS 下运行的纯汇编语言程序,可以忽略 DGROUP。不过,如果计划将汇编语言例程与用高级语言编写的程序集成,建议遵循微软的 DGROUP 约定。例如,如果计划将 C 库中的例程链接到汇编语言程序中,应该在程序开头附近包含以下行:
```asm
DGROUP group _DATA,STACK
```
创建.EXE 程序时另一个值得关注的微软约定是段顺序。高级编译器假设代码段总是首先出现,然后是远数据段,接着是近数据段,最后是堆栈和堆。在开始将汇编语言代码与高级语言库中的例程集成之前,这个顺序不会对你造成太大影响,但最好从一开始就学会使用这个约定。
## 5. 总结与最佳实践建议
### 5.1 MS - DOS 结构与加载总结
MS - DOS 的结构设计巧妙,通过多个层次将操作系统内核、用户与硬件隔离开,各层次分工明确:
- BIOS 负责提供硬件相关的基本输入输出功能,为系统与硬件的交互奠定基础。
- DOS 内核为应用程序提供了一系列独立于硬件的系统功能,是 MS - DOS 功能实现的核心。
- 命令处理器则是用户与系统交互的桥梁,负责解析和执行用户输入的命令。
在加载过程中,从 ROM 引导例程开始,逐步加载磁盘引导例程、系统文件,进行内核初始化和配置文件处理,最终加载命令解释器,每一步都紧密相连,确保系统能够正常启动和运行。以下是加载过程的关键步骤总结:
| 步骤 | 操作内容 |
| ---- | ---- |
| 1 | ROM 引导例程读取磁盘引导例程到内存并转移控制权 |
| 2 | 磁盘引导例程检查系统文件是否存在 |
| 3 | 加载 IO.SYS 和 MSDOS.SYS 到内存 |
| 4 | SYSINIT 确定内存并重新定位内核 |
| 5 | 初始化 DOS 内核,设置中断向量 |
| 6 | 处理 CONFIG.SYS 文件,加载可安装设备驱动程序 |
| 7 | 重新打开标准设备 |
| 8 | 加载命令解释器(shell) |
### 5.2 .COM 和 .EXE 程序对比与选择
.COM 程序和.EXE 程序各有特点:
- **.COM 程序**:结构简单,加载速度稍快,但大小受限,适用于编写小型、简单的实用程序。在编写.COM 程序时,需要注意起始地址为 0100H,所有代码和数据混合在一个段中,并且在使用内存时要谨慎,避免出现内存管理问题。
- **.EXE 程序**:大小几乎不受限制,代码、数据和堆栈可以放在不同的段中,更适合编写大型、复杂的程序,尤其是在多任务环境中具有优势。在编写.EXE 程序时,要确保正确声明代码段、数据段和堆栈段,合理设置段属性和入口点。
两者的区别总结如下:
| 比较项 | .COM 程序 | .EXE 程序 |
| ---- | ---- | ---- |
| 最大大小 | 65,536 字节减去 256 字节(PSP)和 2 字节(堆栈) | 无限制 |
| 入口点 | PSP:0100H | 由 END 语句定义 |
| 段寄存器初始值 | 所有段寄存器指向 PSP | 根据文件头和声明设置 |
| 堆栈 | 简单,初始为零字 | 可初始化或未初始化,大小由声明确定 |
| 子程序调用 | 通常为近调用 | 近调用或远调用 |
### 5.3 汇编语言程序编写最佳实践
在编写汇编语言程序时,遵循以下最佳实践可以提高程序的质量和可维护性:
- **模块化设计**:将程序分解为多个模块,每个模块实现一个特定的功能。使用模块和目标模块库可以减小源文件的大小,提高开发效率,并且便于维护和升级。
- **合理使用段**:理解物理段和逻辑段的概念,根据程序的大小和需求合理声明代码段、数据段和堆栈段。对于.EXE 程序,至少声明三个逻辑段,将常用和不常用的代码、数据分别放在不同的段中,以提高程序的性能。
- **过程化编程**:将程序分解为多个过程,每个过程执行一个明确定义的单一功能。过程应具有良好的封装性,避免产生全局影响的操作。过程的长度不宜过长,最好不超过一页(约 60 行),并在代码前加上详细的注释,说明调用序列、返回结果、受影响的寄存器和访问修改的数据项。
- **遵循微软约定**:在编写与高级语言集成的汇编语言程序时,遵循微软的 DGROUP 约定和段顺序约定,这样可以使程序更容易与其他语言的代码进行交互和集成。
### 5.4 未来展望
虽然 MS - DOS 已经逐渐退出了主流操作系统的舞台,但它的设计理念和技术对现代操作系统的发展产生了深远的影响。在学习 MS - DOS 的过程中,我们可以深入理解操作系统的基本原理,如内核设计、内存管理、设备驱动等。同时,汇编语言作为一种底层编程语言,对于理解计算机硬件和系统的工作原理具有重要意义。随着计算机技术的不断发展,我们可以将从 MS - DOS 和汇编语言学习中获得的知识应用到新的领域,如嵌入式系统开发、操作系统内核优化等。
```mermaid
graph LR
A[MS - DOS 结构与加载] --> B[.COM 和 .EXE 程序选择]
B --> C[汇编语言程序编写最佳实践]
C --> D[未来应用与发展]
```
总之,通过对 MS - DOS 操作、应用程序结构和汇编语言编程的深入学习,我们可以掌握计算机系统底层的工作原理,为进一步学习和开发打下坚实的基础。在实际应用中,根据具体需求选择合适的程序类型和编程方法,遵循最佳实践,不断提高自己的编程能力和技术水平。
0
0
复制全文
相关推荐









