跳转至内容

MS-DOS 7 系统编程入门/可执行文件组成示例

来自维基教科书,开放的书籍,为开放的世界

第 9 章 可执行文件组成示例

本章第 9 章中介绍的所有可解释、可执行和配置文件示例,已在多台不同计算机上成功测试,处理器从 486SL(1992 年)到 Pentium-D(2006 年)。但是,无法预见一切。无论如何,所提供的示例应被视为可取的方案,任何人都可以从中选择与个人相关的解决方案。它们的适用性留待您自行决定和实施。

尽管个人问题可能会有所不同,但对于所有敢于深入低级编程的人来说,一些基本的运行习惯是共通的。首先,如何将书籍中打印的程序文本转换为可执行文件。为此,您必须启动一个编辑器程序,它能够保存非格式化文本文件。最广为人知的 WORD 和 WORDPAD 程序不适合,但 NOTEPAD 和 EDIT.COM 非常合适。但是,在 MS-DOS 中,任何国家(非美国)语言注释只能通过 EDIT.COM 编辑器(6.09)插入,并且前提是事先加载了国家语言适配驱动程序(9.01 和 9.04 文章中的示例)。

当编辑器程序启动后,菜单 FILE 中的 NEW 项允许您打开一个新的空窗口,您可以在其中写入任何想要的文本。本书中介绍的可执行文件的文本通常应该从每行的左侧边界开始逐行输入。然后,您应该通过菜单 FILE 中的 SAVE AS 命令将文本保存到文件中。SAVE AS 命令建议您为新文件指定一个名称和一个后缀。每个文件都应该只使用反映文件状态的后缀(2.01-02):BAT - 用于批处理文件,SCR - 用于命令文件,由 DEBUG.EXE 执行,SYS、MNU 或 EXT 后缀 - 用于各种配置文件。

较长的文件可以保存多次:第一次通过 SAVE AS 命令,之后通过 SAVE 命令。如果需要即时修改文本,则可以单独测试未完成文件的各个部分,如 9.07-02 文章所示。当一个包含指定名称的文本文件被完全输入、检查和保存时,编辑器程序就完成了它的任务。

如果没有特别说明,本章中介绍的所有程序示例的成功执行或解释,都意味着满足以下正常条件 

  • 当前命令解释器(COMMAND.COM 或其他)的名称及其前面完整的路径必须由环境变量 %COMSPEC% 的值定义;
  • 属性 H(隐藏)和 S(系统)不应分配给要由被测试程序调用或引用的文件或实用程序(9.11-02 的注 1);
  • 要由被测试程序调用或引用的所有文件或实用程序的路径必须在环境变量 PATH 的值中指定(2.02-02);
  • 在 MS-DOS 7 下不合适的同义实用程序,不得存在于当前目录和环境变量 PATH 值中指定的所有路径中(2.02-02);
  • TEMP 环境变量的值必须指定一个现有目录的路径,该目录用于临时文件,位于可写入的介质上,并具有足够的空间用于此目的。

请注意,%TEMP% 目录的状态意味着该目录中的任何文件都可能被删除或覆盖。可以使用 SET 命令(3.26)检查环境变量的当前值。环境变量的原始值以及其他执行条件应在配置文件中定义。9.04、9.09 文章中显示了此类定义的各种示例。但从下一篇文章 9.01 中介绍的最简单的配置文件开始是明智之举。

9.01 简单配置文件

[编辑 | 编辑源代码]

MS-DOS 7 加载过程是可配置的,并可能导致不同的结果。IO.SYS 加载程序接受来自 MSDOS.SYS 文件(5.01-01)的加载参数,并考虑引导磁盘根目录中是否存在主要的 DOS 文件。9.11-02 文章中描述了根目录中应该存在的 DOS 文件的完整列表。在这其中,有两个可选但非常重要的文件:CONFIG.SYS 和 AUTOEXEC.BAT。正是这两个文件定义了最终 DOS 配置的主要功能。尽管 MS-DOS 7 能够在没有这两个文件的情况下加载自身,但隐式默认配置过于贫乏,不能被认为是足够的。

MS-DOS 7 可以变得有效、方便和友好,但用户必须为此费心。在现代计算机中,即使是简单的配置也必须包含扩展内存驱动程序、"鼠标" 指针设备驱动程序和 CD-ROM 驱动程序。没有合适的文件管理器,DOS 就不方便。本书中介绍的配置文件示例也加载代码页,从而可以使用美国语言和一些国家(非美国)语言。

简单版本的配置文件可用于从可移动介质加载 MS-DOS 7,但最适合用于在 HDD 上安装 MS-DOS 7:无论是格式化后进行临时安装,还是作为几种可选操作系统中的一种可能的选项进行永久安装(9.11-02 中的示例)。

9.01-01 CONFIG.SYS 文件的简单版本

[编辑 | 编辑源代码]

CONFIG.SYS 文件是一个可解释的命令文件:它的每一行都是一个命令。术语“可解释”意味着命令不是直接由 CPU 执行,而是通过一个中介程序 - 命令解释器程序来执行。CONFIG.SYS 命令解释的任务由 IO.SYS 加载程序负责。

所展示的 CONFIG.SYS 文件示例明确地显示了所有应加载的驱动程序,以及它们加载的优先顺序。驱动程序预计位于 \DOS\DRV 目录中。如果您打算使用其他目录结构,则必须相应地更正路径说明。请注意,路径显示不包含磁盘的字母名称。这种说明适用于从任何可启动磁盘加载。

device=\DOS\DRV\Himem.sys
device=\DOS\DRV\Emm386.exe ram v
dos=high,umb,noauto
buffershigh=20,0
fileshigh=30
lastdrivehigh=Z
fcbshigh=1,0
stackshigh=9,256
numlock off
country=007,866,\DOS\DRV\Country.sys
devicehigh=\DOS\DRV\Dblbuff.sys
devicehigh=\DOS\DRV\Ifshlp.sys
devicehigh=\DOS\DRV\Setver.exe
devicehigh=\DOS\DRV\Display.sys con=(ega,,1)
devicehigh=\DOS\DRV\Oakcdrom.sys /D:CD001
installhigh=\DOS\DRV\Mscdex.exe /D:CD001 /E /L:O /M:13
installhigh=\DOS\DRV\Mouse.com
shell=\Command.com \ /E:2016 /L:511 /U:255 /p

有关所有指定命令和驱动程序的帮助信息,请参见第 4 章(“配置命令”)和第 5 章(“选定驱动程序”)。大多数驱动程序可以从 WINDOWS-95/98 操作系统的 \WINDOWS 和 \WINDOWS\COMMAND 目录中获取,除了来自 MS-DOS 6.22 版本的 MOUSE.COM(5.03-02)和来自 WINDOWS-95/98 救援软盘的 OAKCDROM.SYS(5.09-01)。还有很多其他的鼠标和 CD-ROM 驱动程序(GMOUSE.COM、VIDE-CDD.SYS、ECSCDIDE.SYS 等),它们可以与各种设备型号配合使用,并且可以在这里代替 MOUSE.COM 和 OAKCDROM.SYS。

CONFIG.SYS 文件中各行顺序必须符合以下一般规则:提供某些支持的驱动程序必须在其支持需求出现之前加载。上层内存驱动程序(HIMEM.SYS 然后是 EMM386.EXE)必须使用 DEVICE 命令加载,然后才能对 DEVICEHIGH 和 INSTALLHIGH 命令使用上层内存访问。用于加载可执行驱动程序的 INSTALL 和 INSTALLHIGH 命令必须放置在所有 DEVICE 和 DEVICEHIGH 命令之后,但在 SHELL 命令之前。SETVER.EXE 驱动程序必须加载在任何其他需要欺骗 DOS 版本号的驱动程序之前。虽然在所示的 CONFIG.SYS 文件示例中没有这样的驱动程序,但加载 SETVER.EXE 提供了从早期版本的 DOS 执行后期版本特定实用程序(PRINT.EXE、QBASIC.EXE、TREE.COM 等)的机会。

应特别注意第 3 行中 DOS 命令的 NOAUTO 参数:它取消了某些驱动程序(DBLBUFF.SYS、DRVSPACE.BIN、HIMEM.SYS、IFSHLP.SYS)的默认加载,以及沿着默认路径搜索这些驱动程序。实际上,NOAUTO 参数使您可以将 MS-DOS 7 作为独立的操作系统使用。

请注意,CONFIG.SYS 文件最后一行中 COMMAND.COM 文件的路径已简化为一个单斜杠“\”。这足以在根目录中找到 COMMAND.COM 文件,但不足以在 COMSPEC 环境变量的值中正确定义 COMMAND.COM 文件的路径。当然,可以在 CONFIG.SYS 文件中指定特定的磁盘字母名称。这种示例在文章 4.26 和 6.04 中有展示。但是,在实践中,每次您需要从其他磁盘启动 PC 时,在多个地方交换磁盘字母名称并不方便。因此,这里对特定磁盘字母名称的定义推迟到执行最后一个配置文件 AUTOEXEC.BAT(9.01-02)时进行。推迟的磁盘字母名称分配使您能够在一个地方更正字母名称,并提供自动磁盘字母名称确定的机会(9.01-03 和 9.09-02 中的示例)。

9.01-02 AUTOEXEC.BAT 文件的简单版本

[编辑 | 编辑源代码]

由于 IO.SYS 加载程序的功能有限,因此某些配置操作无法在 CONFIG.SYS 文件中以命令的形式表达。这些操作构成了另一个配置文件 - AUTOEXEC.BAT。它也是一个可解释的命令文件,但它的任务是利用更强大的命令解释器 - COMMAND.COM - 的功能来执行许多最终配置操作。

所展示的 AUTOEXEC.BAT 文件版本意味着可启动磁盘上存在目录结构:\TEMP 目录用于临时文件,\DOS 目录及其子目录 \DOS\OTH\DOS\MS7\DOS\VC4\DOS\DRV。这种结构既适用于软盘也适用于固定磁盘,但是如果您打算使用任何其他结构,则必须相应地更改所有路径和引用。该文件专为从 C: 磁盘启动而设计。如果要用于从其他磁盘加载,则必须将第二行中对 C: 磁盘的引用替换为对将实际使用的磁盘的引用。请注意,这是一个必须更正的单个引用;所有其他对实际磁盘的引用将自动交换。

以下是建议的 AUTOEXEC.BAT 的简单版本

@echo off
set dsk=C:
set comspec=%dsk%\Command.com
if not exist TEMP\nul %comspec% nul /f /c md TEMP
if exist TEMP\nul set Temp=%dsk%\TEMP
prompt $p$g
set dircmd= /A /O:GNE /P
path ;
path=%dsk%\DOS\VC4;%dsk%\DOS\OTH;%dsk%\DOS\MS7;%dsk%\
Mode.com con codepage prepare=((866) %dsk%\DOS\DRV\Ega3.cpi)
Mode.com con codepage select=866
Keyb.com ru,866,%dsk%\DOS\DRV\Keybrd3.sys
set VC=%dsk%\DOS\VC4
Vc.com /TSR /no2E /noswap

所示文件第一行中的命令关闭回显标志,就像在普通批处理文件中一样。第二行指定当前磁盘的字母名称,并将其作为 DSK 环境变量的值。请注意,第二行末尾不能有空格。以下各行中对 DSK 变量的多次引用将当前磁盘的字母名称插入所有相关的路径中。这使您只需指定一次磁盘的字母名称。第三行将正确的值分配给 COMSPEC 环境变量,该变量在 CONFIG.SYS 文件解释期间没有获得适当的值。从这一刻起,MS-DOS 7 就可以更改当前磁盘。

第 4 行和第 5 行处理用于临时文件的 TEMP 目录。首先,检查此目录是否存在。如果不存在,则尝试创建 TEMP 目录。然后再次检查其是否存在,如果成功,则将 TEMP 目录的路径作为 TEMP 环境变量的值分配。此过程保证任何可写磁盘上的临时文件都有一个有效的路径。另一方面,如果此过程之后没有 TEMP 变量,则肯定会表明当前磁盘不可写。

以下操作组为其他变量分配值:DIRCMD、PROMPT、PATH。所示值应被视为示例,需要根据磁盘上实际的目录结构进行更正。假定 PATH 变量的值(2.02-02)包含以下各行中指定的所有实用程序的实际路径:MODE.COM、KEYB.COM 和 VC.EXE,以及您可能希望补充的其他路径。

执行 MODE.COMKEYB.COM 会为显示器激活所需的国家代码页以及相应的键盘布局。如果您需要的不是俄语代码页 866,则可以更改它,但事先应该在表 A.02-2 中检查关联的数据表(EGA3.CPIKEYBRD3.SYS)是否包含您需要的数据。如果不是,则这些数据表也必须更改。当然,选择第 437(美国)代码页可以使您免于省略所有包含 MODE.COMKEYB.SYS 的行,因为此代码页默认激活。

AUTOEXEC.BAT 文件中的最后两行用于启动 VC.COM - Volkov Commander 文件管理器(6.25)。其他文件管理器,例如 Norton Commander(NC.EXE)和 Dos Navigator(DN.EXE)可以以类似的方式启动。如果您不打算使用文件管理器,则所示的最后两行不需要。

9.01-03 自动确定磁盘的字母名称

[编辑 | 编辑源代码]

如果有一组自适应配置文件,可以从任何磁盘正确加载 MS-DOS 7,而无需手动更正磁盘的字母名称,那就很方便了。如果您已经组装了第 9.06 部分中提出的 REASSIGN.COM 实用程序,或者您有其他功能等效的实用程序,则可以轻松实现此功能。您只需要将 AUTOEXEC.BAT 文件(9.01-02)中第二行中的固定分配(“set dsk=C:”)替换为以下两行,执行磁盘字母名称的自动确定

set dsk=33
\DOS\OTH\Reassign.com dsk

当然,路径示例("\DOS\OTH\") 必须根据实际可以找到实用程序的目录进行更改,如果需要。执行完这些命令后,当前磁盘的字母名称将成为 DSK 变量的值,然后所有其他相关的规范将自动更正,如文章 9.01-02 中所示。磁盘字母名称的自动确定并不一定需要特殊的实用程序;它可以通过标准的 MS-DOS 方法实现。但是,这种想法的实现并不简单,而且还需要访问可写磁盘。因此,这里展示的 AUTOEXEC.BAT 文件版本不适合从 CD-ROM 加载 MS-DOS 7,但它可以用作,实际上已经用于在格式化后将 MS-DOS 7 单层重定位到硬盘上。

@echo off
if exist ..\nul goto L19
prompt=@echo off$_Set dsk$q$N:$_goto L7
%comspec% /f /c $.bat > $.bat
type Autoexec.bat >> $.bat
for %%Z in ("del A" "ren $.bat A" "A") do %%Zutoexec.bat
:L7
set comspec=%dsk%\Command.com
set Temp=%dsk%\TEMP
if not exist %Temp%\nul md %Temp%
prompt $p$g
set dircmd= /A /O:GNE /P
path ;
path=%dsk%\DOS\VC4;%dsk%\DOS\OTH;%dsk%\DOS\MS7;%dsk%\
Mode.com con codepage prepare=((866) %dsk%\DOS\DRV\Ega3.cpi)
Mode.com con codepage select=866
Keyb.com ru,866,%dsk%\DOS\DRV\Keybrd3.sys
set VC=%dsk%\DOS\VC4
Vc.com /TSR /no2E /noswap
:L19

此版本 AUTOEXEC.BAT 文件中的第 8 行到第 18 行执行第 9.01-02 部分中描述的普通功能。但是第 2 行到第 6 行以及标签是特定于此版本的。为了方便查找行,标签名称中的数字代表相应行的序号。

第 2 行检查当前目录的父目录是否存在。如果存在,则当前目录不能是磁盘的根目录,然后我们通过 L19 标签退出。因此,该文件在存储在目录结构中的任何位置时都将不执行任何操作。它在移动到磁盘的根目录后才变得功能化:然后父目录不存在,第 2 行中的检查允许继续执行下一行。

在接下来的第 3 行中,PROMPT 命令(3.22)设置了一个新的提示符,该提示符仅在第 4 行中启动命令解释器时发出一次。在该命令解释器中,正式执行一个空文件 $.BAT,该文件在准备同一行中的重定向时由 DOS 创建。结果将写入相同的 $.BAT 文件。由于它最初是空的,所以它只会填充新的提示符。假设当前磁盘是 D:;则 $.BAT 文件的内容将如下所示

@echo off
Set dsk=D:
goto L7

请注意,$.BAT 文件第二行中的字母名称 D: 之前没有预先设置,它是 DOS 返回的命令提示符的元素,即实际的当前磁盘字母名称。

以下第 5 行中的 TYPE 命令读取 AUTOEXEC.BAT 文件,并通过输出重定向将其副本附加到 $.BAT 文件中显示的三个原始行。

AUTOEXEC.BAT 文件第 6 行的 FOR 循环通过依次替换虚拟参数 %%Z 的三个不同值来执行三个操作。第一次替换生成 del AUTOEXEC.BAT 命令,AUTOEXEC.BAT 文件将不再存在。然后第二次替换生成命令 ren $.BAT AUTOEXEC.BAT,之前的 $.BAT 文件将被重命名为 AUTOEXEC.BAT。第三次替换生成 AUTOEXEC.BAT,即执行新文件 AUTOEXEC.BAT 从其第一行开始的命令。

由于此新文件的名称前面没有 CALL 命令(3.02),因此不会返回到已执行的以前批处理文件,该文件当前尚不存在。请注意,除非所有替换操作都写在一行中,否则无法执行当前执行文件的替换。

新的 AUTOEXEC.BAT 文件以之前 $.BAT 文件的这三行开头,这些行已在上面显示。在执行过程中,当前磁盘的字母名称将作为值分配给 DSK 环境变量,并执行无条件跳转到标签 L7。因此,包含自修改操作的一组行将永远被绕过。从标签 L7 开始,将执行 AUTOEXEC.BAT 文件的普通操作。在执行过程中,找到的当前磁盘的字母名称将自动插入到必须指定它的所有路径中。

9.02 由调试器 DEBUG.EXE 解释的命令文件

[编辑 | 编辑源代码]

9.02-01 引导扇区保存和恢复

[编辑 | 编辑源代码]

在每个逻辑磁盘中,第一个扇区是引导扇区。它包含参数块 BPB(A.03-4)、可执行代码部分以及应提供控制以加载操作系统的这些文件的规范。每次格式化磁盘时,引导扇区都会由 FORMAT.COM 实用程序(6.15)写入,并且当磁盘变为可引导时,可以由 SYS.COM 实用程序(6.24)重新写入。一些特殊程序,例如 DDO(动态驱动程序叠加),使用非标准引导扇区配置,这些配置无法由 MS-DOS 7 提供的实用程序恢复。您可能需要将引导扇区保存到文件中,以便在偶尔的数据失真、病毒感染或操作系统安装过程中覆盖后(文章 9.11-02 中的示例)稍后恢复它。虽然主命令解释器 - COMMAND.COM - 不提供对引导扇区的访问权限,但对于调试器实用程序 DEBUG.EXE 而言,引导扇区保存是一项简单的任务,完全可以通过调试器的内部命令解决。以下是一个命令文件的示例,该命令文件会诱使 DEBUG.EXE 将引导扇区从磁盘 C:复制到文件中

L CS:100 2 0 1
r BX
0000
r CX
200
n Bootsect.dat
w CS:100
q

第一行是将引导扇区从磁盘 C:读取并将其副本写入从地址 CS:100 开始的内存中的命令。第 2-5 行指定应复制到文件中的数据区域的长度(00000200h)。第 6 行指定要创建的文件的名称。第 7 行导致数据从内存复制到文件。最后一个命令(“Q”)终止调试器会话。

显示的命令文件的内容应在编辑器程序窗口(NOTEPAD 或 EDIT.COM)中键入,并以任何合适的名称(不妨命名为 SAVEBOOT.SCR)保存在当前目录中。然后,应通过命令 DEBUG.EXE < SAVEBOOT.SCR 将此文件发送到调试器。

执行的结果是一个 512 字节的文件 BOOTSECT.DAT,出现在当前目录中。该文件是磁盘 C:上引导扇区的精确镜像。您可以通过类似的方式(6.05-10)从任何其他有效逻辑磁盘读取引导扇区。例如,要访问磁盘 A:,您必须将 SAVEBOOT.SCR 中的第一行替换为以下内容:

L CS:100 0 0 1

当然,除非可移动介质中有有效可移动介质,否则您无法访问可移动介质的驱动器。

将引导扇区写回磁盘的反向操作由一个更简单的命令文件执行

n Bootsect.dat
L CS:100
w CS:100 2 0 1
q

这里第一行宣布包含引导扇区映像的文件的名称;该文件必须存在于当前目录中。第二行从该文件读取扇区数据到内存,第三行将扇区数据从内存写回其原始磁盘(在本例中为磁盘 C:)。显示的命令文件的内容应在编辑器程序窗口中键入,然后保存到任何合适名称的文件中,例如 RESTBOOT.SCR。由于 RESTBOOT.SCR 的解释意味着直接访问磁盘,因此必须在 DOS 下完成此操作,而不是在 WINDOWS 操作系统下的“DOS 框”中完成。当命令文件 RESTBOOT.SCR 通过输入重定向发送到其解释器时,将发生引导扇区的恢复

DEBUG.EXE < RESTBOOT.SCR

使用所描述的命令文件,引导扇区恢复成为一项简单的操作。然而,此操作不应用于引导扇区的移植。即使在相同格式的磁盘之间移植也会引发一些关于其序列号和卷标唯一性的问题,因为后者在根目录中重复。通常,每个引导扇区都是唯一的,并且仅适用于其原始磁盘,而不是其他磁盘。

9.02-02 将主引导记录复制到文件中

[编辑 | 编辑源代码]

每次您打开计算机时,它都会从其 HDD(硬盘驱动器)加载已安装的操作系统。此普通过程包括几个阶段,其中一个重要的阶段是执行主引导记录 (MBR),它指定 HDD 的结构并进一步指导加载过程。与磁盘上的任何其他记录一样,MBR 也会因自然退化而损坏,它可能会因偶然的故障或病毒而损坏。在任何此类情况下,您都必须从恢复磁盘或紧急软盘启动计算机,以恢复 MBR。

对于 Windows-95/98/ME 操作系统,您可以尝试使用 FDISK.EXE 实用程序(6.13)恢复 MBR,但它无法恢复分区表,分区表与 MBR 一起写入同一个扇区(A.13-5)。一些其他操作系统、DDO(动态驱动程序叠加)和引导管理器使用的 MBR 的特定形式也无法通过 FDISK.EXE 恢复。

同时,存在一个简单且通用的解决方案:将 MBR 的副本与分区表一起存储在可移动介质(光盘或软盘)上的文件中。然后,您将能够在需要时从该文件恢复 MBR 和分区表。

MS-DOS 7 没有提供复制 MBR 的特殊方法,但提供调试器 DEBUG.EXE,它使您能够编写任何一系列机器命令,并且可以立即执行这些命令。本文介绍了一个包含一系列命令的命令文件,这些命令会诱使 DEBUG.EXE 将 MBR 复制到文件中。此命令文件的内容如下

a 100
mov          DX,0080       ;prepare access to head 00h of HDD 80h
mov          CX,0001       ;specify start at cylinder 00h, sector 01h
mov          BX,0200       ;load BX with offset 0200h for data buffer
mov          AX,0201       ;specify function 02 of INT13, copy 1 sector
int          13            ;call INT13 handler, copy sector to buffer
mov          [01F6],AH     ;save exit code in memory cell 01F6h
ret                        ;quit execution of "G=100" instruction
org          130           ;write next commands from cell 130 and on
mov          AL,[01F6]     ;copy exit code into AL register
mov          AH,4C         ;specify termination function 4C (8.02-55)
int          21            ;call INT21 handler, terminate session

g =100
n Mbr.dat
r CX
0200
r BX
0000
w CS:0200
d 01F6,L1
g =130

建议的命令应在编辑器程序的窗口中键入,如第 9 章的介绍文章中所述。分号右侧的注释不会由 DEBUG.EXE 执行,因此可以省略。请注意 "INT 21" 和 "{{{1}}}" 命令之间的空行:这一点很重要(7.01-04),并且必须存在。然后,文本应保存在文件中,该文件可以命名为,例如 READ_MBR.SCR。

如果您的计算机配置为不是从逻辑磁盘 C:启动,而是从任何其他逻辑磁盘(D:、E:或其他)启动,那么您必须检查实际的可引导物理 HDD 的数量,例如,通过命令 FDISK.EXE /status

从显示的表格中,您将能够确定可引导物理 HDD 的数量;如果它不是 1 号,则 READ_MBR.SCR 文件第二行中的 HDD 代码 80h 必须更正。如果可引导驱动器是 HDD 2 号,则应指定 81h HDD 代码,如果可引导驱动器是 HDD 3 号,则应指定 82h HDD 代码,依此类推。

现在是时候解释 READ_MBR.SCR 的工作原理了。它的第一行“A 100”将 DEBUG.EXE 切换到汇编程序模式,以便从地址 CS:0100h 开始将机器命令的代码写入内存。当 DEBUG.EXE 保持在汇编程序模式时,它允许在分号后插入注释,因此第 2-12 行中命令的作用从这些注释中可以明显看出。有关每个命令的更详细的信息,请参阅第 7 章。准备了两组机器代码:一组从内存单元 100h 开始,另一组由第 9 行中的 ORG 命令宣布,从内存单元 130h 开始。命令到机器代码的转换将一直持续到遇到空行 13:

第 14 行中的命令“G”启动从地址 CS:0100h 执行第一个准备好的机器代码序列。在执行过程中,MBR 与分区表一起从 HDD 读取,并从单元 0200h 开始写入内存。读取操作的退出代码暂时存储在任意选择的空闲内存单元 01F6h 中。第一个准备好的机器代码序列的执行由第 8 行中的 RET 命令(7.03-73)完成,该命令将控制权返回到调试器 DEBUG.EXE。

DEBUG.EXE 从第 15 行中的命令“N”(6.05-12)继续其工作。它为要创建的文件准备名称(MBR.DAT)。您可以指定其他名称或添加前缀路径,如果文件要创建在当前目录以外的其他地方。第 16-19 行中的命令指定要创建的文件的长度(00000200h)。第 20 行中的命令“W”(6.05-19)从地址 CS:0200h 开始从内存读取数据,并将这些数据写入到文件中,该文件事先指定了它的长度和名称(MBR.DAT)。第 21 行中的命令“D”(6.05-04)显示存储在内存单元 01F6h 中的读取操作退出代码。

第 22 行中的最后一个命令“G =130”启动执行第二个准备好的机器代码序列。读取操作的退出代码从内存单元 01F6h 复制到 AL 寄存器中,然后 DOS 的 INT 21\AH=4Ch 函数(8.02-55)终止调试器会话。同时,来自 AL 寄存器的退出代码将成为由终止的调试器会话留下的错误级别值,以便以后可以使用条件“if errorlevel”命令(3.15-03)检查它。

在解释命令文件 READ_MBR.SCR 之前,您需要确保它存在于可写介质上的当前目录中。由于 MBR 将被保存到名为 MBR.DAT 的文件中,因此应从当前目录中删除任何同名文件,否则它将被覆盖而不会提示。完成准备检查后,您可以将 READ_MBR.SCR 发送到调试器进行解释。

DEBUG.EXE < READ_MBR.SCR

在解释过程中,将出现一条消息“程序正常终止”,但这仅意味着 DEBUG.EXE 在执行第一组机器代码后已成功获得控制权。MBR 读取尝试的结果由退出代码表示 - 一个十六进制数字,显示在屏幕的倒数第二行。非零退出代码表示 MBR 读取尝试失败。在这种情况下,将创建一个 MBR.DAT 文件,其中仅包含垃圾数据。在“如果错误级别为 1”的情况下(3.15-03),它应该被自动删除。根据表 A.06-1 解释非零退出代码可能有助于揭示失败的原因。

退出代码值为 00h 表示 MBR 读取尝试成功。在这种情况下,文件 MBR.DAT 包含 MBR 的副本以及磁盘的分区表。MBR.DAT 文件转储的示例显示在图 12 中(在文章 A.13-5 中)。

9.02-03 主引导记录 (MBR) 的恢复。

[编辑 | 编辑源代码]

MBR 故障是一个相对罕见的事件,但没有人能保证避免它。一旦类似的事情发生在你身上,你必须测试并排除其他假设:错误的 BIOS 设置、CMOS 内存故障、磁盘表面损坏、引导扇区覆盖等。最令人信服的实验是将当前 MBR 的镜像保存到一个文件中,如前一篇文章 9.02-02 中所示,并将其与之前保存在另一个文件中的原始 MBR 镜像进行比较。比较可以使用 FC.EXE 实用程序(6.12)进行。如果文件不同,则必须重写或恢复 MBR。

MS-DOS 7 没有提供从文件将 MBR 镜像写入硬盘的特殊方法,但您可以准备一个命令文件,它将强制 DEBUG.EXE 完成这项工作。假设命令文件名为 WriteMBR.SCR,原始 MBR 镜像的副本名为 MBR.DAT,并且这两个文件都存在于当前目录中。WriteMBR.SCR 的内容可能如下所示。

A 100
mov          DX,0080       ;prepare to access head 00h, hard disk 80h
mov          CX,0001       ;specify start at cylinder 00h, sector 01h
mov          BX,0200       ;load BX with offset 0200 of data buffer
mov          AX,0301       ;specify INT13 function 03h, write 1 sector
int          13            ;call INT13 handler, write data into sector
mov          [01F6],AH     ;save exit code in a memory cell
ret                        ;quit execution of DEBUG's "G" instruction

N MBR.DAT
L CS:0200
G =0100
d 01F6,L1
q

此命令文件第一行中的“A 100”命令将 DEBUG.EXE 切换到汇编模式,以便以下第 2-8 行中的命令不会立即执行,而是被翻译成机器代码,这些机器代码被写入从 CS:0100h 开始的内存单元中。当 DEBUG.EXE 保持在汇编模式时,它允许用注释提供每一行。因此,第 2-8 行中命令的任务从命令文件本身就很清楚。第 9 行的空行强制 DEBUG.EXE 退出汇编模式。然后第 10 行中的“N”命令(6.05-12)指定要加载的文件的名称,第 11 行中的“L”命令(6.05-10)将 MBR 镜像从 MBR.DAT 文件加载到从地址 CS:0200h 开始的内存中。

第 12 行中的“G”命令(6.05-07)启动从地址 CS:0100h 开始准备好的机器代码的执行。执行继续进行,直到在第 8 行中遇到 RET 命令(7.03-73),该命令将控制权返回给 DEBUG.EXE。然后,WriteMBR.SCR 文件中命令的执行从第 13 行继续进行,其中包含“D 01F6,L1”命令,该命令显示执行 INT13\AH=03h 写入函数后留下的退出代码。最后一行 14 中的“Q”命令终止调试器的会话。

当然,如果您的可引导磁盘不是第一个物理 HDD,您必须更改 WriteMBR.SCR 文件第 2 行中物理磁盘的代码,就像前一篇文章 9.02-02 中所描述的那样。当您确定物理磁盘的代码已正确指定,MBR 需要恢复,所有必要的文件已准备就绪并位于当前目录中时,您可以使用以下命令发送 WriteMBR.SCR 文件以执行。

DEBUG.EXE < WriteMBR.SCR

执行后,屏幕上将显示一个十六进制退出代码。任何退出代码的解释都可以在表 A.06-1 中找到。退出代码 00h 表示 MBR 已成功恢复。

注意 1:为了确保自己,不要在当前正在使用的 HDD 上执行 WriteMBR.SCR 实验!这会导致无法修复的数据丢失!您只能对那些不包含任何需要保存的内容的 HDD(无论是新的还是旧的)进行实验。

注意 2:WriteMBR.SCR 文件需要直接访问磁盘。因此,MBR 恢复不应在 WINDOWS 操作系统下的“DOS 盒”中执行,而应在真正的 MS-DOS 7 下执行。

9.03 批处理文件的示例

[编辑 | 编辑源代码]

COMMAND.COM 解释器通过重定向接受普通命令文件,就像 DEBUG.EXE 一样(9.02)。但批处理文件代表一种特殊的命令文件类,COMMAND.COM 仅从命令行识别和接受它们,而无需重定向。此外,在批处理文件中,COMMAND.COM 可以执行几个重要的命令(3.02、3.14、3.21、3.27),这些命令不能在普通命令文件中或从命令行执行。由于这些原因,COMMAND.COM 解释器的各种命令文件最终被限制在批处理文件类中。

本书中最简单的批处理文件是 AUTOEXEC.BAT 文件,它在文章 9.01-02 中介绍。以下文章介绍了一些不太简单的批处理文件示例。这些示例演示了批处理编程的技术,这些技术可能远远超出所显示批处理文件的功能。

9.03-01 用于归档的批处理文件 ARC.BAT

[编辑 | 编辑源代码]

根据计算机术语,归档意味着将多个文件压缩成一个组合的文件档案。这个含义来自很久以前,那时计算机不可靠,人员必须将多个文件保存为一个组合的数据流,写入磁带介质上。从那时起,大多数东西都发生了演变,但对归档的兴趣并没有消失。归档减少了磁盘簇中可用空间的损失,使复制和碎片整理速度更快。文件大小压缩对于通信网络中的有限传输速度至关重要。将多个文件打包成一个档案非常方便,并且广泛用于交付大型程序产品。

已知的归档算法很多,但只有两种算法——ZIP 和 RAR——获得了广泛的实际认可。ZIP 算法由 Phil Katz 在 1990 年代初开发,它不提供最大的压缩,但具有其他两个优点:速度和兼容性。由原始 ZIP 算法打包的档案可以被许多其他归档程序解压缩。打包和解压缩 ZIP 档案的程序可以从以下网站下载,例如 http://comp.site3k.net/?/comp/pkzip.html

RAR 算法由 Eugene Roshal 在 1990 年代中期开发,它提供更好的压缩,并且能够修复具有内部恢复记录的部分损坏的档案。但 WINRAR 程序的新版本(针对 WINDOWS 操作系统)创建的档案无法由早期版本的 RAR 归档器解压缩。这种不兼容性可能会让你在与收件人交流时失望。因此,为了形成 RAR 档案,应该优先考虑非最新但足够好的 RAR.EXE 程序的免费版本 2.50(日期为 1999 年)。这个版本的 RAR 归档器可以从互联网下载,例如,从网站 http://dosprogram.narod.ru/arc/index.html 下载。

为了使档案的使用更方便,Volkov Commander 文件管理器使您可以用鼠标按钮单击进入档案内部,并将它们的内容几乎像普通目录的内容一样对待(详情请参见文章 6.25-04)。从文件管理器的菜单创建档案也比从命令行更方便。但文件管理器无法阻止用户在准备归档数据时犯错误;此外,有时找到故障原因会变得更加困难。因此,出现了编写一个命令文件的想法,该命令文件将检查从 Volkov Commander 文件管理器传递到归档程序的数据。这个想法是在一个批处理文件中实现的,即一个针对 COMMAND.COM 解释器的命令文件。ARC.BAT 批处理文件阻止了归档器的失败,ARC.BAT 发送到显示的错误消息有助于找出每个错误的原因。

随着时间的推移,ARC.BAT 文件中的检查数量增加到七个。当需要决定哪个批处理文件应该作为有用且相对简单的示例来展示时,ARC.BAT 文件被认为是最合适的。

ARC.BAT 文件的原理是基于这样的假设:在文件管理器的活动面板中,用户用鼠标右键选择一组要包含到新档案中的文件,然后用鼠标左键突出显示要分配给新档案的文件名。在图 5 中显示了此类文件选择的示例(在文章 6.25-01 中)。之后,只需在相应的菜单项上单击鼠标按钮,即可创建新的档案。如果此时仅打开一个文件管理器的面板,则档案将在当前目录中创建。如果两个文件管理器的面板都打开,则目标目录将是相反(非活动)面板中显示的目录。

在描述的过程过程中,两个面板的状态和所选文件的名称通过文件管理器的宏命令发送,这些宏命令由文章 6.25-02 中显示的某些字符组合调用。这些字符组合应该在菜单文件 VC.MNU(6.25-02)的一行中指定,以便由宏命令返回的每个数据项成为 ARC.BAT 文件的独立虚拟参数的值。特别是,对于创建 RAR 档案,调用 ARC.BAT 的菜单行可能如下所示。

@Arc.bat RAR !: !~\ !~ !~@ %: %~\

当 Volkov Commander 文件管理器解释菜单文件中的这行时,每个调用宏命令的字符组合将被该宏命令返回的数据项替换。然后,包含所有已执行替换的命令行将被发送到命令解释器 COMMAND.COM。后者在命令行中的数据项和批处理文件 ARC.BAT 的虚拟参数之间建立了顺序对应关系。在批处理文件中,虚拟参数用原始命令行中对应数据项的序号表示 - 从 0 到 9 的数字,前面加上百分号 (2.03-03)。ARC.BAT 文件的行将被 COMMAND.COM 解释,然后虚拟参数的指定符将被相应的数据项替换。根据显示的数据项顺序,虚拟参数将进行以下替换

%0 - 当前处理文件的名称 (ARC.BAT)
%1 - RAR (或 ZIP):请求的存档类型
%2 - 活动面板中显示的磁盘的字母名称
%3 - 活动面板中打开的目录的路径
%4 - 左键单击突出显示的文件名
%5 - 右键单击选择的文件列表
%6 - 非活动面板中显示的磁盘的字母名称
%7 - 非活动面板中打开的目录的路径

从文件管理器接收到的数据将由 ARC.BAT 文件从操作系统请求的其他数据补充。所有这些数据将被考虑在内,以便发现可能妨碍存档过程成功执行的错误。根据检查结果,将调用存档程序,或显示详细的错误消息。包含所有检查的命令行的 ARC.BAT 文件的完整文本如下所示。

@echo off
set V1=02
if %1"==ZIP" set V1=Pkzip.exe
if %1"==RAR" set V1=Rar.exe
if %6"==" set V1=02
if %V1%==02 echo Parameters are invalid or not defined!
if %V1%==02 goto END
set V2=08
for %%Z in (%path%) do if exist %%Z\%V1% set V2=%%Z\%V1%
rem ============ Line 10 ============
if %V2%==08 echo The %V1% archiver hasn't been found!
if %V2%==08 goto END
set V3=13
for %%Z in (%path%) do if exist %%Z\Find.exe set V3=%%Z\Find.exe
if %V3%==13 echo The Find.exe utility hasn't been found!
if %V3%==13 goto END
if %7"==" echo Archive in inactive panel must be closed!
if %7"==" goto END
if %4"==.." echo Name for the archive isn't chosen!
rem ============ Line 20 ============
if %4"==.." goto END
%V3% /C /I /V ".%1" %5 | %V3% ": 0" > nul
if not errorlevel 1 echo Chosen file(s) - already %1-archive(s)!
if not errorlevel 1 goto END
%V3% /I "%4.%1" %5 > nul
if not errorlevel 1 if %2%3"==%6%7" echo Conflicting filenames!
if not errorlevel 1 if %2%3"==%6%7" goto END
ctty nul
%comspec% /f /c copy %V3% %6%7%4.%1 /Y | %V3% "1 f"
rem ============ Line 30 ============
ctty con
if errorlevel 1 echo Non-writable target disk or overwrite denied
if errorlevel 1 goto END
del %6%7%4.%1
if %1==RAR %V2% a -s- -rr -ems- -w%5\.. %6%7%4.%1 @%5
if %1==RAR if errorlevel 1 goto END
for %%Z in (1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1) do echo.
if %1==ZIP %V2% %6%7%4.%1 -ex -P -wHS -jhsr @%5
if %1==ZIP if errorlevel 1 goto END
rem ============ Line 40 ============
echo Archive %4.%1 is written into the %6%7 directory.
:END

ARC.BAT 文件的结构很简单:它只是一系列检查。没有循环,没有子例程。当检查条件不满足时,将执行跳转。所有跳转的目标是最后一行中的单个标签 END。为了方便进行序号搜索行,每十行是一个注释,用于宣布行的编号。

ARC.BAT 文件的第一行关闭 ECHO 标志,因为否则很难注意到显示的消息。第 2-7 行中的命令检查第一个虚拟参数的值和第六个虚拟参数的值的存在。如果其中任何一个条件不满足,错误消息将告知参数无效或未定义。但如果两个条件都满足,则将调用要调用的存档程序的名称作为变量 V1 的值分配。

第 9 行中的命令检查所选的存档程序是否可以在 PATH 变量值 (2.02-02) 中指定的目录中找到。PATH 变量的正确值应事先准备好:它是第 9 章介绍文章中规定的必要条件之一。在本手册中介绍的所有配置文件集中都显示了 PATH 变量的赋值示例。但所选的存档程序是否真的能找到 - 这完全是你的责任。如果找不到存档程序,则会显示错误消息,并且从第 12 行到 END 标签的跳转将终止执行。如果找到了存档程序,则其名称和前缀路径将作为变量 V2 的值分配。

类似地,第 14 行中的命令安排对 FIND.EXE 实用程序 (6.14) 的搜索,该实用程序用于几个后续检查。如果找到了 FIND.EXE 实用程序,则其名称和前缀路径将作为变量 V3 的值分配。

ARC.BAT 文件的第 17 行和第 18 行中的命令检查第七个虚拟参数的值是否为空。当非活动文件管理器面板显示的不是目录,而是其他一些存档时,此值为空。在这种情况下,ARC.BAT 显示的错误消息将提示用户关闭非活动面板中的存档。

ARC.BAT 文件的第 19 行检查用户必须使用鼠标左键单击突出显示的名称。此名称稍后将分配给创建的存档。但有时文件管理器面板中突出显示的行会指向双点别名表示的父目录。在这种情况下,错误消息将告知名称选择无效。

ARC.BAT 文件的第 22 行中的命令检查文件列表的内容,其中包含应包含在新的存档中的文件的名称。现在就提醒一下,在第 22 行中两次替换的 V3 变量的值是 FIND.EXE 实用程序的名称和前缀路径。首次调用 FIND.EXE 实用程序时,该程序在文件列表中计算不属于要创建的存档类型的文件名。在准备好的文件组中允许存在此类存档,但对存档的重复压缩是不明智的:数据存储的可靠性将降低。计数结果通过中间重定向传输,然后,第二次调用相同的 FIND.EXE 实用程序检查计数结果是否为零。如果在准备好的组中没有找到其他类型的文件,则错误消息将指出所选文件已经是请求类型的存档。

ARC.BAT 文件的第 25 行中的命令在所选组中搜索具有与新存档指定相同名称和相同后缀的文件。如果同名文件已存在,则必须在当前目录之外创建新的存档,否则该同名文件将在包含到新存档之前被覆盖。如果检测到这种情况,则错误消息将告知当前目录中文件名冲突。

在 ARC.BAT 文件的第 29 行中,尝试将与新存档同名的文件写入要写入新存档的目标目录。目标目录的寻址使用所有为寻址不可访问的介质所需的预防措施:第 28 行中的 CTTY NUL 命令阻止了恐慌错误消息,并且使用 /f 参数调用 COMMAND.COM 解释器保证了不间断执行。应提醒注意,在第 29 行中替换的 COMSPEC 变量的值只是 COMMAND.COM 解释器的名称和前缀路径。此值是在首次启动命令解释器时 (6.04) 自动分配给 COMSPEC 变量的。

第 29 行中进行的尝试可能会失败,如果目标目录中存在具有 HRS 属性 (6.01) 保护的同名文件。另一个失败原因可能是不可写或受写保护的介质。无论如何,写入尝试的结果都反映在 COPY 命令发送到 STDOUT 通道的消息中。但在第 29 行中,STDOUT 通道被重定向到 FIND.EXE 实用程序。如果后者注册了写入尝试失败,则由于无法在目标目录中创建具有指定名称的文件而终止执行。但如果成功,则在第 34 行中,DEL 命令 (3.09) 将删除已写入的文件。这样就消除了存档程序任务成功执行的最后一个障碍。

在调用任何程序之前,应考虑该程序显示消息的方式。第 35 行中调用的 RAR.EXE 存档程序通过绕过 STDOUT 通道形成其屏幕字段,因此屏幕需要稍后清除。当 CLS 命令清除屏幕时,以下结论性消息显示在屏幕的顶行,并在文件管理器的面板下隐藏起来。因此,在第 37 行中,屏幕由 FOR 命令清除,将光标向下移动 20 行。此外,文件管理器的面板不能完全展开。Volkov Commander 面板的长度可以使用鼠标或箭头键修剪,同时按住 ALT-F11 或 ALT-F12 键组合。之后,应使用 SHIFT-F9 键在 VC.INI 文件中存储选定面板的大小。

PKZIP 存档程序通过 STDOUT 通道发送其消息,因此它在屏幕清除后被调用在第 38 行中。由于之前的检查,存档过程失败的可能性微乎其微,但并未被忽视。因此,在第 36 行和第 39 行中,为跳转到最终 END 标签做出了规定,以便在 ARC.BAT 解释终止后,存档程序发送的错误消息仍然可见。当存档任务成功完成时,第 41 行中的 ECHO 命令将显示结论性消息,告知创建的存档的名称及其写入的目录。

准备好的批处理文件 ARC.BAT 应存储在可以通过 PATH 变量 (2.02-02) 中指定的路径访问的目录中。应优先选择与其他文件管理器文件相同的目录。文章 6.25-02 中显示了包含启动 ARC.BAT 的行的 Volkov Commander 菜单文件 VC.MNU 的示例。

9.03-02 用于介质状态测试的批处理文件

[edit | edit source]

当您需要修理一台未知的计算机时,第一个问题是确定是否存在磁盘驱动器以及这些驱动器是否可访问。功能强大的实用程序 MSD.EXE (在 MS-DOS6.22 中) 和 NDIAGS.EXE (来自 Norton Utilities 软件版本) 旨在解决此问题;两者都与 MS-DOS 7 兼容,但两者都不报告介质状态 - 介质是否存在、是否已格式化、是否可写等。

提供的批处理文件 - 我们将其命名为 DISK.BAT - 提供了对磁盘介质状态的简短但全面的调查。主要思想是通过读取卷标来测试磁盘的可访问性,然后通过写入相同的卷标来测试磁盘的可写性。这是安全的,因为写入过程的成功或失败都不会对测试中的磁盘造成更改。

另一方面,DISK.BAT 文件可以被视为一些紧急任务的几个非显而易见的解决方案示例的来源

  • 将子例程合并到批处理文件中;
  • 不间断地测试磁盘,包括不可访问的磁盘;
  • 避免不必要的错误消息;
  • 生成临时命令文件;
  • 将 STDOUT 输出捕获到环境变量中。

以下是 DISK.BAT 文件的完整内容。为了方便查找特定行,每十行将以注释的形式标注行号。此外, DISK.BAT 中的标签以对应的行号命名;例如,标签 L28 表示第 28 行,标志着主程序部分的结束和子程序的开始。

@echo off
if %1"==&" if not %2"==" goto L%2
if %Path%"==" %0 & 79 4 PATH
if %Temp%"==" %0 & 79 4 TEMP
Call %0 & 28 A Attrib D Debug F Find L Label
if VA"==" goto L87
set V1=%Path%
set Path=%1
set V2=%Path%
rem ============ Line 10 ============
set Path=%V1%
set V1=
Call %0 & 39 A B C D E F G H I J K L M N O P Q R S
if %V1%"==" if not %1"==" %0 & 79 5
if %1"==" set V1=A C D E F G O R
ctty nul
%VA% -H -R -S %Temp%\tmp.*
echo e 100 'call %%1 & 46' 20 > %Temp%\tmp.scr
echo w >> %Temp%\tmp.scr
rem ============ Line 20 ============
echo q >> %Temp%\tmp.scr
set Dircmd=
for %%Z in (%V1%) do call %0 & 53 %%Z: %1
del %Temp%\tmp.*
ctty con
for %%Z in (A D F L 1 2) do set V%%Z=
goto L87
:L28
shift
rem ============ Line 30 ============
shift
set VL=
for %%Z in (%path%) do if exist %%Z\%2.exe set VL=%%Z\%2.exe
if %VL%"==" echo Error: the %2.exe utility hasn't been found!
if %VL%"==" set VA=
if not %VL%"==" set V%1=%VL%
if not %4"==" goto L28
goto L87
:L39
rem ============ Line 40 ============
shift
if %V2%"==%2" set V1=%2
if %V2%"==%2:" set V1=%2
if not %3"==" goto L39
goto L87
:L46
set V1=%6
if not %7"==" set V1=%6 %7
if not %8"==" set V1=%6 %7 %8
rem ============ Line 50 ============
if %5"==has" set V1=NO NAME
goto L87
:L53
%comspec% /f /c Dir /-p %3\nul > %Temp%\tmp.txt
%VF% "Volume in d" < %Temp%\tmp.txt > %Temp%\tmp.bat
if errorlevel 1 %0 & 79 6 %3 %4
%VF% "Directory of " < %Temp%\tmp.txt
if errorlevel 1 %0 & 79 7 %3
%VF% "0 bytes free" < %Temp%\tmp.txt
rem ============ Line 60 ============
if not errorlevel 1 %0 & 79 8 %3
%VD% %Temp%\tmp.bat < %Temp%\tmp.scr
call %Temp%\tmp.bat %0
set V2=if errorlevel 20 del %Temp%\tmp.bat
%comspec% /f /c for %%Z in ("%VL% %3%V1%" "%V2%") do %%Z
%VF% "Volume Seria" < %Temp%\tmp.txt
if errorlevel 1 if not exist %Temp%\tmp.bat %0 & 79 9 %3
if errorlevel 1 if exist %Temp%\tmp.bat %0 & 73 1 %3
if not errorlevel 1 if not exist %Temp%\tmp.bat %0 & 73 2 %3
rem ============ Line 70 ============
if not errorlevel 1 if exist %Temp%\tmp.bat %0 & 73 3 %3
goto L87
:L73
if %3"==1" if %3"==A:" echo Disk %4 (%V1%) is writable > con
if %3"==1" if not %3"==A:" echo Disk %4 (%V1%) is a RAM-disk > con
if %3"==2" echo Disk %4 (%V1%) is write-protected > con
if %3"==3" echo Disk %4 (%V1%) is writable > con
Dir /a:ARD /-p /v %4\ | %Dsk%\DOS\MS7\Find.exe "otal d" > con
:L79
rem ============ Line 80 ============
if %3"==4" echo The %4 variable is not defined
if %3"==5" echo Wrong parameter, it must be a diskletter or none
if %3"==6" if not %5"==" echo Letter %4 doesn't refer to a disk > con
if %3"==7" echo Disk %4 has no media inside > con
if %3"==8" echo Disk %4 is probably a CD-ROM (no free space) > con
if %3"==9" echo Disk %4 is not formatted > con
:L87

DISK.BAT 文件的第二行检查第一个虚拟参数的值。如果该值为 "&",则跳转到子程序;否则,继续执行主程序部分。需要注意的是,跳转地址 ("goto L%2") 不是固定的,而是由第二个虚拟参数的值决定。这使得子程序可以作为主批处理文件的一部分,否则子程序必须是独立的文件。

第 3 行到第 22 行指定了准备操作。首先检查 PATH 和 TEMP 变量的值。PATH 变量 (2.02-02) 必须指定到 MS-DOS 7 文件的路径,TEMP 变量必须指定到临时文件的目录路径。如果这两个变量都没有定义,则跳转到标签 L79,显示错误信息,并终止 DISK.BAT 文件的解释。本书中提供的每个配置示例都包含 PATH 和 TEMP 变量的赋值示例。

第 5 行的下一个检查调用了一个子程序,该子程序从标签 L28 开始。该子程序将所有用于执行后续测试的实用程序的名称和路径写入环境变量。这些实用程序的名称列在第 5 行,并通过虚拟参数传递给子程序。从 PATH 变量的值中提取特定路径的主要操作在第 33 行执行。如果路径未找到,则第 34 行的 ECHO 命令将显示错误信息。直到虚拟参数列表为空,第 37 行到标签 L28 的跳转将重复相同的循环,以查找下一个实用程序的路径。最终,Attrib.exe、Debug.exe、Find.exe 和 Label.exe 实用程序的路径将分别成为变量 VA、VD、VF 和 VL 的值。

从 L28 子程序返回后,命令解释器继续执行第 7 行到第 11 行中的命令。这些操作将用户在命令行中指定的磁盘盘符转换为大写。但是,用户可能指定除盘符以外的其他符号。因此,为了确定指定的符号是否属于磁盘盘符列表,第 13 行调用了位于 DISK.BAT 文件第 39 行到第 45 行的另一个子程序。如果指定的符号被拒绝,则从第 14 行跳转到标签 L79,显示错误信息,并终止执行。如果指定的符号确实是盘符,它将被赋值给变量 V1,然后将仅检查该磁盘。但是,DISK.BAT 可以不带参数启动,此时应检查预定义的磁盘组。一个包含对应盘符列表的值将被赋值给第 15 行的变量 V1。

磁盘检查过程需要在临时文件目录中创建三个辅助文件:TMP.SCR、TMP.TXT 和 TMP.BAT。为了确保这些辅助文件被创建,第 17 行的操作将删除该目录中可能已存在的同名文件的属性。第 17 行中替换的 VA 变量的值是带前缀路径的 ATTRIB.EXE 实用程序的名称。第一个辅助文件 - TMP.SCR - 是通过第 18 行到第 21 行中的输出重定向创建的。该文件的内容和用途将在后面解释。

第 23 行的 FOR 命令启动了主要探索循环,该循环按顺序对每个要测试的磁盘进行检查。要测试的磁盘由 V1 变量的值表示。磁盘检查由 DISK.BAT 文件第 53 行到第 72 行中的子程序执行。对于一个磁盘的检查,对该子程序的调用看起来像这样

call %0 & 53 %%Z: %1

其中 "call" 表示一个命令,每次执行 L53 子程序终止时都会返回到相同的 FOR 循环。虚拟参数 "%0" 会替换批处理文件名:DISK.BAT 或其他名称,如果该文件被重命名。

参数 "& 53" 将标签 L53 指定为跳转的目标,该跳转将从第二行执行。将磁盘盘符替换为 "%%Z:" 参数指定要检查的磁盘。

需要注意的是,第 24 行的 FOR 循环之前是第 16 行中的 CTTY NUL 命令 (3.07),该命令会中断与 DOS 的 IN/OUT 函数的默认通信,除了显式指定的通信。这可以避免出现大量错误信息,否则这些信息会不可避免地由对无效或不存在的磁盘的访问尝试引起。但是,错误信息可能对调试 DISK.BAT 文件本身很有用;因此,在第一次执行 DISK.BAT 文件时,最好通过在 CTTY NUL 命令前面加上 REM 命令 (3.24) 来禁用它。如果一切顺利,则应删除第 16 行中 REM 命令之前的 REM 命令。

磁盘检查子程序从标签 :L53 开始。第 54 行的第一个测试是由以下命令执行的

%Comspec% /f /c Dir /-p %3\nul

在执行之前,变量 %COMSPEC% 的名称将被替换为它的值,即 COMMAND.COM 解释器及其路径的名称。因此,将加载命令解释器的独立驻留模块。"/f" 参数强制该驻留模块不间断地工作,自动以 "FAIL" 回答所有关于错误的查询。"/c" 参数表示驻留模块必须仅执行一个后续命令 (DIR),并在执行完该命令后立即卸载自身。DIR 命令发送到 STDOUT 通道的消息被第 54 行重定向到辅助文件 TMP.TXT 中。

TMP.TXT 文件的内容被 FIND.EXE 实用程序检查四次,分别在第 55 行、第 57 行、第 59 行和第 66 行。这些行的 VF 变量名称被替换为 FIND.EXE 名称。第一次检查可以拒绝不存在的驱动器;第二次检查可以拒绝没有插入介质的可移动介质驱动器;第三次检查可以识别 CD/DVD-ROM 驱动器。如果检查条件不满足,则跳转到标签 L79,并在屏幕上显示相应的信息。只有那些存在、有插入介质且不是 CD/DVD-ROM 的磁盘才能通过前三次检查。FIND.EXE 实用程序在第 55 行的第一次检查中选择的一个字符串被发送到输出重定向,并被写入辅助文件 TMP.BAT 中;该文件的内容可能如下所示

Volume in drive D is EXTENDED1

经过前三次检查后,第 62 行将 TMP.BAT 文件的内容作为数据传递给 DEBUG.EXE,DEBUG.EXE 从命令文件 TMP.SCR 接收命令。TMP.SCR 由第 18 行到第 21 行的重定向预先创建;它包含以下行

e 100 'call %1 & 46' 20
w
q

第一行命令 "e 100" (6.05-05) 强制 DEBUG.EXE 覆盖加载字符串的一部分,从而将该字符串转换为

call %1 & 46 ve D is EXTENDED1

第二个命令 "w" (6.05-19) 使 DEBUG.EXE 将转换后的字符串写回 TMP.SCR 文件,第三个命令 "q" 关闭调试器会话。在进行强制转换后,TMP.BAT 文件包含一个 COMMAND.COM 解释器的 CALL 命令 (3.02)。执行该 CALL 命令将调用该程序,该程序将由 TMP.BAT 文件的第一个虚拟参数 %1 定义。这正是当第 63 行的 DISK.BAT 文件执行 TMP.BAT 文件时发生的情况。但是,第 63 行的 TMP.BAT 文件的第一个参数是 DISK.BAT 文件的 %0 虚拟参数,即 DISK.BAT 文件本身。因此,第 63 行的命令执行对 DISK.BAT 文件的递归调用。当执行该递归调用时,"& 46" 参数定义了从第二行跳转的目标标签 L46,第六个参数 ("EXTENDED1" 在所示示例中) 是所检查磁盘的卷标。因此,控制权将被转移到 DISK.BAT 文件第 46 行到第 51 行的子程序。该子程序将 V1 变量的前一个值替换为第六个虚拟参数的值 - 所检查磁盘的卷标。因此,L46 子程序的任务已完成,命令解释器返回到 DISK.BAT 文件的第 64 行,即子程序 L53。

DISK.BAT 文件的第 64 行为 V2 变量准备了一个值,该值只有一个目的:避免第 65 行的命令字符串发生换行,否则该字符串将过长。第 65 行的 FOR 循环执行两个操作,这两个操作由 VL、V1 和 V2 变量的值定义。第一个操作是尝试将卷标写回同一个磁盘。第二个操作是有条件地删除 TMP.BAT 文件,如果由前一次写入尝试返回的 errorlevel 值表明该尝试失败。从这一刻起,TMP.BAT 文件的存在证明了写入尝试成功,因此也表明所检查的磁盘是可写的。第 66 行的检查显示磁盘序列号是否出现在 TMP.TXT 文件中。结果由 errorlevel 值表示。这个 errorlevel 值以及 TMP.BAT 文件的存在是两个参数,这两个参数足以通过 DISK.BAT 文件第 67 行到第 71 行的条件控制转移来识别介质状态。控制权将被转移到 L73 子程序或 L79 子程序,这两个子程序都将向 CON 设备 (显示) 发送信息,告知所检查的介质的状态。

当发现所检查的介质是可访问的时,将执行 L73 子程序,因此第 78 行的命令将能够显示所测试磁盘的大小以及已占用磁盘空间的百分比。当所检查的介质的状态使人们无法获得更多关于该介质的信息时,将执行 L79 子程序。L73 或 L79 子程序将终止对一个磁盘的检查。然后控制权将返回到第 23 行的 FOR 循环的继续执行,磁盘检查过程最初是从这里调用的。

第 23 行的 FOR 循环将继续调用磁盘检查子程序 L53,按顺序从要检查的磁盘列表中指定下一个磁盘的盘符。当所有磁盘都被检查后,第 24 行的 DEL 命令将删除剩余的辅助文件,第 25 行的 CTTY CON 命令将恢复 DOS I/O 函数的默认通信,第 26 行的 FOR 循环将删除所有本地环境变量。第 27 行到最终标签 L87 的跳转将终止 DISK.BAT 文件的执行。在使用 DISK.BAT 文件时,应注意该文件需要直接访问要测试的磁盘。因此,DISK.BAT 文件不应在 WINDOWS 操作系统下的 "DOS 盒" 中启动,它只能在 MS-DOS 7 或 MS-DOS 8 下启动。此外,当然必须满足介绍性文章中规定的所有五个成功执行的必要条件。

9.04 配置文件与 RAM 磁盘的重新定位

[edit | edit source]

本文介绍了两种配置檔案 (CONFIG.SYS 和 AUTOEXEC.BAT) 版本,它们实现了两种选择:普通启动模式或启动后建立虚拟 RAM 磁盘,然后将 DOS 重新定位到该 RAM 磁盘上。虚拟 RAM 磁盘比任何真实的磁盘都快,使用它可以减少物理驱动器的负载和磨损。但出于其他原因,在计算机的修复过程中,RAM 磁盘至关重要。当计算机从可移动介质(软盘或 CD-ROM)启动时,包含该介质的驱动器会一直忙于其任务。除非将操作系统重新定位到其他位置,否则您无法将其他介质插入驱动器。在这种情况下,RAM 磁盘是将 DOS 重新定位的最合适目标。WINDOWS-95/98 版本中提供的 RAMDRIVE.SYS 驱动程序 (5.05-01) 允许在 MS-DOS 7 下建立虚拟 RAM 磁盘。大多数 RAM 磁盘驱动程序(包括 RAMDRIVE.SYS)的常见问题是,分配给虚拟 RAM 磁盘的字母名称事先没有预设。RAM 磁盘被分配了在分配给固定逻辑磁盘之后可用的第一个字母名称。但不同计算机的固定逻辑磁盘数量可能不同。因此,分配给 RAM 磁盘的字母名称事先无法确定,必须进行确定。为此,MS-DOS 8 使用了两个特殊檔案:批处理檔案 FINDRAMD.BAT 和可执行檔案 FINDRAMD.EXE。提议的配置檔案 (CONFIG.SYS 和 AUTOEXEC.BAT) 对解决了字母名称确定问题,方法是只使用 MS-DOS 7 的方式,无需辅助檔案。

9.04-01 加载 RAMDRIVE.SYS 驱动程序的 CONFIG.SYS 檔案

[edit | edit source]

这个版本的 CONFIG.SYS 檔案展示了相对简单的块结构示例。第一个块使用保留名称 [menu] 提供两种选择。当它被执行时,会显示两个菜单项的标题,并邀请您使用向上和向下箭头键选择其中一个。在您使用回车键确认选择后,加载程序 IO.SYS 会将所选替代方案的名称(“relocation” 或 “ordinary”)作为值分配给 CONFIG 环境变量,然后继续解释 CONFIG.SYS 檔案中同义块中的命令。

[menu]
numlock off
menuitem=relocation, Relocate DOS onto a 5.6 Mb RAM-disk
menuitem=ordinary, MS-DOS 7.10, ordinary loading
menudefault=relocation,20

[relocation]
include=ordinary
devicehigh=\DOS\DRV\Ramdrive.sys 5600 /E

[ordinary]
accdate c- d- e- rdevice=\
DOS\DRV\Himem.sys /v
device=\DOS\DRV\Emm386.exe ram v
dos=high,umb,noauto
buffershigh=30,0
fileshigh=30
lastdrivehigh=Z
fcbshigh=1,0
stackshigh=8,256
country=007,866,\DOS\DRV\Country.sys
devicehigh=\DOS\DRV\Dblbuff.sys
devicehigh=\DOS\DRV\Ifshlp.sys
devicehigh=\DOS\DRV\Setver.exe
devicehigh=\DOS\DRV\Atapimgr.sys /W:6 /NDR /T:5 /LUN
devicehigh=\DOS\DRV\Oakcdrom.sys /D:CD001

[common]
installhigh=\DOS\DRV\Ctmouse.exe
installhigh=\DOS\DRV\Keyrus.com
shell=\Command.com \ /E:2016 /L:511 /U:255 /p

块 [ordinary] 与文章 9.08-01 中介绍的 CONFIG.SYS 檔案版本类似,但有两个区别。首先,加载 ATAPIMGR.SYS 驱动程序 (5.07-01) 以便访问 DVD-ROM 磁盘。第二个区别是,此 CONFIG.SYS 檔案中没有加载 MSCDEX.EXE TSR 程序 (5.08-03),因为在这里加载它可能会影响磁盘字母名称的分配。等效的 TSR 程序 SHSUCDX.COM (5.08-04) 将稍后从下一个配置檔案 AUTOEXEC.BAT (9.04-02) 加载。

当然,CONFIG.SYS 檔案中指定的所有路径都必须与您计算机中的实际目录结构相对应。如果您选择 [ordinary] 替代方案,MS-DOS 7 将以普通方式加载,不会进行重新定位。

块 [relocation] 由两行组成。第一行中的命令 “include=” 强制 IO.SYS 加载程序执行块 [ordinary] 中的所有行。然后,块 [relocation] 中的第二行加载 Microsoft 的 RAM 磁盘驱动程序 RAMDRIVE.SYS (5.05-01)。后者根据指定选项在扩展内存中创建了一个 5600 KB 的虚拟 RAM 磁盘。这个大小的 RAM 磁盘可以由所有拥有 8 MB 或更多 RAM 内存的计算机提供,也就是说,所有现代计算机,甚至是一些过时的计算机都可以提供。

此 CONFIG.SYS 檔案中的最后一个块有一个保留名称 [common]。无论选择哪种替代方案,都会执行具有此名称的块。[common] 块中的命令加载 “鼠标” 驱动程序 CTMOUSE.EXE (5.03-03) 和用于切换代码页和键盘布局的组合驱动程序 KEYRUS.COM (5.02-05)。[common] 块中的最后一行将控制权转交给命令解释器 COMMAND.COM:后者必须通过执行最后一个配置檔案 AUTOEXEC.BAT (9.04-02) 来完成配置过程。

9.04-02 重新定位到 RAM 磁盘的 AUTOEXEC.BAT 檔案

[edit | edit source]

这个版本的 AUTOEXEC.BAT 檔案是为从逻辑磁盘 A: 加载 MS-DOS 7 而设计的,该磁盘至少包含一个 \DOS 目录。逻辑磁盘 A: 可以由真实的软盘表示,也可以由 BIOS 从存储在 CD-ROM 光盘中的映像模拟。如果您打算使用其他磁盘来启动计算机,则必须

  • 更改第五行中的磁盘字母名称分配;
  • 将此磁盘从要测试的磁盘列表中排除(“_relocation” 部分中的第三行);
  • 更正“_relocation” 部分中第四行和第五行中的 “IF” 命令的条件。

当然,如果确定了当前磁盘的字母名称,例如,通过 REASSIGN.COM 实用程序 (9.06) 确定,那么所需的更正可以自动完成,如文章 9.01-03 中所示。但在这里,最好将注意力集中在其他问题上——搜索分配给 RAM 磁盘的字母名称。大多数情况下,类似的加载场景是从逻辑磁盘 A: 启动的。

@echo off
if %1"==J" if not %2"==" goto _%2
prompt $p$g
set dircmd= /A /O:GNE /P
set dsk=A:
set comspec=%dsk%\Command.com
goto _%config%

:_relocation
echo.
echo Seeking RAM-disk as the last valid disk...
for %%Z in (C D E F G H I J K L) do call \Autoexec.bat J test %%Z:
if A:==%dsk% echo RAM-disk is not found!
if A:==%dsk% goto _ordinary
echo RAM-disk is assumed to be %dsk%
echo.
set comspec=%dsk%\Command.com
\DOS\MS7\Xcopy.exe \*.* %dsk%\ /S /E
%dsk%\Autoexec.bat J ordinary

:_ordinary
%dsk%
cd \
if not exist TEMP\nul %comspec% nul /f /c md TEMP
if exist TEMP\nul set Temp=%dsk%\TEMP
if %Temp%"==" echo Note: the TEMP variable is left not defined!
Lh \DOS\DRV\Shsucdx.com /D:?CD001 /L:N /~+ /R /Q
set VC=%dsk%\DOS\VC4
path ;
path=%VC%;%dsk%\DOS\OTH;%dsk%\DOS\MS7
Vc.com /TSR /no2E /noswap
goto _end

:_test
echo Testing disk %3 ...
%comspec% nul /f /c if exist %3\nul cd DOS
if exist ..\nul set dsk=%3
if exist ..\nul echo              valid
if not exist ..\nul echo              inaccessible
cd \
:_end

这个版本的 AUTOEXEC.BAT 从第二行中的条件跳转开始,这使递归子程序调用成为可能。当 AUTOEXEC.BAT 第一次被解释时,不会执行此跳转。之后是普通的赋值。第七行执行跳转到一个标签,该标签自 CONFIG.SYS 檔案 (9.04-01) 执行以来作为 CONFIG 变量的值存储。这个值可以是 “relocation”(如果您选择了 DOS 重新定位)或 “ordinary”(如果您希望将 DOS 保持原样)。

如果选择 “ordinary” 替代方案,则会解释 “_ordinary” 部分中的命令。这些命令包括变量 TEMP 和 PATH 的赋值,用于访问 CD/DVD-ROM 的 SHSUCDX.COM TSR 程序的加载,以及启动 VC.COM 文件管理器。最后,MS-DOS 7 保持在用于启动计算机的磁盘上处于活动状态。

如果选择 “relocation” 替代方案,则会解释 “_relocation” 部分中的命令。“_relocation” 标签后的第三行执行一个 FOR 循环,该循环递归地调用同一 AUTOEXEC.BAT 檔案的最后一部分 “_test” 中的测试子程序。测试过程应用于由 FOR 命令括号内的字母名称指定的一系列磁盘。这样做是为了找到最后一个字母名称,该字母名称对应于一个有效的、可访问的磁盘。如果 IFS 和网络驱动程序尚未加载,则只有这个字母名称对应于 RAM 磁盘。后一个条件是将 SHSUCDX.COM TSR 程序的加载推迟到测试循环结束的原因。

磁盘字母名称通过第三个虚拟参数 %3 按顺序发送到 “_test” 子程序。"_test" 标签后的第二行执行对任何给定磁盘的根目录是否存在进行测试。即使对于不可访问和不存在的磁盘,也可以应用此测试。如果测试中的磁盘存在根目录,则在当前磁盘上,当前目录将更改为 \DOS。接下来的几行检查当前目录的父目录是否存在:\DOS 目录有一个父目录(根目录),但根目录本身没有父目录。如果当前目录已更改,则测试中磁盘的字母名称将作为值分配给环境变量 DSK。"_test" 部分中的最后一行将当前目录返回到根目录。

在 FOR 循环中,给定一系列磁盘字母名称,"_test" 过程会按顺序用下一个值覆盖 DSK 变量的先前值,每个值都表示下一个可访问磁盘的字母名称。但最后一个有效磁盘的字母名称不会被覆盖,它将是 FOR 循环结束后的 DSK 变量的值。这个值将是 RAM 磁盘的字母名称。

当 FOR 循环终止时,"_relocation" 部分中的命令解释将继续。COMSPEC 变量的值被重新分配,这次它指向 RAM 磁盘的根目录。然后,XCOPY.EXE 将所有非隐藏檔案以及来自原始(当前)磁盘到 RAM 磁盘的整个目录结构一起复制。此时,隐藏檔案 (IO.SYS 和 MSDOS.SYS) 已经完成了其任务,不再需要。请注意,执行复制的实用程序 XCOPY.EXE 必须能够在同一目录中找到其库檔案 XCOPY32.EXE(即,在 \DOS\MS7 中)。

"_relocation" 部分中的最后一行递归地将控制权转移到原始 AUTOEXEC.BAT 檔案,而不是转移到 RAM 磁盘的根目录中的副本,因为 DSK 变量的值已经改变,现在指向 RAM 磁盘。由于同样的原因,DSK 变量定义的所有后续寻址也将引用 RAM 磁盘。

AUTOEXEC.BAT 檔案副本的解释从 "_ordinary" 部分的第一行继续。其第一行的操作具有重大意义:它将当前磁盘更改为 RAM 磁盘。从那时起,原始的可启动磁盘将被放弃,其所有檔案都已关闭,可以将其从驱动器中弹出。现在活动的系统是 RAM 磁盘上的 MS-DOS 7 副本。"_ordinary" 部分中的所有后续操作都将像 MS-DOS 7 通常从 RAM 磁盘加载一样执行。

9.05 简单实用程序的示例

[edit | edit source]

虽然调试器 DEBUG.EXE (6.05) 不是创建可执行程序的最方便的工具,但它能够编写简单的 COM 格式程序。COM 格式的一个显着特点是,程序在 PC 的内存中用于执行的方式与它在檔案中显示的方式完全相同。因此,程序员的经验应该从编写最简单的 COM 格式程序开始。最简单的程序没有结构,不依赖磁盘、檔案或用户。简单性使您可以忽略一些限制,包括对分配内存空间的限制(8.02-50 的注释 2)。在本部分的后面,介绍了两个非常简单的程序示例。这两个程序都是为了解决意外问题而自发编写的。后来在互联网上,找到了用于解决相同问题的更完善的程序。然而,这里介绍的是最简单的原始版本,因为完美不应该是初学者追求的主要目标。在这个阶段,我们的主要目标是您能够理解概念并加以实现。

9.05-01 蓝色亮度校正

[edit | edit source]

编写这个最简单程序的动力是显示器从以前的 CRT 更改为新的 LCD。由于 LCD 屏幕的调制特性不同,文件管理器的面板通常使用的深蓝色颜色变得太亮,导致视觉刺激。为了将以前的颜色恢复到文件管理器的面板上,编写了 BLUE.COM 程序。它对显卡的数模转换器 (DAC) 有影响,从而降低了蓝色的亮度。BLUE.COM 程序仅针对视频模式 03h 设计,没有适应能力。然而,它已被证明是有用的;也许您也会发现它有用。

BLUE.COM 檔案是由调试器 DEBUG.EXE 在执行命令序列后生成的。此命令序列应该使用编辑程序(如第 9 章引言文章中所述)写入命令檔案 BLUE.SCR,然后通过输入重定向发送到调试器。

Debug.exe < Blue.scr

命令檔案 BLUE.SCR 必须包含以下行:

A 100
MOV          AX,1010       ;100 Specify brightness function (8.01-24)
MOV          BX,0001       ;103 Prepare DAC's register number
MOV          CX,0015       ;106 CL - blue color brightness,
MOV          DX,0000       ;109               CH - green, DH - red
INT          10            ;10C Call for BIOS's INT10 handler
MOV          AX,4C00       ;10E Specify DOS's exit function (8.02-55)
INT          21            ;111 Call for DOS's INT21 handler

N Blue.com
R BX
0000
R CX
0013
W
Q

第一个命令 "A 100" 将调试器 DEBUG.EXE 切换到汇编模式,并指定开始地址 CS:0100h 用于写入机器代码。接下来的 7 行定义了 BLUE.COM 程序的所有操作。每个操作的含义在每个对应行分号后的注释中解释。第 9 行为空(7.01-04),它将强制 DEBUG.EXE 退出汇编模式。然后“N”命令(6.05-12)宣布要创建的文件的名称,并将它的长度(00000013h = 19 字节)写入寄存器 BX 和 CX。第 15 行的“W”命令创建文件 BLUE.COM 并将准备好的机器代码复制到该文件。最后一行中的“Q”命令终止调试器的会话。

在命令文件 BLUE.SCR 处理时,调试器会在屏幕上显示列表。列表允许通过将列表中的实际偏移量与分号后的第一个项目(每个行中的注释)中给出的正确偏移量进行比较来监控 BLUE.SCR 文件的排版正确性。比较技术在文章 9.07-01 中描述。如果列表没有发现错误,那么 BLUE.COM 实用程序就可以执行。对于这种简单的程序,不需要进一步的测试。如果您对 BLUE.COM 实用程序设置的蓝色亮度级别不满意,那么可以在 BLUE.SCR 文件的第 4 行中写入 CX 寄存器的亮度值,可以根据您的意愿进行更正。更正后的 BLUE.SCR 文件应该再次通过重定向发送到 DEBUG.EXE,以便创建 BLUE.COM 实用程序的新版本。

BLUE.COM 实用程序应该从 AUTOEXEC.BAT 文件中的那行启动,该行在启动 Volkov Commander 文件管理器之前。除此之外,默认亮度级别也会重新重置,例如,由 SCANDISK.EXE 实用程序(6.21)和 LXPIC.EXE 浏览器(与 VC.EXT 文件相关,6.25-03)重置。在执行完这些程序后,应该再次执行 BLUE.COM 实用程序。如果 BLUE.COM 从公共批处理文件的下一行或公共配置文件(例如 VC.EXT)启动,则可以自动完成此操作。这些例子这里没有展示,因为颜色校正的必要性取决于个人视觉感知。然而,BLUE.COM 实用程序执行的蓝色颜色校正已实现,用于制作截图 fig.3(在文章 6.09 中)和 fig.5(在文章 6.25-01 中)。

9.05-02 如何关闭 ATX 计算机

[编辑 | 编辑源代码]

1999 年,当 ATX 外形尺寸的计算机取代了以前的 AT 计算机时,又出现了编写一个简单程序的动力。新型的 ATX 计算机被设计为由操作系统关闭,而 ATX 计算机中电源按钮的可调节作用难以预见。DOS 从未有过用于关闭电源的实用程序。由于需要这种实用程序,它已经被编写出来,并且命名为 TURN_OFF.COM。

TURN_OFF.COM 文件是由调试器 DEBUG.EXE 生成的,它是命令序列执行的结果。这个命令序列应该使用编辑器程序(如第 9 章介绍性文章中所述)写入命令文件 TURN_OFF.SCR 中,然后通过输入重定向发送到调试器。

Debug.exe < Turn_off.scr

命令文件 TURN_OFF.SCR 必须包含以下行

a 100
mov          AX,5301       ;100 Specify APM activation function
mov          BX,0000       ;103 Prepare identifier of APM BIOS
int          15            ;106 Call INT15 handler for activation
mov          AX,530E       ;108 Specify request for APM emulation
mov          BX,0000       ;10B Prepare identifier of APM BIOS
mov          CX,0102       ;10E Request emulation of version 1.2
int          15            ;111 Call INT15 handler for emulation
mov          AX,5307       ;113 Specify power supply mode function
mov          BX,0001       ;116 Prepare all device's identifier
mov          CX,0003       ;119 Request power OFF operation
int          15            ;11C Call INT15 handler for power OFF
mov          AX,4C00       ;11E Specify DOS's exit function code
int          21            ;121 Call DOS's INT21 handler for exit

n turn_off.com
r BX
0000
r CX
0023
w
q

提出的命令文件 TURN_OFF.SCR 确实非常简单,它不指定条件跳转,它只使用两种类型的机器代码,并且生成的执行文件只有 35(23h)字节。

第一个命令 "A 100" 将调试器 DEBUG.EXE 切换到汇编模式,并指定开始地址 CS:0100h 用于写入机器代码。接下来的 13 行定义了 TURN_OFF.COM 程序的所有操作。每个操作的含义在每个对应行分号后的注释中解释。有关 INT 15\AH=53h 函数的更详细的信息,请参阅文章 8.01-70 - 8.01-72。

第 15 行为空(7.01-04),它将强制 DEBUG.EXE 退出汇编模式。然后“N”命令(6.05-12)宣布要创建的文件的名称,并将它的长度(00000023h = 35 字节)写入寄存器 BX 和 CX。第 21 行的“W”命令创建文件 TURN_OFF.COM 并将准备好的机器代码复制到该文件。最后一行中的“Q”命令终止调试器的会话。

为了监控 TURN_OFF.SCR 文件排版的正确性,每行中的注释都以相应机器命令的正确偏移量开头。此偏移量应与调试器在显示的列表中显示的实际偏移量进行比较。比较技术在文章 9.07-01 中描述。检查创建的文件的长度可能是明智的,可以使用 DIR 命令(3.10)或文件管理器面板中的长度指示来检查。如果列表和长度检查都没有发现错误,那么 TURN_OFF.COM 实用程序就可以执行。对于这种简单的程序,通常不需要进一步的测试。

在使用 TURN_OFF.COM 实用程序时,应该考虑到,上个世纪生产的大多数计算机没有 APM 系统,因此会忽略对 INT 15\AH=53h 函数的调用。此外,TURN_OFF.COM 实用程序不应该在 WINDOWS 操作系统下的“DOS 窗口”中运行,它只能在原生 DOS 下运行。通过文件管理器的菜单启动 TURN_OFF.COM 很方便(文章 6.25-02 中有示例)。

当然,最好是采取措施来防止可能出现的错误:防止在“DOS 窗口”中执行,以及防止在特定计算机中没有 APM 系统。然而,这样做会导致 TURN_OFF.COM 实用程序变得不那么简单。另一方面,相关的补充检查也不太复杂。例如,在文章 9.10-02 中的文件的 13Ah-141h 行中实现了对 WINDOWS 操作系统环境的识别。

文章 9.06、9.08 和 9.10 中展示了多个错误指示示例。在熟悉了这些示例之后,您就可以根据自己的需要升级 TURN_OFF.COM 实用程序。

注意 1:电源供应意外中断会导致这些数据的丢失,这些数据可能当时尚未由 SMARTDRV.EXE 驱动程序(5.06-01)从缓存缓冲区写入磁盘。这对意外的电源中断和使用 TURN_OFF.COM 实用程序关闭电源同样适用。如果您打算使用 SMARTDRV.EXE 驱动程序,可以在启动 TURN_OFF.COM 实用程序之前强制将数据写入磁盘,但完全禁用写回缓存更安全。

9.06 为环境变量赋值

[编辑 | 编辑源代码]

很久以前,DOS 被开发为通用操作系统,它的命令集是根据这个目的形成的。但现在 DOS 的作用已经专业化,因此以前的命令集现在看来部分冗余,部分不足。本文介绍了一个实用程序,它执行三个操作,这些操作补充了 DOS 命令集的最紧急的不足。特别是,这三个操作可以实现第 9.09 节中描述的自适应加载场景。

由于建议的实用程序用请求的其他数据替换了一些环境变量的当前值,因此它被命名为 REASSIGN.COM。REASSIGN.COM 文件并不大——总共 992 字节——因为它依赖于与 DOS 内部 SET 命令(3.26)的合作,并且没有重复它的功能。SET 命令准备一个环境变量,并且准备好的变量值中的第一个字符定义了 REASSIGN.COM 实用程序的操作

  1. 报告最大空闲 XMS 内存块的大小;
  2. 从键盘接受输入;
  3. 报告当前磁盘的字母名称。

准备好的值中的其余字符被忽略,但它们的存在为新值保留了空间。如果此空间不足以容纳返回的结果,则会显示错误消息。如果结果没有占用整个预留空间,则其剩余部分将用空格字符(20h)填充。

当计算机从任何可移动介质启动时,当前磁盘的字母名称由 BIOS 指定。REASSIGN.COM 实用程序的函数 3 可以确定分配的字母名称

set disk=33
Reassign disk
echo Current disk is %disk%

准备好的值 33 将被新的值替换,例如,D:。由于分配的字母名称已知,因此配置文件中的所有后续寻址都可以自动适应。

自适应加载过程的下一个问题是确定用于 DOS 重新定位的 RAM 磁盘的可行大小,而可用的内存量事先未知。但 REASSIGN.COM 实用程序的函数 1 给出了答案

set xms=11111
Reassign xms
echo Largest free XMS memory block is %xms% kb

上面示例中的最后一行显示了返回的结果。现在您已准备好指定 RAM 磁盘的大小,REASSIGN.COM 实用程序的函数 2 将接受所需的值

echo Specify the size of RAM-disk in kb:
set ramdisk=22222
Reassign ramdisk
Tdsk.exe R: %ramdisk% 512 /M /F:2

在执行函数 2 时,REASSIGN.COM 实用程序会提示您通过键盘输入所需的值。错误的数字可以使用 BackSpace 键删除。完成数据输入后,应使用 ENTER 键继续执行。所示示例最后一行中的命令将所需的大小值替换到 TDSK.EXE 驱动程序(5.05-02)的一组参数中,该驱动程序会安排所需大小的 RAM 磁盘。当然,REASSIGN.COM 实用程序除了上面提到的用途之外,还可以找到很多其他应用。

重要的是要注意,所有显示的示例都不能从 Norton Commander、Volkov Commander 或类似文件管理器提供的命令行执行。原因是文件管理器在单独的环境中执行每个命令行。因此,在一个命令行中使用 SET 命令设置的值在下一个命令行中将不可用。但普通的命令行以及批处理文件中的行在公共环境中执行,因此所有显示的示例都将在该环境中正确执行。

REASSIGN.COM 实用程序是由调试器 DEBUG.EXE 生成的,它是命令序列执行的结果。这个命令序列应该使用编辑器程序(如第 9 章介绍性文章中所述)写入命令文件 REASSIGN.SCR 中。口头评论可以省略。应特别注意空行——从末尾算起的第 8 行。它必须存在,因为空行会强制 DEBUG.EXE 退出汇编模式(7.01-04)。然后,命令文件 REASSIGN.SCR 应该通过输入重定向发送到调试器

Debug.exe < Reassign.scr

命令文件 REASSIGN.SCR 必须包含以下行

a 100
      ;************* Reassign.com **************
      ;********* Section 1: initial preparations
         ; 110 - target offset for jump from line 104
cmp          SP,2010       ; 100 Allotted less than 8 kb?
jbe          0110          ;*104 If yes, leave it intact
mov          SP,1FFE       ; 106 Set stack's top at 8 kb
mov          BX,0200       ; 109 Request for 8 kb space
mov          AH,4A         ; 10C Call for free MCB
int          21            ; 10E        creation function
mov          DX,03A8       ;=110 Message 4 offset (help)
cmp byte ptr [005D],20     ; 113 Is 1st byte in FCB free?
jz           0151          ;*118 If yes, go to display help
cmp byte ptr [005D],3F     ; 11A 1st byte - question mark?
jz           0151          ;*11F If yes, go to display help
cld                        ; 121 Set count upwards (DF=UP)
      ;********** Section 2: search results analysis
call         0173          ;*122 Call for PSP test subroutine
cmp byte ptr [0165],F7     ; 125 1st or 2nd cycle errors?
ja           0151          ;*12A Exit, if yes
mov          DX,0345       ;*12C Prepare message 2 offset
les          DI,[00F8]     ; 12F ES:DI - address of variable
ES:                        ; 133 Read 1st character in
mov          BL,[DI]       ; 134          variable's value
cmp          BL,31         ; 136 Lower limit: function 1
jb           0151          ;*139 Exit, if value is less
cmp          BL,33         ; 13B Upper limit: function 3
ja           0151          ;*13E Exit, if value is greater
shl          BL,1          ; 140 multiply by 2
mov          BH,00         ; 142 Prepare BX for calculation
call         [BX+0105]     ;*144 Call functional subroutines
jz           0151          ;*148 ZR state - message display
jb           015F          ;*14A CY - fail, errorlevel in DH
      ;************* Section 3: execution conclusion
         ; 151 - target for 118, 11F, 12A, 139, 13E, 148
         ; 15F - target for jump from lines 14A, 14F
call         01F9          ;*14C Call for copying subroutine
jmp          015F          ;*14F Jump to termination
mov          BX,DX         ;=151 DS:DX - message address
mov          CX,[BX-02]    ; 153 CX = number of characters
mov          BX,0001       ; 156 BX = handle to STDOUT
mov          AH,40         ; 159 Call for DOS's message
int          21            ; 15B            display function
mov          DH,F0         ; 15D Errorlevel = F0h
mov          AL,DH         ;=15F Restore errorlevel
mov          AH,4C         ; 161 Call for DOS's program
int          21            ; 163        termination function
      ;************** Section 4: data block.
         ; 165 - used in lines 125, 1D1, 1D6, 1F4, 205
dw           00FC
         ; 167 = (2*31 + 105) used in lines 144, 221, 22D
         ; 169 - used in lines 225, 236, 240, 247, 252, 280
dw           0213,029A,02DC,0000,0000,0000
      ;************** Section 5: PSP test subroutine
         ; 173 - target for calls from lines 122, 1A3
         ; 19F - target for a jump from line 197
         ; 1A6 - target for jumps from 17A, 183, 18F, 19D
xor          DI,DI         ;=173 Write zero in DI register
ES:                        ; 175 Does segment start
cmp word ptr [DI],20CD     ; 176          from CD20 command?
jnz          01A6          ;*17A Exit, if not
ES:                        ; 17C Is there CD21 command
cmp word ptr [0050],21CD   ; 17D            at offset 0050h?
jnz          01A6          ;*183 Exit, if not
push         ES            ; 185 Save PSP segment address
ES:                        ; 186 Load environment's segment
mov          ES,[002C]     ; 187            into ES register
call         01B3          ; 18B Call for name search
pop          ES            ; 18E Restore PSP address in ES
jz           01A6          ;*18F Exit, if name isn't found
mov          AX,ES         ; 191 Load PSP segment into AX
mov          DI,0016       ; 193 Is word in ES:[0016] cell
scasw                      ; 196     equal to segment in AX?
jnz          019F          ;*197 If no, word is parent's PSP
mov          DI,0010       ; 199 Is word in ES:[0010] cell
scasw                      ; 19C equal to segment in AX?
jz           01A6          ;*19D If yes, exit, nothing found
ES:                        ;=19F Load parent's PSP segment
mov          ES,[DI-02]    ; 1A0            into ES register
call          0173         ; 1A3 Call for PSP test subroutine
ret                        ;=1A6 Return from subroutine
      ;************* Section 6: name search subroutine
         ; 1A7 - target for jumps from lines 1C8, 1CF
         ; 1B3 - target for call from line 18B
         ; 1DF - target for jumps from lines 1B1, 1BA
mov          CX,0100       ;=1A7 Set number of comparisons
mov          AL,00         ; 1AA Compare with zero value
repnz                      ; 1AC Repeat till 00 is found
scasb                      ; 1AD Compare [ES:DI] with AL=00
cmp          CX,0000       ; 1AE If number of repetitions
jz           01DF          ;*1B1        expires, exit search
mov          DX,0318       ;=1B3 Entrance point to search
ES:                        ; 1B6 If [DI]==00h, hence
cmp byte ptr [DI],00       ; 1B7     environment is searched
jz           01DF          ;*1BA     up to end, exit search
mov          SI,005D       ; 1BC Name address - in DS:SI
mov          CX,000A       ; 1BF Set number of comparisons
repz                       ; 1C2 Repeat until difference
cmpsb                      ; 1C3 Compare [DS:SI] and [ES:DI]
cmp byte ptr [SI-01],20    ; 1C4 Does name end with space?
jnz          01A7          ;*1C8 If not, compare next name
ES:                        ; 1CA Is there equality sign
cmp byte ptr [DI-01],3D    ; 1CB            after the name?
jnz          01A7          ;*1CF If not, compare next name
sub byte ptr [0165],04     ;*1D1 Shift record offset by 04
mov          SI,[0165]     ;*1D6 Load record offset into SI
mov          [SI],DI       ; 1DA Save name's address
mov          [SI+02],ES    ; 1DC Save environment's segment
ret                        ;=1DF Return from subroutine
      ;******** Section 7: value copying subroutine
         ; 1E0 - target for jump from line 210
         ; 1E5 - target for jump from line 1F1
         ; 1F3 - target for jumps from lines 1E9, 1EE
         ; 1F9 - target for a call from line 14C
         ; 1FB - target for jump from line 202
         ; 204 - target for jump from line 1FF
CS:                        ;=1E0 Load source address
lds          SI,[00F8]     ; 1E1               into DS:SI
ES:                        ;=1E5 Is there free space at
cmp byte ptr [DI],00       ; 1E6        destination address?
jz           01F3          ;*1E9 If not, start next cycle
cmp byte ptr [SI],00       ; 1EB Is the source empty?
jz           01F3          ;*1EE If yes, start next cycle
movsb                      ; 1F0 Let's copy one byte
jmp          01E5          ;*1F1 Jump to check next byte
CS:                        ;=1F3 Calculate cell offset with
add word ptr [0165],0004   ; 1F4    next destination address
mov          AL,20         ;=1F9 Subroutine entrance point
ES:                        ;=1FB Is there a space to fill
cmp byte ptr [DI],00       ; 1FC     at destination address?
jz           0204          ;*1FF If not, finish filling
stosb                      ; 201 Send space to destination
jmp          01FB          ;*202 Go to check next byte
CS:                        ;=204 Load offset of cell with
mov          SI,[0165]     ; 205 destination address in SI
CS:                        ; 209 Load destination address
les          DI,[SI]       ; 20A                  into ES:DI
cmp          SI,00F8       ; 20C Is cell offset the last?
jnz          01E0          ;*210 If not, start next cycle
ret                        ; 212 Return from subroutine
      ;****** Section 8: function 1, XMS-memory space
         ; 213 - target address, stored in cell 167
         ; 256 - target for jumps from lines 23E, 247, 250
         ; 25B - target for jump from line 263
         ; 269 - target for jump from line 277
         ; 275 - target for jump from line 272
         ; 284 - target for jump from line 27E
         ; 285 - target for jumps from lines 21A, 234
mov          AX,4300       ;=213 Function 1 entrance point
int          2F            ; 216 Check whether HIMEM.SYS
cmp          AL,80         ; 218         driver is installed
jnz          0285          ;*21A Exit, if not installed
mov          AX,4310       ; 21C Driver's entrance request
int          2F            ; 21F Entrance address - in ES:BX
mov          [0167],BX     ; 221 Store entrance offset
mov          [0169],ES     ; 225 Store entrance segment
mov          BL,00         ; 229 Prepare 00h in BL register
mov          AH,08         ; 22B Code of XMS space request
call far     [0167]        ; 22D Send request to HIMEM.SYS
cmp          BL,00         ; 231 Is request satisfied?
jnz          0285          ;*234 Exit, if not
mov byte ptr [0169],00     ; 236 Let errorlevel be 00 if
cmp          AX,1900       ; 23B   free XMS area is not
jb           0256          ;*23E   enough for 6 Mb disk
inc byte ptr [0169]        ; 240 Let errorlevel be 01 if
cmp          AX,4900       ; 244   5600 Kb XMS-disk can
jb           0256          ;*247   be arranged
inc byte ptr [0169]        ; 249 Let errorlevel be 02 if
cmp          AX,8C00       ; 24D   XMS-disk must be limited
jb           0256          ;*250   to 16 Mb, if not, then
inc byte ptr [0169]        ; 252   let errorlevel be 03
mov          BP,SP         ;=256 Store current SP state
mov          BX,000A       ; 258 Set decimal divisor
xor          DX,DX         ;=25B Write zero into DX register
div          BX            ; 25D Divide by decimal divisor
push         DX            ; 25F Push remainder into stack
cmp          AX,0000       ; 260 Is dividing finished?
jnz          025B          ;*263 If not, go to next cycle
les          DI,[00F8]     ; 265 Target address - into ES:DI
pop          AX            ;=269 Pop a digit out of stack
add          AL,30         ; 26A Translate digit to ASCII
mov          SI,DI         ; 26C Store DI state in SI
ES:                        ; 26E Is there free space
cmp byte ptr [DI],00       ; 26F          at target address?
jz           0275          ;*272 Skip writing, if not
stosb                      ; 274 Write digit, increment DI
cmp          SP,BP         ;=275 All digits are popped?
jb           0269          ;*277 If not, go pop next digit
mov          DX,036F       ;*279 Prepare 3rd message offset
cmp          DI,SI         ; 27C Has writing been skipped?
jz           0284          ;*27E If skipped, skip errorlevel
mov          DH,[0169]     ; 280 Read errorlevel into DH
ret                        ;=284 Return from subroutine
mov          DH,FF         ;=285 Prepare errorlevel = FF
stc                        ; 287 Indicate failure by CF
ret                        ; 288 Return from subroutine
      ;******* Section 9: function 2, keyboard input
         ; 289 - target for jump from line 2AD
         ; 29A - target from 169, 28D, 291, 295, 2B4, 2BD
         ; 2BF - target for jump from line 2A8
         ; 2C7 - target for jumps from lines 2A3, 2C3
ES:                        ;=289 Let's check whether
cmp byte ptr [DI],00       ; 28A              buffer is full
jz           029A          ;*28D If yes, wait 1Bh, ODh, 08h
cmp          AL,20         ; 28F Characters below 20h
jb           029A          ;*291       shouldn't be accepted
cmp          AL,3D         ; 293 Equality sign
jz           029A          ;*295       shouldn't be accepted
int          29            ; 297 Display inputted character
stosb                      ; 299 Copy it into ES:DI buffer
mov          AH,10         ;=29A Function 2 entrance point
int          16            ; 29C Read inputted character
mov          DH,FF         ; 29E Errorlevel = FFh
cmp          AH,01         ; 2A0 Is the ESC key pressed?
jz           02C7          ;*2A3 If yes, no copying, exit
cmp          AH,1C         ; 2A5 Is the ENTER key pressed?
jz           02BF          ;*2A8 If yes, copy and then exit
cmp          AH,0E         ; 2AA Is BackSpace key pressed?
jnz          0289          ;*2AD If not, start next cycle
ES:                        ; 2AF Check, whether offset
cmp byte ptr [DI-01],3D    ; 2B0     in DI would point ahead
jz           029A          ;*2B4     to buffer's first cell
mov          AX,2008       ; 2B6 Output backspace sign and
call         02D2          ;*2B9          a space via INT 29
dec          DI            ; 2BC Decrement offset in DI by 1
jmp          029A          ;*2BD Return to start of cycle
cmp          DI,[00F8]     ;=2BF Has offset in DI changed?
jz           02C7          ;*2C3 If not, let's leave DH = FF
mov          DH,00         ; 2C5 If yes, let's set DH = 00
mov          AX,0A0D       ;=2C7 Output of line feed and
call         02D2          ;*2CA         CR signs via INT 29
cmp          DH,20         ; 2CD Set flags by comparison
cmc                        ; 2D0 Reverse state of CF flag
ret                        ; 2D1 Return from subroutine
      ;***** Section 10: function 2, output subroutine
         ; 2D2 - target for calls from lines 2B9, 2CA
         ; 2D5 - cycle return offset from line 2D9
mov          CX,0003       ;=2D2 Preset repetitions limit
int          29            ;=2D5 Send character to display
xchg         AL,AH         ; 2D7 Exchange characters
loop         02D5          ;*2D9 Cycle iteration check
ret                        ; 2DB Return from subroutine
      ;***** Section 11: function 3, disk determination
         ; 2DC - target address, stored in cell 16B
         ; 2EE - target for call from line 2E9
         ; 2F6 - target for call from line 30C
         ; 305 - target for call from line 2FE
         ; 30E - target for calls from lines 2F9, 308
mov          AH,19         ;=2DC Function 3 entrance point
int          21            ; 2DE Determine current disk
mov          DL,AL         ; 2E0 Copy disk number into DL
add          AL,41         ; 2E2 Translate number into ASCII
stosb                      ; 2E4 Copy ASCII code into ES:DI
ES:                        ; 2E5       and increment DI by 1
cmp byte ptr [DI],00       ; 2E6 Is buffer full?
jz           02EE          ;*2E9 If yes, don't append colon
mov          AL,3A         ; 2EB ASCII code of colon - in AL
stosb                      ; 2ED Copy colon's code to ES:DI
mov          DH,00         ;=2EE Prepare zero errorlevel
push         DI            ; 2F0 Save DI pointer in stack
mov          AX,0803       ; 2F1 Query for 1st DDT address
int          2F            ; 2F4 1st DDT address - in DS:DI
cmp          [DI+04],AL    ;=2F6 Is it a floppy disk?
ja           030E          ;*2F9 If no, don't search further
cmp          [DI+04],AH    ; 2FB If disk drive is the same,
jz           0305          ;*2FE        it should be skipped
mov          AH,[DI+04]    ; 300 Read disk drive number
inc          DH            ; 303 Increment number of drives
cmp word ptr [DI],FFFF     ;=305 Is this DDT the last?
jz           030E          ;*308 If yes, no further search
lds          DI,[DI]       ; 30A Next DDT address - in DS:DI
jmp          02F6          ;*30C Go to investigate next DDT
pop          DI            ;=30E Restore DI state from stack
push         CS            ; 30F Restore original
pop          DS            ; 310               segment in DS
cmp          DH,FF         ; 311 Clear ZF flag to NZ state
clc                        ; 314 Clear CF flag to NC state
ret                        ; 315 Return from subroutine
      ;********************* Section 12: message texts
dw           002B
         ; 318 - 1st message, mentioned at 1B3
db           0D 0A 'ERROR: specified name hasn'
db           27 't been found' 0D 0A
dw           0028
         ; 345 - 2nd message, mentioned at 12C
db           0D 0A 'ERROR: invalid value of the'
db           20 'variable' 0D 0A
dw           0037
         ; 36F - 3rd message, mentioned at 279
db           0D 0A 'ERROR: no space for new value,'
db           20 'old one is too short' 0D 0A
dw           0138
         ; 3A8 - 4th message (help), mentioned at 110
db           0D 0A 09 'Reassign.com overwrites value'
db           20 'of existing variable' 0D 0A 'Usage:'
db           0D 0A 09 'Reassign Anyname' 0D 0A 'Anyn'
db           'ame - name example (up to 8 letters) o'
db           'f a variable' 0D 0A 'The first in its'
db           20 'value must be a digit 1, 2 or 3 - i'
db           't defines function:' 0D 0A 09 '1 - get'
db           20 'size of largest free XMS block' 0D 0A
db           09 '2 - accept keyboard' 27 's input' 0D
db           0A 09 '3 - get current disk letter' 0D 0A
         ; 4E0

n Reassign.com
rbx
0000
rcx
03E0
w
q

REASSIGN.COM 实用程序的文本从释放内存的程序开始,这绝对是不需要的(详细信息请参见 A.12-7 的注释 5)。然后在第 110-11F 行中,对环境变量的名称进行检查,该名称应由命令解释器从命令行读取,转换为大写,并写入从偏移量 005Dh 开始的第一个 FCB 块(参见 A.07-1 的注释 4)。如果偏移量 005Dh 处的单元格为空或包含问号,则 REASSIGN.COM 将显示帮助消息并将控制权返回给命令解释器,并将错误级别设置为 240。否则,偏移量 005Dh 处的单元格被认为包含该环境变量的名称,该环境变量的值将被重新分配。

第 2 部分从第 122 行开始,调用在当前环境和底层环境中搜索变量的名称。搜索过程包括第 5 部分介绍的 PSP 测试子程序和第 6 部分介绍的名称搜索子程序。第 175-183 行中的命令检查偏移量 0000h 和 0050h 处单元格中的特定 PSP 签名。如果确认 PSP 有效,则从偏移量 002Ch 处的单元格读取对应环境的地址,并将其发送到从第 18B 行调用的名称搜索子程序。

名称搜索子程序检查整个环境空间,如果未找到请求的名称,则退出并设置 ZF 标志。但是,当搜索成功结束时,将把该变量值的完整地址(段:偏移量)写入内存单元格。该内存单元格的偏移量是通过从存储在偏移量 0165 处的指针中减去 4 来计算的。因此,在不同环境中找到的地址不会互相覆盖,而是并排存储在属于 REASSIGN.COM 实用程序的 PSP 的末尾。

如果名称搜索子程序返回时 ZF 标志清除,则 PSP 测试子程序必须“下降”到底层(“父”)PSP 并继续在那里进行测试。当前 PSP 单元格中偏移量 ES:[0016] 和 ES:[0010] 处的指针被视为“父”PSP 候选地址。相对于选定的“父”PSP 候选,PSP 测试子程序在第 1A3 行递归调用自身。所有检查都在该底层 PSP 及其环境中重复进行。递归下降到底层 PSP 可能重复多次。当它到达“底部”PSP,或当找到最后一个 PSP 无效,或当在最后一个环境中找不到请求的变量的名称时,它将结束。

在 PSP 测试子程序执行完毕后,第 2 部分中的操作继续执行。在那里,在第 12F 行中,与最近(“上层”)环境相关的变量值的地址被写入寄存器 ES:DI。该地址指向变量值的第一个字符,该字符定义了 REASSIGN.COM 实用程序的请求功能。此字符必须是数字,对应于 ASCII 代码 31h、32h、33h 之一。如果存在其他代码,则 REASSIGN.COM 将显示错误消息并将控制权返回给命令解释器。但是,如果所有检查都成功通过,则在第 144 行中将调用一个子程序,该子程序将执行请求的功能。子程序入口点的地址取自第 4 部分偏移量 0167h 处的列表。

功能 1 的执行从第 8 部分的入口点 0213h 开始。首先,调用 INT 2F\AX=4300h(8.03-22)检测 HIMEM.SYS 驱动程序(5.04-01)是否已加载。如果未加载,则 REASSIGN.COM 不会显示任何错误消息,但会将错误级别设置为 255 并将控制权返回给命令解释器。当 HIMEM.SYS 驱动程序加载时,将调用 INT 2F\AX=4310h(8.03-23)以获取驱动程序的入口点地址。返回的完整地址在第 22D 行中用于对驱动程序的 08h 功能(A.12-3)进行远距离调用。它返回多个参数,包括最大可用 XMS 内存块的大小。在第 236-252 行中,设置错误级别值以提示可行的 RAM 磁盘大小。第 256-263 行中的命令将十六进制 XMS 块大小转换为十进制数。然后在第 265-277 行中,将十进制数的数字转换为 ASCII 代码,并写入最近(“上层”)环境中变量值的位

功能 2 的执行从第 9 部分的入口点 029Ah 开始。输入的字符被依次接受,并写入最近(“上层”)环境中变量值的位

功能 3 的执行从第 11 部分的入口点 02DCh 开始。使用 INT 21\AH=19h 请求当前磁盘的编号,将其转换为磁盘字母名称的 ASCII 代码,并写入变量的旧值的位置。如果有至少一个字符的空间,则在字母名称后面附加一个冒号。

由于当前磁盘肯定存在,因此功能 3 的错误级别被赋予了另一个任务:确定软盘驱动器的数量。该数字必须为自适应加载过程所知,因为在只有一台软盘驱动器的计算机中,对其他软盘的所有请求都会被 MS-DOS 自动重新定向到唯一的驱动器 A:。软盘问题由于软盘驱动器模拟的可能性而变得更加复杂,这不会反映在 CMOS 内存数据中。

为了确定实际的软盘驱动器数量,第 2F1-30C 行中的命令调用 DDT 表(A.03-2)以获取有关逻辑磁盘与物理驱动器之间对应关系的信息。此信息使能够计算实际的磁盘数量,忽略对同一个物理驱动器的重复引用。之后,执行将从子程序返回到最后的第 3 部分。完成功能 3 后,REASSIGN.COM 将保留错误级别值,该值有助于定义正确的磁盘测试顺序(9.09-02 中的示例)。

每个命令的更详细解释在注释中给出,这些注释在分号之后补充每行。

值得注意的是 REASSIGN.COM 实用程序的模块化结构。每个功能都由一个独立于其他功能的独立子程序执行。此外,在入口点地址列表中还有 3 个未填充的位置(16D、16F、171),因此您可以添加自己的子程序的入口点。但在更新 REASSIGN.COM 之前,您必须注意当前汇编器文本的正确排版。它不像前面第 9.05 部分中的文本那样简单。您不应该尝试立即执行由 DEBUG.EXE 创建的文件,该文件响应您在彻底验证此文本之前键入的文本。

以下第 9.07 部分中给出的某些实用建议将帮助您检测排版错误、测试可执行文件并避免不必要的后续复杂情况。

注释 1:如果在某些底层环境中存在任何同义词变量,则 REASSIGN.COM 也会将新值分配给该变量,并且此新值可能会根据旧值的长度进行截断。应避免在不同上下文中重复使用同一个名称。

9.07 检查和测试的一些建议

[edit | edit source]

验证汇编器文本的常规做法包括清单分析、发现的错误的纠正以及后续的逐步跟踪。无论如何,错误更正是必要的,但用户不应指望 DEBUG.EXE 调试器能提供很大的帮助:它不会发现非语法错误,也不会像更完善的汇编器那样提供自动链接。尽管 DEBUG.EXE 调试器存在明显的缺陷,但在某些情况下,它没有合适的替代品。这种情况发生在不确定条件下,以及调试影响 DOS 或 BIOS 的基本功能或数据结构时。虽然调试器的功能有限,但这些功能仍然可以有效地使用。以下第 9.07 部分显示了调试器功能使用示例。

9.07-01 清单分析

[edit | edit source]

任何键入的汇编器文本中都应该假定存在错误。当 DEBUG.EXE 执行构成汇编器文本的命令时,它将显示清单。DEBUG.EXE 发现的错误将在该清单中标记。由于清单在屏幕上快速滚动,因此很容易错过错误标记。为了更仔细地检查显示的清单部分,您可以暂停执行,按 PAUSE 键。之后,任何其他键击都会恢复执行,然后可以根据需要在所需的时刻暂停多次。使用 PAUSE 键的操作在过时的计算机中已经足够了,但现代计算机的清单滚动速度太快。因此,可以在稍后更方便地检查清单,将其重定向并写入文件,例如

Debug.exe < Reassign.scr > Listing.txt

存储的文件 LISTING.TXT 可以使用任何查看器或编辑器程序进行打印或查看。图 6 显示了 REASSIGN.COM 程序(9.06)清单的一部分。在 LISTING.TXT 中,DEBUG.EXE 检测到的所有错误都从下一行用 ^Error(前面有一个插入符号指针)指出来。插入符号指向前一行的那个字符,DEBUG.EXE 无法解释该字符。这通常足以理解如何纠正错误。图 6 中的插入符号指向前一行中的错误:确实,那里键入了“imp”而不是“jmp”。有时插入符号指向前一行的末尾或分号符号,注释从那里开始。这种情况发生在命令规范中缺少某些必需的元素时。缺少哪个特定元素——这可以从第 7 章中得到澄清,第 7 章介绍了 DEBUG.EXE 可接受的所有机器命令规范形式。

图 6

在任何情况下,包含错误的行都不会被汇编,以下的所有偏移量都会发生偏移,并且在出现单个错误之后,所有后续寻址都会出错。在采取任何进一步的措施之前,必须更正已注册的错误。

信息列表能够揭示甚至那些 DEBUG.EXE 无法识别的错误。因此,您必须准备一个足够信息丰富的汇编文本:在注释中指定正确的偏移值,最好在每一行都指定。第 9.06、9.08 和 9.10 部分中部分展示的汇编文本的几乎每一行都包含一个注释,其中对应机器指令的正确偏移值在分号后立即指定。应该将正确偏移值与列表中每行开头显示的实际偏移值进行比较。这些偏移值的差异表示前一行中存在错误。在图 6 中,从第 147 行开始比较的偏移值不同,因此应该在偏移值为 144 的前一行中预料到错误。实际上,将图 6 中偏移值为 144 的汇编指令与原始文本中的同一行进行比较,就可以发现错误:“call [BX+005]”指令被错误地输入为“call [BX+0105]”。DEBUG.EXE 未注册此错误。在汇编文本中输入数据和文本消息时也会出现类似错误。在任何情况下,偏移值不匹配都应该促使您查找原因并最终进行更正。

机器指令内部的地址也可能包含 DEBUG.EXE 未检测到的错误。如果汇编行中的注释以星号开头,则该汇编指令包含目标地址。该地址必须等于该机器指令或具有相同正确地址(在注释中指定)的数据块的第一个字节的实际地址。为了便于视觉上搜索目标行,这些行的注释前缀为等号。此外,在每个部分的标题下方的单独行中带有注释,指定了指向该部分的目标地址的命令的位置。

最后一个需要检查的重要偏移值是标记空行的偏移值,该空行强制 DEBUG.EXE 退出其汇编操作模式。在第 9.06 部分中展示的汇编文本中,此空行是距末尾的第 8 行。由于普通程序的机器代码从偏移值 100h 开始写入,因此空行的偏移值必须正好比组装程序的总长度大 100h。如果您想将组装后的代码保存到文件中,则必须将 CX 寄存器预设为文件长度值,该值比响应空行返回的实际偏移值小 100h 字节。如果您追求其他目标——检测可能的偏移值位移错误,则必须将空行返回的偏移值与 CX 寄存器中预设的文件长度值进行比较。特别是,在第 9.06 部分中展示的汇编文本的倒数第三行中,CX 寄存器被预设为十六进制数 03E0h。因此,在列表中,响应空行返回的偏移值 04E0h 将表示直到最后一个处理的汇编指令都没有偏移值位移。

9.07-02 交互式调试机会

[编辑 | 编辑源代码]

当需要阐明某些命令或中断处理程序的效果时,输入 5-7 行并强制 DEBUG.EXE 立即执行这些命令行并不困难。但是,无法轻松地编写很长的命令行序列。您将被迫将很长的命令序列准备为文本命令文件,然后将其发送到解释器。对于 DEBUG.EXE,接受命令文件的唯一方法是通过输入重定向。当输入被重定向时,DEBUG.EXE 不会从键盘接受命令,交互式调试的所有优势都将消失。这通常被认为是无可奈何的选择。但这种观点是错误的。

通过输入重定向,DEBUG.EXE 调试器可以接受并执行那些将取消输入重定向并恢复调试器交互功能的命令。所需的命令序列可能如下所示

CS:                       ; Restore JFT contents for
mov word ptr [0018],0101  ;          the program under test
mov          AH,62        ; A query for segment address
int          21           ;           of debugger's own PSP
mov          DS,BX        ; Restore JFT contents
mov word ptr [0018],0101  ;                for the debugger
int          20           ; Return control to debugger

前两行命令恢复对 JFT 表(A.07-1 的注释 3)中单元格的第一个 SFT 表条目的引用,其偏移值为 CS:0018 和 CS:0019。这些单元格对应于 STDIN 和 STDOUT 通道,对第一个 SFT 表条目的调用(A.01-4)激活了 CON 设备驱动程序。因此,执行的替换恢复了对测试程序命令的显示和键盘的正常交互。相同的操作可以通过 INT 21\AH=46h 函数 (8.02-48) 完成,但这里没有应用,因为在这种特殊情况下,执行的替换还不够。

DEBUG.EXE 不服从其 JFT 副本中的引用,该副本是为测试程序创建的。更改还必须应用于原始 JFT——由 COMMAND.COM 解释器在调试器自己的 PSP 内部创建的 JFT。因此,在所提供示例的第 4 行中,对 INT 21\AH=62h (8.02-73) 函数的调用将原始 PSP 的段地址返回到 BX 寄存器。在所提供示例的第 5 和 6 行中,使用此段地址在原始调试器的 JFT 中进行相同的替换。因此,所有准备工作都已完成,以恢复调试器和用户之间的正常交互。最后一个特殊之处是,控制不是通过 RET 命令返回给 DEBUG.EXE(不像第 9.02 部分中的示例),而是通过调用 INT 20 处理程序 (8.02-01) 返回。与 RET 命令不同,INT 20 处理程序将堆栈指针保持在交互式操作模式下的原始状态。

为了避免与测试程序命令混淆,建议的命令可以以机器代码的形式追加到重定向的命令文件中。这些代码可以任意放置在任何空闲的内存空间中。假设偏移值为 F00h 之后的内存可用且绝对空闲。然后,应该替换命令文件中普通最后命令 “w” 和 “q” 的行将如下所示

e F00 2E C7 06 18 00 01 01 B4 62 CD 21 8E DB C7 06 18 00 01 01 CD 20
g=F00

如果通过输入重定向将包含这两行最后命令的命令文件发送到 DEBUG.EXE,则所有前面的命令行将照常执行,但之后将恢复 DEBUG.EXE 的交互式操作模式(而不是将控制权转移到 COMMAND.COM 解释器)。返回交互式操作模式的一个明显优势是,不再需要预先确定要保存的文件的长度。列表中显示了组装代码的实际长度,然后用户可以(事后)从命令行将所需的长度值输入到 CX 寄存器。另一个优势是,不再限制 (6.05 的注释 1) 对调用 STDIN 和 STDOUT 通道的命令的测试。最后,不再需要在每次更正后将处理后的程序保存到文件中:现在只需要将更正插入原始汇编文本中即可。

当程序由多个按顺序补充的部分组成时,这些优势尤其重要。顺序调试促进了早期检测非语法错误,并使纠正其后果变得更容易。在准备那些在文章 9.06、9.08、9.10 中完整的程序时,顺序模块化组成的优势充分体现出来。

9.07-03 批处理文件控制测试

[编辑 | 编辑源代码]

当错误更正后,最后的列表不再显示错误时,就该处理测试过程中剩下的所有错误了。

在最后一次组装迭代中生成的执行文件通常还很原始,无法立即启动。首先,它应该被传递到调试器。DEBUG.EXE 只能从命令行接受文件,如 6.05 部分的介绍文章中所示。然而,有一些理由让人更倾向于使用专门准备的批处理文件来进行调试安排。批处理文件使您能够摆脱每次需要重复测试时都要重新键入命令行的繁琐操作。其次,批处理文件始终提供通用环境。此外,批处理文件可以指定辅助函数,使测试结果更具信息量。

测试批处理文件的总体构成包括准备部分、主要测试执行部分以及分析和显示测试结果的最终部分。当然,每个部分的具体实现必须反映测试程序的具体特征。让我们考虑一个专门为测试 REASSIGN.COM 实用程序的第 2 个函数 (9.06) 而编写的测试批处理文件示例

@echo off
set input=22222
echo Original value of input=%input%
Debug.exe Reassign.com input < Reassign.scr
set E=
set Z=00
set N=
:ErrCycle
for %%Y in (0 1 2 %N%) do if errorlevel %E%%%Y%Z% set E=%E%%%Y
if %Z%"==" goto OUT
if %Z%"==0" set Z=
if %Z%"==00" set Z=0
set N=3 4 5
if not %E%"==2" if not %E%"==25" set N=%N% 6 7 8 9
goto ErrCycle
:OUT
echo Errorlevel is %E%
echo New value of input=%input%
pause

建议的批处理文件中的前三行代表准备部分;第四行代表主要的测试执行部分。应该考虑到,在调试过程中,您很容易在处理的命令序列中迷失方向。因此,应该预先准备包含注释的源程序列表。

在第 4 行中,DEBUG.EXE 从命令行获取一组参数,此外,还通过输入重定向接受命令文件。这里,命令行参数的主要用途是参与填充测试程序的 PSP。在最简单的情况下,命令行中指定的文件 REASSIGN.COM 甚至可以为空,但由于其名称出现在命令行中,因此该名称将与后面的参数(如果有)一起写入专门的 PSP 字段 (A.07-1)。当然,文件 REASSIGN.COM 可能不为空,然后调试器会将其代码从地址 CS:0100h 开始写入内存。此功能便于准备调试较大的可执行文件。

第 4 行的输入重定向将强制 DEBUG.EXE 从文件 REASSIGN.SCR 中组装命令。翻译后的机器命令将写入内存,从指定的任意地址开始,覆盖或补充先前从 REASSIGN.COM 文件中复制的代码。因此,文件 REASSIGN.SCR 只能包含需要进一步调试的汇编文本部分。后者与本书中介绍的汇编文本无关:没有必要将完整文本进行划分。无论如何,通过重定向发送的汇编文本必须以第 9.07-02 节中提出的两行命令结尾。这些命令将强制 DEBUG.EXE 从重定向模式退出到交互式操作模式。

等待来自键盘的命令,DEBUG.EXE 显示其提示符 - 一个闪烁的下划线 - 从而邀请用户进一步操作。最好从使用“G” (= Go,6.05-07) 和“P” (= Proceed,6.05-14) 命令测试程序部分(或子程序)开始。测试程序部分比逐步测试更容易。应特别注意关键点的标志和寄存器状态 - 例如,子程序返回点。如果程序的某一部分返回错误结果,则应对其进行逐步调试。即使程序的某一部分导致挂起,重置计算机并重新启动同一个测试批处理文件也不会花费太多时间。逐步测试可以解决最顽固的错误。

当需要关闭调试器会话以进行更正时,可以输入“Q” (= Quit) 命令,然后按 ENTER 键。在终止后,DEBUG.EXE 始终会留下零错误级别。但有时需要检查测试程序返回的错误级别。在这种情况下,调试器会话应通过调用 DOS 的 INT 21\AH=4Ch 函数(8.02-55)来终止,就像测试程序在调试器 shell 之外执行时必须终止一样。在调试器会话终止后,测试批处理文件最后部分的命令将被执行。REASSIGN.COM 程序执行的结果通过错误级别代码和某些环境变量的返回值来表达。为了捕获返回的错误级别,可以在由 COMMAND.COM 命令解释器的 shell 执行测试程序,该解释器以未记录的 /Z 参数(6.04)启动。但是,此 shell 不会传递环境变量的值。因此,提议的测试文件的第 5-16 行包含一个错误级别确定过程。然后,第 17-18 行的命令显示返回的错误级别值以及要由 REASSIGN.COM 实用程序更改的变量的值。最后一行带有 PAUSE 命令,可以防止显示的结果隐藏在文件管理器的面板下。为了检查测试程序的某些其他功能,必须更改测试批处理文件准备部分中的参数。有时,主测试执行部分中的命令行参数也需要更改。参数值的替换可以通过测试批处理文件的虚拟参数来执行。但是,在提供的示例中,为了简化起见,没有使用虚拟参数。假定所有必要的更改都可以通过编辑程序在批处理文件文本中进行。当批处理文件保存了所有必要的更改后,它就可以再次用于测试下一个功能。

通常,每个程序都必须经过三种类型的测试系列。第一类测试系列检查程序在正常条件下的所有功能。第二类测试系列检查程序对可能出现的异常情况的响应:无效规范、缺少必需参数、不可接受的数据值等。第三类测试系列适用于直接操作硬件的程序:此类程序必须证明其功能在所有相关的硬件配置中有效。当然,具体测试的组成取决于程序的任务和挑战的范围,但原则对于所有程序都是一样的,即使是像提议的 REASSIGN.COM 实用程序那样小的程序。当您的程序成功通过所有测试时,您对该程序的任务就可以被认为已完成。

9.08 让我们尝试组装一个驱动程序

[编辑 | 编辑源代码]

当使用 DOS 首次探索某台计算机时,最好为准备用于 DOS 重定位的 RAM 磁盘分配一个固定的字母名称。这可以通过使用一个驱动程序来实现,该驱动程序声明一定数量的非存在(虚拟)磁盘,以便 DOS 被迫向 RAM 磁盘提供所需的字母名称。

据我所知,已经成功创建了三种这样的驱动程序。但是,此类驱动程序的免费软件版本不会隐藏虚拟磁盘,而唯一隐藏虚拟磁盘的版本不是免费软件。因此,我自己的第四次尝试如下所示。它没有遵循已知的解决方案。它专门用于使驱动程序的特性更加清晰易懂。建议的驱动程序非常小:总共 503 字节。假设它被称为 SKIPDSK.SYS。

SKIPDSK.SYS 驱动程序由调试器 DEBUG.EXE 在命令序列执行后生成。此命令序列应写入命令文件 SKIPDSK.SCR,并应通过输入重定向发送到调试器

DEBUG.EXE < SKIPDSK.SCR

SKIPDSK.SCR 文件应通过编辑程序(如第 9 章的介绍文章中所述)根据下面提供的文本进行准备。可以省略文字注释,但应保留注释中的正确偏移值,以便于进行进一步的调试和测试。

a 0000
      ;************* Section 1: driver's header
         ; 000 Place for next driver's address
dw           FFFF,FFFF
         ; 004 Driver's attributes (A.05-2)
dw           2202
         ; 006 Strategy routine entrance point
dw           0031
         ; 008 Interrupt routine entrance point
dw           006E
         ; 00A Number of disks, accessed from 058, 113
db           00
         ; 00B An identifier (7 following bytes)
db           53,6B,69,70,44,73,6B
      ;************* Section 2: data
         ; 012 Request's offset, accessed from 032, 073
         ; 014 Request's segment, accessed from 037
dw           0000,0000
         ; 016 BPB data (A.03-4), referred at 093 - 0C1
db           00,02,FF,01,00,01,40,00,00,22,F0,01,00
db           12,00,01,00,01,00,00,00,00,00,00,00
         ; 02F CDS, from lines 03D, 04D, 052, 087, 0D8
dw           FEFE
      ;******* Section 3: TSR part of strategy routine
         ; 031 - specified at offset 006
CS:                        ;=031 Strategy routine entrance
mov          [0012],BX     ;*032 Store offset of the request
CS:                        ; 036         data block (A.05-3)
mov          [0014],ES     ;*037 Store its segment
retf                       ; 03B Return far to DOS
      ;*********** Section 4: response to media check
         ; 03C - target for jump from line 084
         ; 05C - target for jump from line 06C
CS:                        ;=03C Is the shift for dummy CDS
cmp byte ptr [002F],FE     ; 03D      record ready in CS:002Fh?
jnb          008E          ;*042 Return back, if no
mov          AH,52         ; 044 Call for List-of-Lists address
int          21            ; 046 Now the address is in ES:BX
ES:                        ; 048 Load address of the 1st CDS
les          BX,[BX+16]    ; 049     record into the ES:BX pair
CS:                        ; 04C Calculate offset of
add          BX,[002F]     ;*04D     the first dummy CDS record
CS:                        ; 051 Mark FFh at 002Fh means that
mov byte ptr [002F],FF     ;*052     media check has been done
CS:                        ; 057 Read the number of
mov          AH,[000A]     ; 058     dummy CDS records (units)
cmp          AH,01         ;=05C Compare the number with 01h
jb           008E          ;*05F Exit, if there are no units
ES:                        ; 061 Else write "disabled disk"
mov word ptr [BX+43],0000  ; 062      attributes into the CDS
add          BX,0058       ; 067 Calculate offset for next CDS
dec          AH            ; 06A Get number of remaining cycles
jmp          005C          ;*06C Repeat the cycle for next CDS
      ;*********** Section 5: TSR part of interrupt routine
         ; 06E - specified at offset 008
         ; 08E - target for jumps from 042, 05F, 082, 13C
pushf                      ;=06E Interrupt routine entrance
push         ES            ; 06F Save states
push         BX            ; 070      of registers and flags
push         AX            ; 071
CS:                        ; 072 Load request block address
les          BX,[0012]     ;*073       into ES:BX pair
ES:                        ; 077 Set "unknown media" status
mov word ptr [BX+03],8007  ; 078        to be returned
ES:                        ; 07D Check operation code
cmp byte ptr [BX+02],01    ; 07E       in request data block
ja           008E          ;*082 Jump to exit, if above 01h
jz           003C          ;*084 Go to media check, if 01h
CS:                        ; 086 If below 01h, then is it
cmp byte ptr [002F],FE     ;*087     the 1st initialization?
jz           00C3          ;*08C Go to initialize, if yes
pop          AX            ;=08E Else restore states
pop          BX            ; 08F        of registers
pop          ES            ; 090
popf                       ; 091 Restore flags
retf                       ; 092 Return far to DOS
      ;********** Section 6: array of BPB offsets for disks
         ; 093 - mentioned in lines 11E, 12A
dw           0016,0016,0016,0016,0016,0016,0016,0016
dw           0016,0016,0016,0016,0016,0016,0016,0016
dw           0016,0016,0016,0016,0016,0016,0016,0016
      ;********* Section 7: interrupt routine, non-TSR part
         ; 0C3 - target for jump from line 08C
push         DX            ;=0C3 Save states of
push         DS            ; 0C4       registers in stack
push         SI            ; 0C5
cld                        ; 0C6 Clear direction flag
ES:                        ; 0C7 Get in AH the disk number,
mov          AH,[BX+16]    ; 0C8        suggested by DOS
mov          AL,41         ; 0CB Convert the disk number into
add          AL,AH         ; 0CD       disk's letter-name in AL
CS:                        ; 0CF Replace with this letter-name
mov          [01E0],AL     ;*0D0       the former one in message
mov          AL,58         ; 0D3 Calculate in AX the shift for
mul          AH            ; 0D5    the first dummy CDS record
CS:                        ; 0D7 Now shift for the first dummy
mov          [002F],AX     ;*0D8    CDS record is in CS:[002F]
      ;********* Section 8: reading of disk's letter-name
         ; 0E2 - target for jump from line 0E7
         ; 0E9 - target for jump from line 0EE
ES:                        ; 0DB Load in DS:SI a pointer to
lds          SI,[BX+12]    ; 0DC       command-line arguments
mov          DX,013F       ;*0DF Offset of 1st error message
lodsb                      ;=0E2 Load a character into AL and
cmp          AL,20         ; 0E3    arrange a cycle searching
jb           00F8          ;*0E5        for first space after
ja           00E2          ;*0E7                driver's name
lodsb                      ;=0E9 Load a character into AL and
cmp          AL,20         ; 0EA    arrange a cycle searching
jb           00F8          ;*0EC       for valid letter after
jz           00E9          ;*0EE              the first space
cmp          AL,43         ; 0F0 Is the letter less than C: ?
jb           00F8          ;*0F2 If yes, jump to section 9
cmp          AL,59         ; 0F4 Is the letter less than Y: ?
jbe 0102                   ;*0F6 If yes, jump to section 10
      ;*********** Section 9: message display part
         ; 0F8 - target for jumps from 0E5, 0EC, 0F2, 10C
push         CS            ;=0F8 Prepare segment address in DS
pop          DS            ; 0F9          for display function
mov          AH,09         ; 0FA Call for a string
int          21            ; 0FC          display function
mov          AL,00         ; 0FE 0 disks to be set after error
jmp          010E          ;*100 Go to prepare return to DOS
      ;*********** Section 10: calculation of TSR part size
         ; 102 - target for jump from line 0F6
         ; 10E - target for jump from line 100
inc          AL            ;=102 Get RAM-disk's letter-name
mov          DX,01C3       ; 104 Offset of 2nd error message
CS:                        ; 107 Calculate number of dummy
sub          AL,[01E0]     ;*108     disks to be established
jb           00F8          ;*10C If below 0, display a message
ES:                        ;=10E Write number of disks into
mov          [BX+0D],AL    ; 10F       the request data block
CS:                        ; 112 Duplicate the number
mov          [000A],AL     ; 113        into driver's header
mov          AH,00         ; 116 Calculate offset for a
cmp          AL,00         ; 118    pointer to 1st byte past
jz           0121          ;*11A       driver's TSR part
shl          AX,1          ; 11C         according to formula
add          AX,0093       ;*11E                (2*AX + 093h)
      ;********* Section 11: filling the request data block
         ; 121 - target for jump from line 11A
ES:                        ;=121 Write the pointer's offset
mov          [BX+0E],AX    ; 122      into request data block
ES:                        ; 125 Write segment address for
mov          [BX+10],CS    ; 126        pointer offset at 0Eh
ES:                        ; 129 Offset of array of BPB offsets
mov word ptr [BX+12],0093  ;*12A       for each installed disk
ES:                        ; 12F Write segment address for
mov          [BX+14],CS    ; 130       the array of BPB offsets
ES:                        ; 133 Write "happy end"
mov word ptr [BX+03],0100  ; 134        status word for return
pop          SI            ; 139 Restore states
pop          DS            ; 13A          of registers
pop          DX            ; 13B
jmp          008E          ;*13C Go to return to DOS
      ;*********** Section 12: error messages
         ; 13F - 1st error message, referred at 0DF
db           0D 0A "SkipDsk: diskletter isn" 27 "t found"
db           20 "or out of range" 0D 0A
db           "Example:" 0A "device=A:\SkipDsk.sys Q:"
db           0D 0A 09 09 09 "Q: - diskletter (C: - Y:)"
db           20 "to be skipped" 0D 0A 0A 24
         ; 1C3 - 2nd error message, referred at 104
db           0D 0A "SkipDsk: diskletters below" 20
         ; 1E0 - letter-name, accessed from 0D0, 108
db           "A: are assigned yet" 0D 0A 0A 24
         ; 1F7 - checkpoint, end of driver's code

m 0000 L01F7 0100
n SkipDsk.sys
rBX
0000
rCX
01F7
w
q

与普通可执行文件不同,驱动程序从偏移量 0000h 开始加载,即没有为 PSP 预留空间 (A.07-1)。由于这个原因,用于组装驱动程序的开始命令不应为“A 100”,而是“A 0000”,就像普通程序一样。如果开始地址指定为其他值,则显示的目标偏移量会出现混乱。

另一个特殊功能是 DOS 驱动程序具有两个入口点,这两个入口点都不与驱动程序的代码开始地址重合。与策略例程相关的第一个入口点用于接收请求并通过相应的设备(打印机、磁盘驱动器等)启动其执行。与中断例程相关的第二个入口点用于收集结果并形成对接收到的请求的响应。介于两者之间的时间由 DOS 和物理设备并行使用,每个设备执行自己的工作。入口点在驱动程序的标头中宣布:策略例程位于偏移量 0006h,中断例程位于偏移量 0008h。这些和其他驱动程序标头元素在表 A.05-1 中显示。

SKIPDSK.SYS 驱动程序中策略例程的驻留部分由汇编文本的第 3 节表示。它很简单,只是将请求数据块的段地址和偏移量 (A.05-3) 写入准备好的内存单元中。SKIPDSK.SYS 驱动程序的策略例程没有非驻留部分。

所有特定于请求的操作都由中断例程执行,该例程从汇编文本的第 5 节开始。第 5 节以保存标志和寄存器的状态开始 - 这也是任何驱动程序的特定责任。然后检查请求操作的代码。由于 SKIPDSK.SYS 驱动程序仅“服务”虚拟磁盘,因此操作代码大于 01h 的请求始终会导致返回到 DOS,并带有“无效介质”状态字节。但是,对操作 00h 和 01h 的请求会调用第 7-11 节或第 4.0 节中提供的命令的执行。

对驱动程序的第一个请求始终是使用操作代码 00h 初始化。由于初始化仅请求一次,因此相应的第 7-11 节超出了驱动程序的驻留部分。第 7 节中的命令准备数据以供进一步使用,第 8 节中的命令从命令行读取要跳过的磁盘的字母名称,第 9 节中的命令始终准备显示错误消息。错误消息模板已在第 12 节中准备。第 10 节中的命令计算虚拟磁盘的数量和驱动程序驻留部分的实际长度。第 11 节中的命令使用应返回到 DOS 的数据填充请求块。根据这些数据,DOS 更正其 DPB (A.03-1) 和 CDS (A.03-3) 表,以便到指定名称的磁盘的字母名称都显示为繁忙。当稍后启动 RAM 磁盘驱动程序时,它将获得下一个空闲字母名称,该名称因此由 SKIPDSK.SYS 驱动程序预先确定。

在将字母名称分配给 RAM 磁盘时,DOS 必须将声明的虚拟磁盘视为有效。但是,之后它们的有效状态会限制进一步的字母名称分配。实际磁盘和虚拟磁盘的混合会造成混乱。因此,SKIPDSK.SYS 驱动程序的下一个任务是禁用虚拟磁盘。为此,必须再次激活 SKIPDSK.SYS。

SKIPDSK.SYS 第二次被“介质检查”操作(代码 01h)的请求激活,因为它在所有其他请求之前,这些请求针对可移动磁盘。除此之外,当 CONFIG.SYS 文件的解释完成时,DOS 会自动请求介质检查操作。作为对介质检查请求的响应,SKIPDSK.SYS 驱动程序执行其驻留部分 4.0 中的命令。这些命令确定第一个虚拟 CDS 记录的地址,然后执行将无效属性字 0000h 写入所有虚拟 CDS 记录的循环。此后,所有“虚拟”磁盘都将被隐藏并被视为无效。它们的先前字母名称可以分配给 CD-ROM 和网络驱动器。

当虚拟磁盘的先前字母名称已分配给另一个驱动程序时,必须禁止将属性字 0000h 写入同一个 CDS 记录中。因此,第 4 节中的命令之一将标记 FFh 写入偏移量 02Fh 处的内存单元中。每次调用 SKIPDSK.SYS 驱动程序的中断例程时都会检查此标记的存在。由于此检查,对 SKIPDSK.SYS 驱动程序的重复请求不会影响对已获得虚拟磁盘先前字母名称的其他磁盘的正常访问。

可以在汇编文本文件 SKIPDSK.SCR 的行注释中找到对特定操作的更详细说明。

与大多数汇编文本一样,重要的是要注意空行,即从末尾算起的第 9 行。它强制 DEBUG.EXE 退出汇编操作模式,因此必须存在。下一行带有“M”命令 (6.05-11) 将整个组装后的代码移动 100h 字节,从而使 PSP 区域可用。这对于指定驱动程序的名称是必要的,该名称将被写入 PSP 区域,并在下一行通过“N”命令 (6.05-12) 宣布。SKIPDSK.SCR 文件的结束行在 BX:CX 寄存器中准备驱动程序文件的长度,并将 SKIPDSK.SYS 驱动程序写入当前磁盘。

生成的 SKIPDSK.SYS 文件在对其清单进行全面检查之前,不应被视为有效,如第 9.07-01 章所述。清单中的最后一个实际偏移量在响应提到的空行时给出 - 距离文本末尾第 9 行。由于汇编从地址 CS:0000h 开始,此最后一个偏移量必须为 01F7h,与从最后开始第 8 行和第 3 行指定的驱动程序的总长度完全相同。如果清单未发现任何错误,则 SKIPDSK.SYS 很有可能通过测试。

在安排测试时,应考虑到 SKIPDSK.SYS 驱动程序的功能出于简单性和紧凑性考虑而受到限制。它不能影响已经分配的磁盘字母名的分配。它不能接受以斜杠开头的字母名规范。它不能容忍其命令行中使用制表符代码 (09h) 而不是空格 (20h)。最后,它不能在早于 4.0 的 MS-DOS 版本下运行。

在实践中,可能需要在完成 CONFIG.SYS 文件的解释之前重新分配“虚拟”磁盘的字母名。为此,应该提前激发对 SKIPDSK.SYS 驱动程序的介质检查请求,例如,通过以下命令:

devicehigh=\DOS\DRV\SkipDsk.sys Q:
devicehigh=\DOS\DRV\Ramdrive.sys 2400 /E
install=\Command.com nul /low /f /c vol Q:

在这个例子中,字母名 Q: 将分配给最后一个虚拟磁盘,而 RAM 盘将被赋予下一个字母名 R:。在最后一行,对“虚拟”磁盘 Q: 的显式调用激发了介质检查请求,从那时起,所有“虚拟”磁盘的字母名都将对软件免费重新分配,软件随后可以由 INSTALL= 或 INSTALLHIGH= 命令加载。关于 SKIPDSK.SYS 驱动程序使用的另一个类似示例,请参阅第 9.09-01 章。

9.09 探索性配置文件

[edit | edit source]

本文中建议的 CONFIG.SYS 和 AUTOEXEC.BAT 配置文件版本实现了多种加载模式。除了以普通方式加载 MS-DOS 7 外,它还可以被重新定位到 RAM 盘或硬盘驱动器上,并且还可以不带驱动程序加载,就像在进行 RAM 测试和重新编程 BIOS 内存芯片时应该做的那样。建议的配置文件的主要优点是,对最合适的加载模式的选择不一定是“盲目的”,它可以基于初步测试的结果。如果您希望将 MS-DOS 7 重新定位到 RAM 盘上,则其大小选择将基于 XMS 内存调查的结果。如果您希望将 MS-DOS 7 重新定位到硬盘驱动器上,您将提前收到有关哪些磁盘可用、所有可写磁盘的总大小和使用百分比的信息。

当操作系统加载过程尚未完成时,硬件测试不能像操作系统已经加载那样进行。因此,这里不能像 DISK.BAT 文件(9.03-02)中那样安排磁盘测试。由于同样的原因,标准 DOS 的方法也不够,必须使用其他驱动程序和实用程序。另一方面,在操作系统加载过程中,可以采用一些简化的假设。假设当前磁盘是用于加载 MS-DOS 7 的磁盘,假设此磁盘上的目录结构已知,并且还知道在每个阶段哪些加载操作尚未执行。特别是由于最后一个假设,不需要识别 RAM 盘和 CD/DVD-ROM 驱动器。

建议的配置文件最适合在硬件配置未知的 AT 兼容计算机上加载 MS-DOS 7。加载适应能力对于维修服务尤其重要。

9.09-01 多重选择 CONFIG.SYS 文件

[edit | edit source]

此版本的 CONFIG.SYS 文件在 [menu] 部分中列出了 5 种加载替代方案。其中一些替代方案包括标准 DOS 方法无法执行的操作。特别是,通过 TDSK.EXE 驱动程序(5.05-02)的免费版本 2.42 实现了调整 RAM 盘大小的机会。目前尚不知道该驱动程序的合适替代品。除此之外,还使用了 SKIPDSK.SYS 驱动程序(9.08),您可以自己制作。潜在地,JAMSOFT 的 JDRIVE.SYS 驱动程序可以替代使用,但它不是免费的。

其他使用驱动程序的数据和参数可以在第 5 章中找到。这些驱动程序要么包含在 WINDOWS-95/98 版本中,要么具有接近的等效项,因此它们的選擇并不关键。当然,每次替换都需要根据驱动程序的要求对命令行参数进行更正。建议的 CONFIG.SYS 文件的文本如下所示。

[menu]
numlock off
menuitem=L047, User-configurable real mode
menuitem=L048, User-configurable V86 mode
menuitem=L029, Quick boot in real mode
menuitem=L031, Quick boot in V86 mode
menuitem=L104, Real mode without drivers
menudefault=L029,20

[L047]
device=\DOS\DRV\Himem.sys /v
device=\DOS\DRV\Umbpci.sys
include=L104
country=007,866,\DOS\DRV\Country.sys
devicehigh=\DOS\DRV\Dblbuff.sys
devicehigh=\DOS\DRV\Ifshlp.sys
devicehigh=\DOS\DRV\Setver.exe
devicehigh=\DOS\DRV\Dvs.sys /D:CD001
devicehigh=\DOS\DRV\Skipdsk.sys Q:
device=\DOS\DRV\Tdsk.exe 0
install=\Command.com nul /low /f /c vol Q:
installhigh=\DOS\DRV\Shsucdx.com /D:?CD001 /L:N /~+ /R /Q
installhigh=\DOS\DRV\Ctmouse.exe
installhigh=\DOS\DRV\Keyrus.exe

[L048]
device=\DOS\DRV\Himem.sys /v
device=\DOS\DRV\Jemm386.exe X=TEST noems verbose
include=L104
country=007,866,\DOS\DRV\Country.sys
devicehigh=\DOS\DRV\Dblbuff.sys
devicehigh=\DOS\DRV\Ifshlp.sys
devicehigh=\DOS\DRV\Setver.exe
devicehigh=\DOS\DRV\Dvs.sys /D:CD001
devicehigh=\DOS\DRV\Skipdsk.sys Q:
device=\DOS\DRV\Tdsk.exe 0
install=\Command.com nul /low /f /c vol Q:
installhigh=\DOS\DRV\Shsucdx.com /D:?CD001 /L:N /~+ /R /Q
installhigh=\DOS\DRV\Ctmouse.exe
installhigh=\DOS\DRV\Keyrus.exe

[L029]
include=L047

[L031]
include=L048

[L104]
accdate C- D- E- Fdos=
high,umb,noauto
buffershigh=30,0
fileshigh=30
lastdrivehigh=Z
fcbshigh=1,0
stackshigh=9,256

[common]
shell=\Command.com \ /E:2016 /L:511 /U:255 /p

CONFIG.SYS 文件的文本以 [menu] 部分开头。有 5 个项目,每个项目都有一个明确的标题和一个名称代码(L029 - L104)。选择具有某个名称代码的项目将启动对 CONFIG.SYS 文件中同义部分的命令解释。

除此之外,所选项目的名称代码还会自动作为值分配给 CONFIG 环境变量;此值稍后用于选择其他配置文件 - AUTOEXEC.BAT 的相应部分。为此,菜单中的项目以 AUTOEXEC.BAT 文件(9.09-02)中的标签标记命名。

选择菜单项目 L029、L031、L047、L048 将导致加载几乎相同的驱动程序集,这些驱动程序集在 [L047] 和 [L048] 部分中定义。这两个部分之间唯一的区别在于它们的第 2 行:驱动程序 UMBPCI.SYS(5.04-04)允许访问 CPU 实模式中的上层内存,而驱动程序 JEMM386.SYS(5.04-02 的注释 4)允许访问 V86 模式中的类似内存。L047 和 L048 两个部分都包含 XMS 内存驱动程序、访问 CD/DVD-ROM 的驱动程序、“鼠标”驱动程序和其他一些驱动程序的加载。请注意,与大多数其他驱动程序不同,用于安排 RAM 盘的 TDSK.EXE 驱动程序加载到常规内存中,并且 RAM 盘的大小在此处未指定。如果认为安排 RAM 盘是明智的,则应在解释 AUTOEXEC.BAT 文件(9.09-02)期间稍后指定其大小。

L029-L048 配置之间的足够差异也反映在稍后的 AUTOEXEC.BAT 文件解释过程中,除了由菜单项 L104 定义的一个配置。相应的 [L104] 部分仅包含 DOS 设置,不包含驱动程序和 TSR。CONFIG.SYS 文件中的最后一个部分是 [common] 部分,它在任何情况下都会执行。它加载命令解释器 COMMAND.COM。

CONFIG.SYS 文件各行中的所有路径都不包含磁盘字母名,因此适合从任何磁盘加载。应注意所有提到的驱动程序和其他文件是否都在规定的目录中。路径更改是允许的,但会影响 AUTOEXEC.BAT 文件执行(9.09-02)的条件,因此必须保持一致。

建议的 CONFIG.SYS 文件应作为非格式化文本键入,并保存在可引导的可移动介质的根目录中 - 既可以是软盘也可以是闪存卡,专门用于将 MS-DOS 7 加载到硬件配置未知的 AT 兼容计算机上。

9.09-02 用于自适应加载的 AUTOEXEC.BAT 文件

[edit | edit source]

此版本的 AUTOEXEC.BAT 文件实现了 MS-DOS 7 的探索性和自适应功能。为此,使用 REASSIGN.COM 实用程序,您可以自己组装(9.06)。目前尚不知道 REASSIGN.COM 实用程序的完整功能替代品。

为了使 AUTOEXEC.BAT 文件中的方向更容易,标签标记包括相应的行号;例如,标签 L029 表示第 29 行。除此之外,每十行都有一条评论宣布行号。

AUTOEXEC.BAT 中的局部变量命名为 V0-V8。其中一个 V8 没有永久任务。其余局部变量的专用任务是:

V0 - 要测试的磁盘列表
V1 - 最大可用 XMS 内存块的大小
V2 - 推荐的 RAM 盘大小
V3 - 重新定位的目标候选磁盘
V4 - 用于引导 PC 的当前磁盘
V5 - 当前磁盘的可写状态
V6 - 不可访问或不可写的磁盘列表
V7 - 可写磁盘列表

用于自适应加载的 AUTOEXEC.BAT 文件版本的文本如下所示。

@echo off
if %1"==J" if not %2"==" goto L%2
prompt $p$g
path ;
path=\DOS\MS7;\DOS\OTH
set V0=C D E F
set V4=33
Reassign.com V4
if errorlevel 2 set V0=%V0% B
rem ============ Line 10 ============
if errorlevel 1 set V0=%V0% A
set comspec=%V4%\Command.com
set V1=11111111
set V2=300
Reassign.com V1
if errorlevel 1 if not errorlevel 2 set V2=5600
if errorlevel 2 if not errorlevel 3 set V2=16000
if errorlevel 3 if not errorlevel 100 set V2=32000
if errorlevel 100 for %%Z in (1 2) do set V%%Z=
rem ============ Line 20 ============
if errorlevel 100 if not %config%"==L104" set config=L047
if not %config%"==L104" goto %config%
for %%Z in (3 5 6 7) do set V%%Z=
for %%Z in (%V0%) do call \Autoexec.bat J 117 %%Z: Q
if %V5%"==F" echo Warning: current disk %V4% is not writable!
if not %V7%"==" echo Writable disk(s): %V7%
if %V7%"==" echo Warning: no writable disks have been found!
goto L104
:L029
rem ============ Line 30 ============
:L031
set V3=
set ramdrive=?:
%V4%\DOS\DRV\Tdsk.exe %V2% /E /M /F:2
if not %ramdrive%"==?:" if not %ramdrive%"==::" set V3=%ramdrive%
if not %V3%"==" if not %V2%==300 goto L086
if not %V3%"==" if %V2%"==300" goto L104
set V1=
echo RAM-disk arranging procedure has failed!
rem ============ Line 40 ============
goto L047
:L042
for %%Z in (1 1 1 1 1 1 1) do echo=
ctty nul
call %V4%\Autoexec.bat J 117 %V3% S
ctty con
:L047
:L048
for %%Z in (1 1 1 1 1 1 1) do echo=
rem ============ Line 50 ============
for %%Z in (5 6 7) do set V%%Z=
ctty nul
for %%Z in (%V0%) do call %V4%\Autoexec.bat J 117 %%Z: V
ctty con
if %ramdrive%"==" set ramdrive=R:
if not %V7%"==" echo Writable disk(s): %V7%
if %V7%"==" echo Writable disks have not been found!
if not %V1%"==" echo Available XMS-memory is %V1% kb
if %V1%"==" echo XMS-memory is unavailable
rem ============ Line 60 ============
echo Select one of the shown keys and then press ENTER:
if not %V7%"==" echo %V7% - set DOS onto the chosen disk
if not %V6%"==" echo %V6% - retry inaccessible disk(s)
if not %V1%"==" echo %ramdrive% - set a RAM-disk for DOS
echo ESC - leave DOS on %V4%, no relocation
echo=
:L067
set V3=2
Reassign.com V3
rem ============ Line 70 ============
if not errorlevel 128 if %V3%"==" goto L067
if errorlevel 128 set V3=
if errorlevel 128 goto L104
set V8=%path%
path %V3%
set V3=%path%:
path=%V8%
if %V3%"==%ramdrive%" if not %V1%"==" goto L031
set V8=N
rem ============ Line 80 ============
for %%Z in (%V6%) do if %V3%==%%Z set V8=F
if %V8%==F goto L042
for %%Z in (%V7%) do if %V3%==%%Z set V8=T
if %V8%==T if %V3%"==%V4%" goto L104
if not %V8%==T goto L067
:L086
ctty nul
for %%Z in (. OTH MS7 VC4) do Attrib -h -r -s %V3%\DOS\%%Z\*.*
for %%Z in (. ..\TEMP OTH MS7 VC4) do md %V3%\DOS\%%Z
rem ============ Line 90 ============
for %%Z in (Autoexec.bat Command.com) do copy /B \%%Z %V3%\DOS /Y
ctty con
if not exist %V3%\DOS\Command.com set V3=
if %V3%"==" echo Relocation attempt has failed!
if %V3%"==" goto L104
echo Copying the following files to disk %V3%
for %%Z in (OTH MS7 VC4) do copy /B \DOS\%%Z\*.* %V3%\DOS\%%Z /Y
set comspec=%V3%\DOS\Command.com
%V3%
rem ============ Line 100 ============
for %%Z in (OTH MS7 VC4) do \DOS\MS7\Attrib +r \DOS\%%Z\*.ini > nul
set V4=%V3%
%V3%\DOS\Autoexec.bat J 104
:L104
if %V3%"==" for %%Z in (%V7%) do set V3=%%Z
if not %V3%"==" if not exist %V3%\Temp\nul md %V3%\Temp
if not %V3%"==" if exist %V3%\Temp\nul set Temp=%V3%\TEMP
if %Temp%"==" echo Warning: the TEMP variable is not defined!
set dircmd= /A /O:GNE /P
rem ============ Line 110 ============
path=%V4%\DOS\OTH;%V4%\DOS\MS7;%V4%\DOS\VC4;%V4%\;%V4%\DOS
%V4%\DOS\OTH\Blue.com
if not %config%==L104 set VC=%V4%\DOS\VC4
for %%Z in (0 1 2 3 4 5 6 7 8) do set V%%Z=
if not %config%==L104 %VC%\Vc.com /TSR /no2E /noswap
goto END
:L117
%comspec% nul /f /c if exist %3\nul cd \DOS
if not exist ..\NUL if %4"==Q" goto END
rem ============ Line 120 ============
if not exist ..\NUL goto L152
%comspec% nul /f /c call %0 J 141 %3 %4
%V4%
if not exist ..\NUL if %3"==%V4%" set V5=
if not exist ..\NUL if %4"==S" goto END
if not exist ..\NUL if not %V7%"==" set V7=%3 %V7%
if not exist ..\NUL if %V7%"==" set V7=%3
if not exist ..\NUL goto END
cd \
rem ============ Line 130 ============
if not %4"==Q" echo Disk %3 is not writable! > con
if %3"==%V4%" set V5=F
if not %4"==S" if not %V6%"==" set V6=%V6% %3
if not %4"==S" if %V6%"==" set V6=%3
if not %4"==S" goto END
echo If disk %3 is write-protected, close protection > con
echo hole in its cartridge. Press any key to continue > con
pause < con > nul
goto END
rem ============ Line 140 ============
:L141
set TEMP=
%3
ver | shift
if not %4"==" goto END
cd %V4%\
if not %3"==V" goto END
echo Disk %2 is writable: > con
dir /a:ARD /-p /v %2\ | %V4%\DOS\MS7\Find.exe "otal d" > con
rem ============ Line 150 ============
goto END
:L152
cd \DOS
set V8=if errorlevel 15 if not errorlevel 16 cd \
if %4"==S" set V8=if errorlevel 20 cd \
%comspec% /f /c for %%Z in ("Label.exe %3trial" "%V8%") do %%Z
if not exist ..\nul if not %4"==S" goto END
if not %4"==S" if not %V6%"==" set V6=%V6% %3
if not %4"==S" if %V6%"==" set V6=%3
rem ============ Line 160 ============
set V8=Disk %3 either is unformatted or has an improper format
if exist ..\nul set V8=Drive %3 probably has no media inside
if %4"==V" set V8=Disk %3 is either unformatted or not inserted
echo %V8% > con
cd \
if not %4"==S" goto END
echo Insert proper media and press any key to continue > con
pause < con > nul
:END

此 AUTOEXEC.BAT 文件版本的特定功能从第 2 行的条件跳转开始,它允许执行内部子例程的递归调用。但是,当第一次解释 AUTOEXEC.BAT 时,跳转条件没有得到满足。然后,在第 3-5 行中,对环境变量 PROMPT 和 PATH 赋值。PATH 变量的值不是最终的;它将在 DOS 重新定位后由最终值替换。REASSIGN.COM 实用程序在第 8 行被第一次调用,以确定当前磁盘的字母名。REASSIGN.COM 实用程序返回的错误级别值有助于编译要测试的磁盘列表。REASSIGN.COM 实用程序在第 15 行被再次调用,以确定 XMS 内存的可用性并确定最大 XMS 块的大小。这次,返回的错误级别值提示推荐的 RAM 盘大小,该大小可以稍后安排。如果发现 XMS 内存不可用,则在第 21 行中,CONFIG 变量的值将被更改,以防止安排 RAM 盘的肯定失败尝试。

GOTO 命令在第 22 行执行一个重要的跳转:它使进一步的处理与由 CONFIG 环境变量的值定义的菜单项目用户选择保持一致。如果选择了快速加载,则跳转将引导至标签 L029 或 L031。在那里,在第 34 行中,TDSK.EXE 驱动程序安排了一个指定大小的 RAM 盘,并将 RAM 盘的字母名分配给环境变量 RAMDRIVE。现在提醒一下,RAMDRIVE 变量仅由 TDSK.EXE 驱动程序的版本 2.42 定义。如果使用了该驱动程序的早期版本或其他类似的 RAM 盘驱动程序(BITDISK.EXE、SRDISK.EXE、XMSDSK.EXE),则应使用 SET 命令(3.26)将固定值 R: 分配给 RAMDRIVE 变量。当然,RAM 盘字母名适应的机会将会丢失。

当推荐的 RAM 盘大小不够大时,第 37 行中的跳转将导致在没有 DOS 重新定位的情况下终止部分,并且 RAM 盘将专门用作临时文件的位置。但通常 RAM 盘的大小足以重新定位 DOS,然后第 36 行中的跳转将导致标签 L086 - 指向 AUTOEXEC.BAT 文件的 DOS 重新定位部分。

AUTOEXEC.BAT 文件的重定位部分被设计成可用于将 DOS 重定位到 RAM 磁盘以及任何非空的物理磁盘。因此,重定位过程首先从移除目标目录中文件的属性开始,因为否则标准 DOS 实用程序将无法覆盖那里的同名文件,甚至无法确定它们的存在。第 89 行的操作创建了目标目录的典型结构,如果它之前不存在的话。然后将文件 AUTOEXEC.BAT 和 COMMAND.COM 复制到目录 %V3%\DOS。如果复制成功,则在第 97 行,所有其他文件将被复制到其目标目录,并且将为 COMSPEC 和 V4 环境变量分配新值。第 103 行的命令将控制权转移到目标目录 %V3%\DOS 中的 AUTOEXEC.BAT 文件副本,以便不会返回到原始 AUTOEXEC.BAT 文件,并且副本的执行将从其第 105 行开始。

如果用户最初选择了用户可配置的加载替代方案,则从第 22 行跳转到标签 L047 或 L048,磁盘探索过程从那里开始。磁盘探索由子程序 L117 执行,它是同一个 AUTOEXEC.BAT 文件的一部分。它在第 53 行的循环 FOR 中递归调用,分别针对每个测试中的磁盘。子程序 L117 在第 118 行进行了首次尝试访问提交的磁盘,以检查它是否可读。如果磁盘不可读,则跳转到标签 L152,在那里第 156 行的下一个测试是区分不存在的驱动器和那些内部没有格式化介质的驱动器。不存在的驱动器将被忽略,但所有其他驱动器将被添加到不可访问磁盘列表中,该列表由 V6 环境变量的值表示。由于这些磁盘的状态可能会改变,因此可能会重复测试它们。

如果磁盘被证明是可读的,则下一个检查是可写性测试,由子程序 L141 执行。它在第 122 行被命令解释器 COMMAND.COM 的一个单独模块调用。决定性操作在第 144 行:在那里中间重定向意味着在提交的磁盘上创建一个临时文件。如果无法创建此临时文件,则同一行中的 SHIFT 命令将不会执行,虚拟参数将不会被移位,子程序 L141 将在第 145 行退出,并且第 133-134 行中的命令将添加此不可写磁盘的字母名称到不可访问磁盘列表中。如果第 144 行中的重定向可以创建临时文件,它将立即自动删除,但 SHIFT 命令将被执行,第 148 行的 ECHO 命令将显示确认消息,并且第 149 行的 DIR 命令将显示磁盘空间的使用情况。然后在第 151 行,子程序 L141 终止,并且第 126-127 行中可写磁盘的字母名称被添加到可访问磁盘列表中,该列表由环境变量 V7 的值表示。

磁盘探索子程序 L117 在第 128 行或第 135 行终止 - 这取决于测试结果。在这两种情况下,控制权都将返回到第 53 行的循环 FOR。然后,子程序 L117 将反复被调用,直到磁盘字母名称列表(由变量 V0 的值表示)结束。当所有磁盘都被测试后,结果以及建议的替代方案将由第 56-65 行的命令显示给用户。用户可以选择最合适的替代方案。用户答案由第 69 行的 REASSIGN.COM 实用程序接受。由于用户的响应可以用大写字母和小写字母表示,因此第 74-77 行的命令将任何接受的字母转换为大写,然后第 78-85 行中的条件命令将满足用户的意愿。

第 78 行中的检查是为了弄清返回的字母是否是 RAM 磁盘的字母名称。如果是,则执行跳转到标签 L031,并且所有后续事件将重复快速加载到 RAM 磁盘上的场景,就像上面描述的那样。在任何其他选择情况下,第 81 行的循环 FOR 将在不可访问磁盘列表中搜索返回的字母。如果找到该字母,则跳转到 L042。在那里,第 45 行调用 L117 探索子程序,以更彻底地测试所选磁盘。由第四个参数“S”定义的彻底调查模式意味着其他测试标准以及有关恢复磁盘可访问性的方法的更详细的提示。子程序会暂停,允许更换可移动磁盘或关闭其盒带中的写保护孔。之后,在第 53 行,对同一个子程序 L117 进行正常调用,以更新不可访问和可写磁盘列表,然后系统会再次要求用户做出选择。

用户选择将 DOS 重定位到可写磁盘由第 83 行的循环 FOR 注册。在这种情况下,执行将通过标签 L086 继续到 AUTOEXEC.BAT 文件的 DOS 重定位部分。重定位过程与上面针对 RAM 磁盘所描述的过程相同。需要注意的是,DOS 重定位过程不会使所选磁盘可引导,它只允许在从用于引导 PC 的存储设备中删除可引导介质后继续当前的 DOS 会话。由于 DOS 重定位过程不会在根目录中写入文件,因此所选磁盘的原始可引导性也不会受到影响。

用于引导 PC 的磁盘也可能是可写的,并且它的字母名称可以包含在由 V7 变量的值表示的列表中。但是将 DOS 重定位到此磁盘是毫无意义的,应该避免。这就是为什么第 84 行中的检查会拦截这种用户选择并将进一步执行转向标签 L104。因此,绕过了 DOS 重定位。由于可引导磁盘是可写的,它也将用作临时文件的存放位置。但是,普通的 1.44 Mb 软盘没有足够的空间存放临时文件;应该优先选择任何其他可写磁盘。因此,用户被给予另一个机会绕过 DOS 重定位:ESC 键按下会强制从第 73 行跳转到同一个目标标签 L104,但在这种情况下,将尝试在其他合适的磁盘上找到临时文件的存放位置。

引导菜单中的最后一项(9.09-01)是在没有驱动程序和 TSR 的情况下加载。用户选择此替代方案也会通过从第 28 行跳转到同一个标签 L104。但在此之前,在第 24 行,会调用探索子程序 L117,其第四个参数为 Q,表示静默模式。详细的探索被跳过,中间消息不会显示。在这种情况下,探索的唯一目的是为第 25-27 行中显示的警告消息准备数据。

标签 L104 表示 AUTOEXEC.BAT 文件的最后部分,所有加载替代方案都汇聚到那里。第 105 行的循环 FOR 为临时文件指定可写磁盘,如果尚未指定。第 106-114 行的操作准备 TEMP、DIRCMD 和 PATH 环境变量的最终值,并删除局部变量 V0-V8。除非选择特殊的加载替代方案 L104,否则将启动 Volkov Commander 文件管理器。AUTOEXEC.BAT 文件中的最后一个操作是无条件跳转到最终标签 END。

此版本的 AUTOEXEC.BAT 应存储在可移动可引导介质的根目录中。从 AUTOEXEC.BAT 文件的行中调用的那些文件必须存在于同一介质的指定目录中。这意味着命令解释器 COMMAND.COM 存在于根目录中,文件 ATTRIB.EXE、FIND.EXE 和 LABEL.EXE 存在于 \DOS\MS7 目录中,实用程序 REASSIGN.COM 和 BLUE.COM 存在于 \DOS\OTH 目录中,驱动程序 TDSK.EXE(版本 2.42)存在于 \DOS\DRV 目录中,文件 VC.COM 和 VC.OVL 存在于 \DOS\VC4 目录中。当然,这些目录中存在其他文件是允许的。如果您打算实施其他文件的分配或其他目录结构,则应相应地更正所有受影响的路径规范。

9.10 线性寻址实验

[edit | edit source]

人们经常认为,现代 32 位 CPU 在实模式下无法寻址超过 1088 kb 的内存空间。尽管官方数据中没有明确否定这种观点,但它仍然是错误的。自 1990 年代初以来,一些报告宣布使用未公开的 CPU 功能来访问扩展内存。Tomas Roden 被认为是这一想法的作者。

对 HIMEM.SYS (5.04-01) 驱动程序代码的分析证明了在实模式下提供访问扩展内存的所有必要元素的存在。也许,这就是 HIMEM.SYS 所做的,但对此没有任何官方确认发表。尽管这个话题已经存在相当长时间,但实模式下线性 32 位寻址的想法仍然是一个传闻话题。

现在没有必要证明在实模式下线性寻址的可能性,但需要演示该想法的有效实现。因此,本书的 9.10 部分提供了两个小型实用程序的文本:GS_LIMIT.COM 实用程序从 GS 寄存器中取消段边界保护,而 GS_DUMP.COM 显示由给定 32 位线性地址指向的内存区域的转储。图 7 显示了所提供的实用程序的效果:在调用 GS_LIMIT.COM 实用程序后,该内存区域(以前在段限制之外是无法访问的)变得可访问。

图 7

那些敢于实施所提供的示例的人将获得一个独特的机会,可以查看 32 位地址空间的所有角落和转折点。

9.10-01 段保护切换开启/关闭

[edit | edit source]

当计算的线性地址超出段边界时,CPU 的段保护会被激活,并且破坏了在实模式下从地址大小覆盖前缀 (7.02-07) 中获得任何益处的希望。然而,问题并不像乍看起来那样无望。所有现代处理器,从 80386 型号开始,都能够改变段大小。当段大小被设置为等于最高地址空间限制时,则任何计算的段地址都将被认为是允许的,并且段保护实际上会被关闭。

关闭段保护是好是坏?一方面,这将导致多任务操作系统中的完全稳定性丢失。另一方面,这将为 DOS 操作环境中的服务和诊断程序打开新的机会。作为任何有效的工具,关闭段保护既有益也有害。因此,CPU 设计人员对段保护的控制非常谨慎。只有在最高(零)特权级别下,受保护模式软件才被赋予设置任意段大小的机会。

当在最高特权级别下处于受保护模式时,操作系统核心仍然处于活动状态,那么所有其他程序都没有机会访问关键的 CPU 设置。但机会是,当 CPU 在实模式下运行时:任何实模式程序都可以将 CPU 切换到受保护模式,在“影子”寄存器中设置最大段大小,然后将 CPU 切换回实模式。之后,相对于受影响的段寄存器的线性 32 位寻址将可用,而不会造成段保护激活的风险。简而言之,这就是所提出的实用程序实现的主要思想。

选择 GS 段寄存器作为受影响段寄存器,因为实模式程序很少使用它。此外,编写良好的程序不会有意违反段限制。作者测试了许多普通的 DOS 程序,这些程序并非针对 GS 段状态的确定。所有这些程序在有和没有 GS 段限制保护的情况下表现都相同。

由于选择了 GS 段寄存器,因此建议的实用程序被命名为 GS_LIMIT.COM。它与已知的同类程序不同,因为它能够在“裸”DOS 下以及与 HIMEM.SYS 驱动程序(5.04-01)协作的情况下执行其任务。第二个区别是 GS_LIMIT.COM 能够打开和关闭段保护。为了关闭保护,您必须指定一个命令

GS_limit off

恢复 GS 段的标准 64kb 限制是通过命令启动的

GS_limit on

如果没有指定任何显示的参数(OFF 或 ON),GS_LIMIT.COM 实用程序将显示简短的帮助信息。

GS_LIMIT.COM 实用程序(长度为 662 字节)由调试器 DEBUG.EXE 生成,它是命令序列执行的结果。此命令序列应使用编辑程序写入命令文件 GS_LIMIT.SCR(如第 9 章的介绍文章中所述)。可以省略文字注释。应特别注意空行——从结尾算起的第 8 行。它必须存在,因为空行会强制 DEBUG.EXE 退出汇编器模式(7.01-04)。然后,应通过输入重定向将命令文件 GS_LIMIT.SCR 发送到调试器:Debug.exe < GS_limit.scr 命令文件 GS_LIMIT.SCR 必须包含以下行

a 100
      ;***************** GS_limit.com ******************
      ;********** Section 1: memory and parameters check
         ; 110 - target for jump from line 104
cmp          SP,2010       ; 100 Less than 8 kb allocated?
jbe          0110          ;*104 If yes, leave it as it is
mov          SP,1FFE       ; 106 Set stack's top at 8 kb
mov          BX,0200       ; 109 Request for 8 kb space
mov          AH,4A         ; 10C A call for free MCB
int          21            ; 10E         creation function
cmp byte ptr [005E],46     ;=110 Is there the "F" parameter?
jz           0148          ;*115 If yes, let's go ahead
cmp byte ptr [005E],4E     ; 117 Is there the "N" parameter?
jz           0148          ;*11C If yes, let's go ahead
mov          DX,0317       ;*11E As required parameters
mov          AL,01         ; 121      are not present, go to
jmp          020A          ;*123      display help and exit
      ;********** Section 2: data, pointers, descriptors
         ; 126 - filled from 17D, called from 187, 1F7
         ; 128 - filled from 181, accessed at 190, 1E5
         ; 12A - GDT pseudo descriptor, accessed at 1CA
         ; 12C - GDT linear address, calculated at 1B2
                           ; 126 HIMEM.SYS entrance point
dw           0000,0000
                           ; 12A GDT size (3 descriptors)
db           18 00
                           ; 12C GDT address (offset = 130)
db           30 01 00 00
                           ; 130 "Empty" descriptor 0000
db           00 00 00 00 00 00 00 00
                           ; 138 4-Gb size descriptor 0008
db           FF FF 00 00 00 93 8F 00
                           ; 140 64-kb size descriptor 0010
db           FF FF 00 00 00 93 00 00
      ;******** Section 3: CPU and protection mode checks
         ; 148 - target for jumps from lines 115, 11C
         ; 162 - target for jump from line 158
pushf                      ;=148 Push 2 copies of original
pushf                      ; 149    flag's states into stack
pop          CX            ; 14A Load a copy into CX
xor          CH,70         ; 14B Invert bits 0C, 0D, 0E
push         CX            ; 14E Send inverted states via
popf                       ; 14F         stack into flags
pushf                      ; 150         register, and then
pop          AX            ; 151         via stack into AX
popf                       ; 152 Restore initial flag states
xor          AH,CH         ; 153 Set non-coincident bits
test         AH,40         ; 155 Is it a 16-bit CPU?
jz           0162          ;*158 If no, check protection
mov          AL,04         ; 15A If yes, go to display
mov          DX,024F       ;*15C         error message
jmp          020A          ;*15F         024F and exit
test         AH,30         ;=162 Is protected mode set?
jz           016F          ;*165 If no, go ahead
mov          AL,08         ; 167 If yes, go to display
mov          DX,0271       ;*169         error message
jmp          020A          ;*16C         0271 and exit
      ;********** Section 4: address bus A20 preparation
         ; 16F - target for jump from line 165
         ; 18B - target for jump from line 176
mov          AX,4300       ;=16F Check whether HIMEM.SYS
int          2F            ; 172         driver is installed
cmp          AL,80         ; 174 If no, go for direct
jnz          018B          ;*176             access to A20
mov          AX,4310       ; 178 If yes, query for entrance
int          2F            ; 17B              point address
mov          [0126],BX     ;*17D Store entrance point
mov          [0128],ES     ;*181     address in memory cells
mov          AH,05         ; 185 Call for A20 bus
call far     [0126]        ;*187      activation function
call         021C          ;=18B Check A20 state and jump
jnz          01A7          ;*18E      if A20 bus is active
mov word ptr [0128],0001   ;*190 Mark: bus A20 is not active
mov          AL,FF         ; 196 Attempt to activate A20 bus
call         022F          ;*198          by subroutine 022F
call         021C          ;*19B Check A20 state and jump
jnz          01A7          ;*19E        if A20 bus is active
mov          AL,02         ; 1A0 If A20 is not active, go
mov          DX,029C       ;*1A2        to display error
jmp          020A          ;*1A5        message and exit
      ;*********** Section 5: preparation of GDT table
         ; 1A7 - target for jumps from lines 18E, 19E
                           ;=1A7 Data size override prefix
db           66
xor          AX,AX         ; 1A8 Write zero into EAX
mov          AX,CS         ; 1AA Copy CS segment into AX
mov          CL,04         ; 1AC Shift 4 bits leftwards
db           66
shl          AX,CL         ; 1AF Obtaining linear address
db           66
add          [012C],AX     ;*1B2 GDT's linear address
      ;******** Section 6: selectors, ban on interrupts
         ; 1C3 - target for jump from line 1BE
mov          CX,0008       ; 1B6 0008 - 4-Gb size selector
cmp byte ptr [005E],46     ; 1B9 Is there "F" parameter ?
jz           01C3          ;*1BE If yes, leave CX=0008
mov          CX,0010       ; 1C0 If no, let it be CX=0010
cli                        ;=1C3 Prohibit interrupts
mov          AL,80         ; 1C4 Prohibit NMI by
out          70,AL         ; 1C6       sending byte 70h
in           AL,71         ; 1C8       into port 70h
      ;********* Section 7: transitions to PM and back
                           ; 1CA = Lgdt fword ptr [012A]
db           0F 01 16 2A 01
                           ; 1CF = mov EAX,CR0
db           0F 20 C0
or           AL,01         ; 1D2 Set protected mode
                           ; 1D4      bit (= mov CR0,EAX)
db           0F 22 C0
                           ; 1D7 Load GS (= mov GS,CX)
db           8E E9
and          AL,FE         ; 1D9 Clear protected mode
                           ; 1DB      bit (= mov CR0,EAX)
db           0F 22 C0
      ;********** Section 8: return to former state
         ; 1F5 - target for jump from line 1EC
mov          AL,7F         ; 1DE Enable NMI by
out          70,AL         ; 1E0      sending byte 7Fh
in           AL,71         ; 1E2      into port 70h
sti                        ; 1E4 Enable interrupts
cmp word ptr [0128],0001   ;*1E5 Check A20 state mark
jb           01FB          ;*1EA If mark=0000, do nothing
ja           01F5          ;*1EC If mark>0001 - to HIMEM.SYS
mov          AL,FD         ; 1EE If mark=0001, restore
call         022F          ;*1F0      A20 state by means
jmp          01FB          ;*1F3      of subroutine 022F
mov          AH,06         ;=1F5 Call for HIMEM.SYS function
call far     [0126]        ;*1F7 switching A20 bus OFF
      ;********** Section 9: message display and exit
         ; 1FB - target for jumps from lines 1EA, 1F3
         ; 208 - target for jump from line 203
         ; 20A - jump target from lines 123, 15F, 16C, 1A5
mov          DX,02E9       ;=1FB "Limit set" message offset
cmp byte ptr [005E],46     ; 1FE Is there "F" parameter?
jnz          0208          ;*203 If yes, specify offset for
mov          DX,02B9       ; 205      message "limit is OFF"
mov          AL,00         ;=208 Specify zero errorlevel
push         AX            ;=20A Jumps target point
mov          AH,40         ; 20B      to display messages
mov          BX,DX         ; 20D Read into CX a number
mov          CX,[BX-02]    ; 20F      characters to display
mov          BX,0001       ; 212 0001 = STDOUT's handle
int          21            ; 215 Send message to STDOUT
pop          AX            ; 217 Return errorlevel into AL
mov          AH,4C         ; 218 Call for DOS's program
int          21            ; 21A       termination function
      ;******** Section 10: A20 state check subroutine
         ; 21C - target for calls from lines 18B, 19B
push         DS            ;=21C Save DS segment in stack
xor          SI,SI         ; 21D Write zero into SI
mov          DS,SI         ; 21F now DS:SI=0000:0000
mov          DI,F0F1       ; 221 Set ES:DI=F0F1:F0F0
mov          ES,DI         ; 224    in order to obtain
dec          DI            ; 226    F0F10h + F0F0h = 100000h
cld                        ; 227 Set count UP
mov          CX,0010       ; 228 Set repeat limit 16 bytes
repz                       ; 22B      and repeat while equal
cmpsb                      ; 22C Is there a foldover?
pop          DS            ; 22D Restore DS, return result
ret                        ; 22E       via state of ZF flag
      ;******** Section 11: A20 state change subroutine
         ; 22F - target for calls from lines 198, 1F0
push         AX            ;=22F Save command code in stack
call         0241          ;*230 Wait controller's readiness
mov          AL,D1         ; 233 D1 - first byte of command,
out          64,AL         ; 235       sent into port 64h
call         0241          ;*237 Wait controller's readiness
pop          AX            ; 23A Restore command code in AX
out          60,AL         ; 23B     and send it to port 60h
call         0241          ;*23D Wait controller's actuation
ret                        ; 240            and then return
      ;******** Section 12: controller waiting subroutine
         ; 241 - target for calls from lines 230, 237, 23D
         ; 245 - cycle return point from line 249
push         CX            ;=241 Save CX state in stack
mov          CX,FFFF       ; 242 Write cycle's limit into CX
in           AL,64         ;=245 Read a byte from port 64h
test         AL,02         ; 247 Check state of the 2nd bit
loopnz       0245          ;*249 Repeat, if port is busy
pop          CX            ; 24B Restore CX state from stack
ret                        ; 24C Return from subroutine
      ;*********** Section 13: messages
db           20 00
         ; 24F - 1st message, mentioned at 15C
db           0D 0A 09 "16-bit processor"
db           20 "can" 27 "t suit" 0D 0A
db           29 00
         ; 271 - 2nd message, mentioned at 169
db           0D 0A 09 "GS_limit can" 27 "t run"
db           20 "in protected mode" 0D 0A
db           1B 00
         ; 29C - 3rd message, mentioned at 1A2
db           0D 0A 09 "Line A20 control error" 0D 0A
db           2E 00
         ; 2B9 - 4th message, mentioned at 205
db           0D 0A 09 "GS segment limit"
db           20 "protection is turned OFF" 0D 0A
db           2C 00
         ; 2E9 - 5th message, mentioned at 1FB
db           0D 0A 09 "GS segment limit"
db           20 "protection is restored" 0D 0A
db           7F 00
         ; 317 - 6th message (help), mentioned at 11E
db           0D 0A "GS_limit.com removes the GS segment"
db           20 "limit protection or can restore it back"
db           0D 0A "Usage examples:" 0A 0D 09 09 "GS_lim"
db           "it off" 0A 0D 09 09 "GS_limit on" 0D 0A
         ; 396 End of assembler text

n GS_limit.com
rBX
0000
rCX
0296
w
q

汇编器文本从第 1 部分开始,以普通方式释放分配的内存剩余部分(A.12-7 的注释 5)。然后执行参数是否存在检查。参数表示形式的选择方式是参数自动大写并写入程序 PSP 中的第一个 FCB(A.07-1 的注释 4)。如果未指定所需的参数,则在第 123 行跳到最后部分,在那里显示帮助信息。之后,实用程序结束,留下错误级别代码 01。

当指定了所需的命令行参数时,执行将从第 3 部分的第 148 行继续,在那里将检查两个条件:CPU 的适用性和其在实模式下的操作。这些检查的本质在附录 A.11-4 的注释 2 和 3 中进行了描述。如果任一条件不满足,则将跳到最后部分,在那里显示相应的错误信息,执行将结束,留下错误级别代码 04 或 08。

当 CPU 检查成功通过时,执行将从第 4 部分的第 16F 行继续,在那里准备地址总线线路 A20 的状态。这种准备的必要性并不明显,但已被证实。地址总线线路 A20 必须打开。如果已安装 HIMEM.SYS 驱动程序,则应调用其函数 AH=05(A.12-3)以打开地址总线线路 A20。如果未安装 HIMEM.SYS 驱动程序,则应调用第 11 部分的子程序 022F,该子程序尝试通过键盘控制器打开地址总线线路 A20(有关详细信息,请参见 A.11-3 的注释 1)。

地址总线线路 A20 的初始状态和最终状态在第 18B 行和第 19B 行通过调用第 10 部分的子程序 021C 进行检查。如果尝试打开地址总线线路 A20 失败,则在第 1A5 行将跳到程序的最后部分。在那里将显示错误信息,程序的执行将结束,留下错误级别代码 02。通常情况下,至少尝试打开一次地址总线线路 A20 会成功,然后执行将从第 5 部分的第 1A7 行继续。

第 5 部分的命令为 GDT 表准备一个伪描述符。GDT 表的地址由 CPU 从此伪描述符复制到其 GDTR 寄存器中。在第 1AC 行,段地址 CS 通过左移 4 位转换为线性地址。第 1B2 行的求和形成伪描述符模板(在第 2 部分的第 12A-12F 行)中 GDT 表的线性地址。

GDT 表描述符的详细结构显示在附录 A.12-2 中。第二和第三个描述符为数据段准备。在第 138 行,第二个描述符(选择器 0008)定义了 4Gb 段大小。它应该用于关闭 GS 段保护。在第 140 行,第三个描述符(选择器 0010)定义了 64kb 段大小。此描述符应用于恢复 GS 段的正常保护。在 CPU 切换到保护模式之前,应在 CX 寄存器中准备与请求的任务相对应的选择器。在第 6 部分的第 1B6-1C0 行的命令将在这两个选择器(0008 和 0010)之间进行选择。第 6 部分的后续命令禁止中断,包括 NMI(有关详细信息,请参见文章 8.01-03 的注释 1)。

第 7 部分的第一个命令是 LGDT(= 加载全局描述符表),它将数据从 GDT 伪描述符加载到 CPU 的 GDTR 寄存器中。DEBUG.EXE “不知道” LGDT 命令,因此其代码(0F 01 16)由 DB 指令作为数据引入。LGDT 命令中的最后两个字节——2A 01——表示从 DS 寄存器中的段地址算起的伪描述符的偏移量,即伪描述符模板在汇编器文本的第 2 部分中准备的行的数字 012A。

CPU 可以通过 INT 15\AH=89h 函数(8.01-78)切换到保护模式,该函数需要一个更大的 GDT 表,并重新编程两个中断控制器。由于宣告的任务意味着返回实模式,因此稍后需要对中断控制器进行反向重新编程。为了避免过度复杂,这里通过设置 CPU 的 CR0 寄存器(A.11-4)中的 PE 位(PE = 保护使能)来执行切换到保护模式的操作。因此,第 1CF 行的命令将 CR0 寄存器的内容复制到 AX 中,在此副本中设置 PE 位,然后在第 1D4 行将修改后的副本写回 CR0 寄存器。关于 CR0 寄存器的命令(7.03-58 的注释 1)“未知”于 DEBUG.EXE,因此在第 1CF 行和第 1D4 行中作为 DB 指令后的数据引入。

当然,仅设置 PE 位不足以获得功能齐全的保护模式,但在这种情况下,不需要完全功能。在保护模式下,GS_LIMIT.COM 实用程序必须执行一个操作:将 CX 寄存器中准备好的选择器(0008 或 0010)写入 GS 段寄存器。将 CX 内容复制到 GS 的命令(7.03-58 的注释 2)“未知”于 DEBUG.EXE,因此其代码在第 1D7 行中作为 DB 指令后的数据引入。将准备好的选择器复制到 GS 寄存器中会导致 GS_LIMIT.COM 实用程序的主要目标的隐藏事件:新内容,包括新段限制,从 GDT 描述符写入 CPU 的 GS “影子”寄存器,该描述符由复制的选择器指向。

当达到顶峰时,正是开始下降的时候。首先,必须清除 PE 位,该位仍然保存在 EAX 寄存器中,因为它已从 CPU 的控制寄存器 CR0 复制。第 1DB 行的命令将这个两次修改的代码写回 CR0 寄存器。因此,CPU 返回到实模式。第 8 部分的命令取消中断禁止,并通过先前改变状态的那些设施恢复地址总线线路 A20 的初始状态。第 9 部分的第 1FB-215 行的命令选择合适的最终信息并显示出来。在第 218-21A 行,对 DOS 的 INT 21\AH=4Ch 函数的调用终止了 GS_LIMIT.COM 实用程序的执行。

在终止后,GS_LIMIT.COM 实用程序留下错误级别代码,这可能对确定 CPU 的操作模式具有独立的意义

GS_limit on > nul
if errorlevel 7 echo Processor runs in V86 mode
if not errorlevel 7 echo Processor runs in real mode

GS_LIMIT.COM 实用程序的最后一个特点是,留在 GS 段寄存器中的值保留选择器的状态。这使人们能够对 CPU 中的错误线性寻址进行实验。在任何操作之后,将新值写入 GS 寄存器后,其内容将获得段地址的正确状态。

9.10-02 线性寻址转储的显示

[edit | edit source]

本文介绍的 GS_DUMP.COM 实用程序在屏幕上显示 128 字节的内存转储,几乎与 DEBUG.EXE 对其“D”命令(6.05-04)的响应相同。主要区别在于 GS_DUMP.COM 实用程序不接受普通地址(段:偏移量),而是接受 32 位线性地址,这些地址能够在 4Gb 地址空间内定义任何访问点。当段保护处于活动状态时,GS_DUMP.COM 实用程序会对所有超出 GS 段的访问请求响应错误信息。但是,当 GS 段保护由 GS_LIMIT.COM 实用程序(9.10-01)关闭时,可以显示 4Gb 地址空间内任何内存区域的转储。

除了其明显的演示效果之外,GS_DUMP.COM 实用程序还提供了许多关于在实模式下线性寻址的实际实现的答案。在命令行中,GS_DUMP.COM 的名称后面必须跟随线性地址,该地址由最多 8 个十六进制数字组成,例如

GS_dump.com FFFE0

指定的线性地址被解释为绝对地址,从地址空间的开头算起,与 GS 寄存器的实际内容无关。在线性地址之后,可能会有两个可选参数之一

A——不要改变地址总线线路 A20 的初始状态;
R——将零写入 GS 段寄存器。

带有可选参数的命令行可能如下所示,例如

GS_dump.com FFFE0 A

当地址总线线路 A20 最初没有激活时,带有“A”可选参数的 GS_DUMP.COM 实用程序将在地址 100000h 处显示地址空间折返。默认情况下,GS_DUMP.COM 实用程序激活地址总线线路 A20,并始终显示打开的地址空间,无论其实际初始状态如何。

GS_DUMP.COM 实用程序(长度为 1291 字节)由调试器 DEBUG.EXE 生成,它是命令序列执行的结果。此序列应使用编辑程序写入命令文件 GS_DUMP.SCR(如第 9 章的介绍中所述)。可以省略注释。应特别注意空行——从结尾算起的第 8 行。它必须存在,因为空行会强制 DEBUG.EXE 退出汇编器模式(7.01-04)。然后,应通过输入重定向将命令文件 GS_DUMP.SCR 发送到调试器

DEBUG.EXE < GS_DUMP.SCR

命令文件 GS_DUMP.SCR 必须包含以下行

a 100
      ;**************** GS_dump.com **************
      ;********* Section 1: preliminary preparations
         ; 110 - target for jump from line 104
cmp          SP,2010       ; 100 Less than 8 kb allocated?
jbe          0110          ;*104 If yes, leave it as it is
mov          SP,1FFE       ; 106 Set stack's top at 8 kb
mov          BX,0200       ; 109 Request for 8 kb space
mov          AH,4A         ; 10C A call for free MCB
int          21            ; 10E          creation function
push         CS            ;=110 Prepare
pop          DS            ; 111           DS = CS
db           66
xor          BX,BX         ; 113 Write zero in EBX register
      ;********* Section 2: CPU checks
         ; 12F - target for jump from line 125
pushf                      ; 115 Push 2 copies of original
pushf                      ; 116    flag's states into stack
pop          CX            ; 117 Load a copy into CX
xor          CH,70         ; 118 Invert bits 0C, 0D, 0E
push         CX            ; 11B Send altered bits via
popf                       ; 11C    stack to flags register,
pushf                      ; 11D        and then again
pop          AX            ; 11E        via stack into AX
popf                       ; 11F Restore initial flag states
xor          AX,CX         ; 120 Set non-coincident bits
test         AH,40         ; 122 Is it a 16-bit CPU?
jz           012F          ;*125 If no, go to next check
mov          AL,02         ; 127 If yes, go to display
mov          DX,03D6       ;*129        error message
jmp          02BF          ;*12C            03D6 and exit
test         AH,30         ;=12F Is protected mode set?
jz           0163          ;*132 If no, bypass section 3
      ;********* Section 3: protected mode checks
         ; 14B - target for jump from line 141
mov word ptr [03D4],0483   ;*134 Prepare message address
mov          AX,1687       ; 13A Send trial request
int          2F            ; 13D           for DPMI server
or           AX,AX         ; 13F Is DPMI server installed?
jnz          014B          ;*141 If not, go further
mov          AL,08         ; 143 If yes, go to display
mov          DX,03FB       ;*145       error message
jmp          02BF          ;*148            03FB and exit
push         DS            ;=14B Save DS segment in stack
mov          DS,BX         ; 14C 0 = interrupt table segment
mov          DS,[019E]     ; 14E Load EMM's segment into DS
cmp word ptr [0014],4249   ; 152 Is it IBM's driver?
pop          DS            ; 158 Restore DS from stack
jz           0163          ;*159 Continue, if IBM's driver
mov          AL,02         ; 15B If not, go to display
mov          DX,041E       ;*15D      error message
jmp          02BF          ;*160            041E and exit
      ;********* Section 4: linear address reading
         ; 163 - target for jumps from lines 132, 159
         ; 16A - cycle return target from line 19C
         ; 17B - target for jump from line 175
         ; 188 - target for jump from line 17E
         ; 194 - target for jumps from lines 16F, 179
mov          CX,0004       ;=163 Preset 4 bit shift
cld                        ; 166 Set SI count upwards
mov          SI,005D       ; 167 Set start address
lodsb                      ;=16A Load one ASCII code into AL
sub          AL,30         ; 16B Translate ASCII code
cmp          AL,09         ; 16D If it is decimal digit,
jbe          0194          ;*16F        append it to EBX
sub          AL,07         ; 171 Translate A-F letter codes
cmp          AL,0A         ; 173 Is it character below "A"?
jb           017B          ;*175 If yes, check the reason
cmp          AL,0F         ; 177 Is it one of letters A-F?
jbe          0194          ;*179 If yes, go append it to EBX
cmp          SI,005E       ;=17B Is it the first iteration?
jnz          0188          ;*17E If not, let's check further
mov          AL,01         ; 180 If yes, go to display
mov          DX,04F1       ;*182        help message 04F1
jmp          02BF          ;*185          and then exit
cmp          AL,E9         ;=188 Is last character a space?
jz           019E          ;*18A If yes, no more characters
mov          AL,01         ; 18C If no, go to display
mov          DX,04CB       ;*18E         error message
jmp          02BF          ;*191             04CB and exit
                           ;=194 Data size override prefix
db           66
shl          BX,CL         ; 195 Shift EBX 4 bits leftwards
or           BL,AL         ; 197 Insert next character in BL
cmp          SI,0064       ; 199 Is 8th iteration reached?
jbe          016A          ;*19C If not, repeat the cycle
      ;******** Section 5: address bus line A20 activation
         ; 19E - target for jump from line 18A
         ; 1C3 - target for jump from line 1AC
         ; 1D3 - target for jump from line 1A3
         ; 1D8 - target for jump from line 1C6
cmp byte ptr [006D],41     ;=19E Is "A" parameter present?
jz           01D3          ;*1A3 If yes, bypass section 5
mov          AX,4300       ; 1A5 Is the HIMEM.SYS
int          2F            ; 1A8         driver installed?
cmp          AL,80         ; 1AA If no, bypass appeals
jnz          01C3          ;*1AC         to HIMEM.SYS driver
push         BX            ; 1AE Save BX contents in stack
mov          AX,4310       ; 1AF Call for HIMEM.SYS
int          2F            ; 1B2      entrance point address
mov          [03C0],BX     ;*1B4 Store entrance point
mov          [03C2],ES     ;*1B8     address in memory cells
mov          AH,05         ; 1BC Call for bus line A20
call far     [03C0]        ;*1BE         activation function
pop          BX            ; 1C2 Restore former BX contents
call         02D3          ;=1C3 Is bus line A20 active?
jnz          01D8          ;*1C6 If yes, bypass next attempt
mov word ptr [03C2],0001   ;*1C8 If no, set a mark
mov          AL,FF         ; 1CE Activate bus line A20
call         02EB          ;*1D0          by subroutine 02EB
call         02D3          ;=1D3 Is bus line A20 active?
jz           01DD          ;*1D6 If yes, message 0445
mov byte ptr [0445],24     ;=1D8        must be made invalid
      ;******* Section 6: indication of GS segment address
         ; 1DD - target for jump from line 1D6
         ; 1E6 - target for jump from line 1E2
cmp byte ptr [006D],52     ;=1DD Is "R" parameter present?
jz           01E6          ;*1E2 If yes, don't read GS
                           ; 1E4 = MOV SI,GS
db           8C EE
                           ;=1E6 = MOV GS,SI
db           8E EE
                           ; 1E8 = PUSH GS
db           0F A8
pop          [03D0]        ;*1EA Store GS contents in 3D0
call         0339          ;*1EE Send 0Dh 20h to STDOUT
call         0349          ;*1F1 Display word stored in 3D0
mov          DX,0445       ;*1F4 Display
call         0330          ;*1F7         message 0445
mov          DX,045B       ;*1FA Display
call         0330          ;*1FD         message 045B
      ;********* Section 7: datum point calculation
db           66
xchg         SI,BX         ; 201 Copy address into ESI
mov          BX,SI         ; 203 Return in BX 2 bytes only
and          BX,000F       ; 205 Select the rightmost digit
neg          BL            ; 209 Get datum point
and          SI,FFF0       ; 20B Now ESI - base address
db           66
mov          DI,SI         ; 210 Copy ESI into EDI
db           66
xchg         [03D0],DI     ;*213 Exchange [03D0] with EDI
db           66
shl          DI,CL         ; 218 In EDI - linear address GS
mov          DX,[03D4]     ;*21A Prepare message number
db           66
sub          SI,DI         ; 21F Calculate offset in ESI
jnb          0226          ;*221 If below zero, change
mov          DX,0460       ;*223      message number to 0460
      ;********* Section 8: flags and pointers replacement
         ; 226 - target for jump from line 221
                           ;=226 Data size override prefix
db           66
pushf                      ; 227 Push EFLAGS into stack
mov          BP,SP         ; 228 Copy stack pointer into BP
mov          AL,[BP+02]    ; 22A Read and store the
mov          [03C4],AL     ;*22D          third flag's byte
and byte ptr [BP+02],FE    ; 230 Clear alignment flag
db           66
popf                       ; 235 Write EFLAGS back
in           AL,21         ; 236 Read port 21h mask
mov          [03C5],AL     ; 238          byte and store it
or           AL,60         ; 23B Set bits 05 and 06
out          21,AL         ; 23D Disable IRQ5 and IRQ6
mov          [03C8],DS     ;*23F Fill segment cells in
mov          [03CC],DS     ;*243         handler's addresses
push         BX            ; 247 Save BX contents in stack
mov          AL,0D         ; 248 Exchange handlers
call         03A0          ;*24A    for interrupt INT 0D and
call         03A0          ;*24D        for interrupt INT 0E
pop          BX            ; 250 Restore BX contents
      ;********* Section 9: trial reading attempt
         ; 251 - cycle return target from line 289
mov          [03CE],SI     ;=251 Save SI for comparison
db           65 67
lodsb                      ; 257 Attempt to read a byte
cmp          SI,[03CE]     ;*258 Has SI value changed?
jnz          0266          ;*25C If yes, go to main cycle
call         0342          ;*25E Display linear address
call         0321          ;*261 Calculate next address
jmp          0286          ;*264 Jump to check new address
      ;********* Section 10: main line display cycle
         ; 266 - target for jump from line 25C
         ; 26E - cycle return target from line 273
         ; 278 - cycle return target from line 27D
         ; 286 - target for jump from line 264
                           ;=266 Data size override prefix
db           66
dec          SI            ; 267 Restore index in ESI
call         0349          ;*268 Display linear address
call         0318          ;*26B Intermediate correction
call         0362          ;=26E Display a byte in AL
inc          BL            ; 271 Increment bytes count by 1
loop         026E          ;*273 1st line display cycle
call         0309          ;*275 Intermediate correction
call         0379          ;=278 Display a byte as ASCII
inc          BL            ; 27B Increment bytes count by 1
loop         0278          ;*27D 2nd line display cycle
call         0339          ;*27F Send 0Dh 20h to STDOUT
mov          DX,[03D4]     ;*282 Update message number
cmp          BL,80         ;=286 Is display line the last?
jb           0251          ;*289 If not, display next line
      ;********* Section 11: return to initial states
         ; 2B4 - target for jump from line 2AB
mov          AL,0D         ; 28B Restore handlers for
call         03A0          ;*28D        interrupt INT 0D and
call         03A0          ;*290        for interrupt INT 0E
mov          AL,[03C5]     ;*293 Read former mask byte and
out          21,AL         ; 296         send it to port 21h
db           66
pushf                      ; 299 Read EFLAGS into stack
mov          BP,SP         ; 29A Copy stack pointer into BP
mov          AL,[03C4]     ;*29C Read and restore former
mov          [BP+02],AL    ; 29F            3rd flag's byte
db           66
popf                       ; 2A3 Restore EFLAGS states
cmp word ptr [03C2],0001   ;*2A4 Check mark of A20 state
jb           02BA          ;*2A9 If 0000, don't touch A20
ja           02B4          ;*2AB IF >0001 - go use HIMEM.SYS
mov          AL,FD         ; 2AD Restore A20 states with
call         02EB          ;*2AF             subroutine 02EB
jmp          02BA          ;*2B2 Jump to final operations
mov          AL,06         ;=2B4 Call HIMEM.SYS function
call far     [03C0]        ;*2B6       in order to close A20
      ;******** Section 12: program's termination
         ; 2BA - target for jumps from lines 2A9, 2B2
         ; 2BF - jumps from lines 12C, 148, 160, 185, 191
mov          DX,0442       ;=2BA Offset for line's end bytes
mov          AL,00         ; 2BD Happy end errorlevel
push         AX            ;=2BF Save errorlevel in stack
mov          AH,09         ; 2C0 Display final
int          21            ; 2C2               message
pop          AX            ; 2C4 Restore errorlevel
mov          AH,4C         ; 2C5 Call for termination
int          21            ; 2C7     function, return to DOS
      ;******* Section 13: interrupt's 0D and 0E handlers
         ; 2C9 - must be specified in cell 3CA
         ; 2CC - must be specified in cell 3C6
mov          DX,04A5       ;=2C9 Call target for Int 0E
mov          BP,SP         ;=2CC Call target for Int 0D
add word ptr [BP+00],+03   ; 2CE Correct return address
iret                       ; 2D2         in stack and return
      ;******** Section 14: state check for bus line A20
         ; 2D3 - target for calls from lines 1C3, 1D3
push         DS            ;=2D3 Save states of
push         CX            ; 2D4       DS and CX registers
db           66
xor          SI,SI         ; 2D6 Write zero into ESI
push         SI            ; 2D8 Save SI=0 in stack
mov          DS,SI         ; 2D9 Now DS:SI=0000:0000
mov          DI,F0F1       ; 2DB Prepare ES:DI=F0F1:F0F0
mov          ES,DI         ; 2DE    in order to get address
dec          DI            ; 2E0    F0F10h + F0F0h = 100000h
cld                        ; 2E1 Set count upwards
mov          CX,0010       ; 2E2 Set count limit 16 bytes
repz                       ; 2E5 Repeat while equal
cmpsb                      ; 2E6 Is there a foldover?
pop          SI            ; 2E7 Restore states of
pop          CX            ; 2E8    registers and return,
pop          DS            ; 2E9        keeping result as
ret                        ; 2EA            state of ZF flag
      ;***** Section 15: bus line A20 control subroutine
         ; 2EB - target for calls from lines 1D0, 2AF
push         AX            ;=2EB Save command in stack
call         02FD          ;*2EC Wait controller readiness
mov          AL,D1         ; 2EF Send 1st command's
out          64,AL         ; 2F1        byte into port 64h
call         02FD          ;*2F3 Wait controller readiness
pop          AX            ; 2F6 Send 2nd command's
out          60,AL         ; 2F7        byte into port 60h
call         02FD          ;*2F9 Wait for controller's
ret                        ; 2FC   actuation and then return
      ;********* Section 16: wait for controller readiness
         ; 2FD - target for calls from lines 2EC, 2F3, 2F9
         ; 301 - cycle return target from line 305
push         CX            ;=2FD Save CX contents in stack
mov          CX,FFFF       ; 2FE Store cycles limit in CX
in           AL,64         ;=301 Read state of port 64h
test         AL,02         ; 303 Check readiness bit
loopnz       0301          ;*305 Repeat, if not ready
pop          CX            ; 307 Restore CX and return
ret                        ; 308      to the caller program
      ;********* Section 17: intermediate correction
         ; 309 - target for call from line 275
         ; 318 - target for call from line 26B
sub          BL,10         ;=309 Return count one line back
push         BX            ; 30C Save BX contents in stack
mov          BL,10         ; 30D 10h = increment value
db           66
add          [03D0],BX     ;*310 Linear address correction
db           66
sub          SI,BX         ; 315 Offset correction
pop          BX            ; 317 Restore former BX contents
call         0393          ;=318 Twice send a space
call         0393          ;*31B        character to STDOUT
mov          CL,10         ; 31E Preset bytes count
ret                        ; 320 Return from subroutine
      ;********* Section 18: transition to next dump line
         ; 321 - target for call from line 261
         ; 330 - target for calls from lines 1F7, 1FD
         ; 339 - target for calls from lines 1EE, 27F
add          BL,10         ;=321 Increment bytes count by 16
push         BX            ; 324 Save BX contents in stack
mov          BL,10         ; 325 Set increment by 16
db           66
add          [03D0],BX     ;*328 Linear address correction
db           66
add          SI,BX         ; 32D Offset correction
pop          BX            ; 32F Restore former BX contents
mov          DI,DX         ;=330 Message address - into DI
mov          AH,09         ; 332 Call for DOS's STDOUT
int          21            ; 334         output function
mov byte ptr [DI],24       ; 336 Disable message
mov          AL,0D         ;=339 Send carriage return
call         0395          ;*33B          command to STDOUT
call         0393          ;*33E Send a space to STDOUT
ret                        ; 341 Return from subroutine
      ;********* Section 19: cell 3D0 contents display
         ; 342 - target for a call from line 25E
         ; 349 - target for calls from lines 1F1, 268
         ; 354 - target for jump from line 35E
         ; 361 - target for jump from line 347
mov          DI,DX         ;=342 Is the requested
cmp byte ptr [DI],24       ; 344          message disabled?
jz           0361          ;*347 If yes, don't display it
mov          AL,0A         ;=349 Send a line feed
call         0395          ;*34B          command to STDOUT
push         BX            ; 34E Save BX contents in stack
xor          BL,BL         ; 34F Turn off limit check
mov          DI,03D3       ; 351 Load 1st byte offset in DI
mov          AL,[DI]       ;=354 Copy that byte into AL
call         0368          ;*356 Call AL byte translation
dec          DI            ; 359 Turn to next byte
cmp          DI,03D0       ;*35A Is it the last byte?
jnb          0354          ;*35E If not, repeat the cycle
pop          BX            ; 360 Restore former BX contents
ret                        ;=361 Return from subroutine
      ;********* Section 20: AL translation subroutine
         ; 362 - target for a call from line 26E
         ; 368 - target for a call from line 356
call         0393          ;=362 Call to display a space
db           67 65
lodsb                      ; 367 Read GS:[linear address]
push         CX            ;=368 Save CX contents in stack
mov          CL,04         ; 369 Preset 4 bits shift in CL
shl          AH,CL         ; 36B Clear 4 bits in AH
shl          AX,CL         ; 36D Separate half-bytes from AL
shr          AL,CL         ; 36F Clear 4 bits in AL
pop          CX            ; 371 Restore former CX contents
call         0384          ;*372 Call for AH display
call         0384          ;*375 Call for AL display
ret                        ; 378 Return from subroutine
      ;********* Section 21: one character display
         ; 379 - target for a call from line 278
         ; 384 - target for calls from lines 372, 375
         ; 38E - target for calls from lines 37E, 382, 38A
         ; 393 - called from lines 318, 31B, 33E, 362
         ; 395 - called from lines 33B, 34B, jump from 391
                           ;=379 Copy one character into AL
db           67 65
lodsb                      ; 37B    from GS:[linear address]
cmp          AL,20         ; 37C Is it 20h value or more?
ja           038E          ;*37E Values AL < 20h replace
mov          AL,2E         ; 380      with dots and jump
jmp          038E          ;*382         to check boundary
xchg         AH,AL         ;=384 Exchange contents AH - AL
add          AL,30         ; 386 Translate 0 - 9 into ASCII
cmp          AL,39         ; 388 Is it 0 - 9 or A - F ?
jbe          038E          ;*38A Leave digits 0 - 9 intact
add          AL,07         ; 38C Translate A - F into ASCII
cmp          BL,80         ;=38E Check boundary
jb           0395          ;*391 If beyond boundary,
mov          AL,20         ;=393     replace it with space
push         AX            ;=395 Save states of AX and
push         DX            ; 396      DX registers in stack
mov          AH,02         ; 397 Call for DOS's function
mov          DL,AL         ; 399      of character output
int          21            ; 39B             into STDOUT
pop          DX            ; 39D Restore former states
pop          AX            ; 39E      of AX and DX registers
ret                        ; 39F Return from subroutine
      ;********* Section 22: handler's exchange subroutine
         ; 3A0 - called from lines 24A, 24D, 28D, 290
mov          AH,35         ;=3A0 Call for handler's address
int          21            ; 3A2   reading function in ES:BX
mov          DI,AX         ; 3A4 Prepare in DI a memory cell
shl          DI,1          ; 3A6        offset: DI = AX * 2
shl          DI,1          ; 3A8 350D*4=D434 350E*4=D438
sub          DI,D06E       ;*3AA D434-D06E=3C6 D438-D06E=3CA
push         DS            ; 3AE Store contents of DS and
push         DX            ; 3AF       DX registers in stack
lds          DX,[DI]       ; 3B0 DS:DX - pointer to a cell
mov          AH,25         ; 3B2 Call for handler's address
int          21            ; 3B4           writing function
pop          DX            ; 3B6 Restore former contents
pop          DS            ; 3B7      of DS and DX registers
mov          [DI],BX       ; 3B8 Store address of the former
mov          [DI+02],ES    ; 3BA     handler in memory cells
inc          AL            ; 3BD Prepare next number in AL
ret                        ; 3BF Return from subroutine
      ;******** Section 23: data and addresses
         ; 3C0 - HIMEM.SYS offset, in lines 1B4, 1BE, 2B6
         ; 3C2 - HIMEM.SYS segment, in lines 1B8, 1C8, 2A4
db           00 00 00 00
         ; 3C4 - 3rd byte of EFLAGS, in lines 22D, 29C
db           00
         ; 3C5 - mask for port 21h, in lines 238, 293
db           00
         ; 3C6 - offset of this cell is calculated in 3AA
         ; 3C8 - segment of 0Dh handler, written from 23F
db           CC 02 00 00
         ; 3CA - offset of this cell is calculated in 3AA
         ; 3CC - segment of 0Eh handler, written from 243
db           C9 02 00 00
         ; 3CE - place to store SI, accessed from 251, 258
db           00 00
         ; 3D0 - linear address - 1EA, 213, 310, 328, 35A
         ; 3D3 - most significant address byte, from 351
db           00 00 00 00
         ; 3D4 - place for message address - 134, 21A, 282
db           71 04
      ;******** Section 24: messages
         ; 3D6 1st message, mentioned at 129
db           0D 0A 'Error: 16-bit machine can' 27
db           't suit' 0D 0A 24
         ; 3FB 2nd message, mentioned at 145
db           0D 0A 'Error: can' 27 't run under WINDOWS'
db           0D 0A 24
         ; 41E 3rd message, mentioned at 15D
db           0D 0A 'Error: incompatible EMM386 version'
         ; 442 4th virtual message, mentioned at 2BA
db           0D 0A 24
         ; 445 5th message, mentioned at 1D8, 1F4
db           09 'Line A20 is disabled' 24
         ; 45B 6th message, mentioned at 1FA
db           'GS=' 20 24
         ; 460 7th message, mentioned at 223
db           09 '- below GS base' 24
         ; 471 8th message, addressed in line 3D4
db           09 '- above GS limit' 24
         ; 483 9th message, mentioned at 134
db           09 'GS range or privilege violation' 20 24
         ; 4A5 10th message, mentioned at 2C9
db           09 'Page isn' 27 't initialized or is'
db           20 'swapped' 24
         ; 4CB 11th message, mentioned at 18E
db           0D 0A 'Address error: invalid character(s)' 0A
         ; 4F1 12th message (help), mentioned at 182
db           0D 0A 09 'GS_dump.com - linear GS address'
db           20 'dump utility' 0D 0A 'Usage examples:'
db           0D 0A 09 09 'GS_dump 002FABCD' 0D 0A 09 09
db           'GS_dump 002FABCD A' 0D 0A 09 09
db           'GS_dump 002FABCD R' 0D 0A 20
db           '002FABCD - linear address example, up to'
db           20 '8 hexadecimal digits long' 0D 0A 09
db           'A - option: don' 27 't try to enable' 20
db           'line A20' 0D 0A 09 'R - option: reset GS'
db           20 'to zero' 0D 0A 24
         ; 60B End of assembler text

n GS_dump.com
rBX
0000
rCX
050B
w
q

程序从第 1 部分开始,以普通方式释放分配的内存剩余部分(A.12-7 的注释 5)。CPU 检查和保护模式检查的执行方式与 GS_LIMIT.COM 程序(9.10-01)完全相同。但是,在保护模式的情况下,GS_DUMP.COM 实用程序不会立即终止;调查将在第 3 部分继续。如果保护模式不是由 WINDOWS 操作系统控制,而是由兼容的 IBM 的 EMM386.EXE 驱动程序(5.04-02)的版本 4.50 控制,则执行将继续进行。

在第 4 部分,命令行中指定的线性地址被读入 EBX 寄存器。在读取过程中,ASCII 代码字符被转换为“原始”代码,并进行正确性检查。如果发现除了正确的十六进制数字之外的任何其他内容,则 GS_DUMP.COM 实用程序将显示错误信息并终止。

在 GS_DUMP.COM 的第 5 部分,地址总线线路 A20 的激活方式与 GS_LIMIT.COM 程序(9.10-01)的第 4 部分中的激活方式相同。

GS_DUMP.COM 程序中的重要特定操作从第 6 部分的 GS 寄存器准备开始。需要进行准备,因为 GS 寄存器的内容可能保留选择器的状态。为了消除不确定性,第 1E6 行的命令执行写入 GS 寄存器操作。即使 GS 寄存器的内容应该保留,第 1E6 行的命令也会在 GS 寄存器中写入先前第 1E4 行的命令读取的 GS 寄存器的值。写入操作后,GS 寄存器的内容获得段地址的正常状态。

第 6 部分最后几行的命令显示 GS 段地址。之后,第 7 部分第 201-20B 行的命令计算基准点字节和基地址,即转储中一行第一个字节的线性地址。在第 218 行,左移 4 位将 GS 段地址转换为线性地址。在第 21F 行,计算基地址和 GS 线性地址之间的差值。此差值仅表示该偏移量,该偏移量对于读取用户在命令行中指定的绝对地址处的数据是必需的。

由于绝对地址读取操作不一定在允许的段限制内进行,因此它可能会调用生成异常 INT 0D、INT 0E(8.01-09 的注释 6 和 7)和 INT 11(8.01-42 的注释 1)。除非事先采取一些特殊预防措施,否则这些异常中的每一个都会导致计算机挂起。可以通过清除 EFLAGS 寄存器(A.11-4)中的未对齐控制位 (12h) 来防止异常 INT 11。因此,第 226-235 行的命令将 EFLAGS 寄存器内容复制到堆栈中,第三个读取字节的初始状态存储在 03C4 内存单元中,未对齐控制位仅在堆栈中清除,并从堆栈中将更改后的结果写回 EFLAGS 寄存器。

无法防止异常 INT 0D 和 INT 0E,处理器在尝试访问受保护的内存区域时不可避免地会生成这些异常。可以拦截对异常处理程序的调用,但在实模式下,很难区分 CPU 对异常的调用和通过中断控制器的 IRQ 5 和 IRQ 6 线接收到的外部中断调用 (8.01-09)。为了避免混淆,GS_DUMP.COM 程序禁止在显示转储所需的时间内通过 IRQ 5 和 IRQ 6 线接收外部中断。为此,第 236-23D 行的命令读取 21h 端口的掩码,将它的初始状态保存到内存单元 03C5 中,在该掩码中设置位 05 和 06,并将更改后的掩码写回端口 21h。

INT 0D 和 INT 0E 异常处理程序的适当替代品已预先在 GS_DUMP.COM 程序的第 13 部分中准备就绪。第 13 部分中的命令在堆栈中进行返回地址更正,然后将控制权返回给中断程序。返回地址更正可以防止在单个命令执行循环中挂起,并使中断程序能够从下一条命令恢复执行。准备好的处理程序替换由第 8 部分的第 23F-24D 行中的命令激活。这些命令使用实际段地址填充内存单元 03C8 和 03CC,然后两次调用子例程 03A0,该子例程将中断表中的中断处理程序地址与内存单元 03C8 和 03CC 中准备好的地址交换。从那时起,GS_DUMP.SYS 程序消除了由 CPU 异常 INT 0D 和 INT 0E 引起的计算机挂起的威胁。

由于地址空间中的访问权限可以以不小于 16 字节的离散度更改,因此可以通过第 9 部分的第 257 行中进行的一次尝试访问来确定 16 字节段落内所有字节的可读性。当可以访问尝试字节时,CPU 在第 257 行执行 LODSB 命令 (7.03-53) 时,会将 SI 寄存器中的偏移量加 1。但是,当读取请求导致段保护启动时,LODSB 命令不会执行,SI 寄存器不会增加。第 258 行中的命令将 SI 寄存器中的当前值与保存在 03CE 内存单元中的前一个值进行比较。比较值相等是段保护启动的证据。如果 CPU 对尝试访问做出段保护启动的响应,则进一步尝试访问同一段落中的任何字节都是无用的。对于此类转储行,第 9 部分中的以下命令显示线性地址,显示有关保护启动的消息,并计算转储下一行的线性地址。然后跳转到第 286 行,在那里检查当前显示的转储行号。如果它不是转储中的最后一行,则返回到尝试访问过程的开头,该过程将对转储的下一行重复。

如果第 258 行中比较的 SI 寄存器内容值不同,则来自第 25C 行的跳转将导致第 10 部分中的主要转储显示过程。这些过程包括调用 0349 子例程以显示基线性地址,一个显示 16 个转储字节的循环 26E-273,然后是一个显示相同 16 个字节作为 ASCII 代码的循环 278-27D。如果当前转储行不是最后一行,则返回到第 9 部分的开头,在那里将对转储的下一行重复所有相同的操作。转储处理完成后,第 11 部分中的命令将所有受影响元素恢复到初始状态。子例程 03A0 被调用两次以恢复中断 INT 0D 和 INT 0E 的前一个处理程序地址。第 293-2A3 行中的命令恢复端口 21h 中的先前掩码和 EFLAGS 寄存器的先前状态。第 2A4-2B6 行中的命令恢复地址总线线 A20 的先前状态。

恢复初始状态后,GS_DUMP.COM 实用程序继续进行结论性部分 12。如果转储已成功显示,则不会显示最终消息,并且错误级别将设置为零值。但是,如果尝试显示转储失败,则第 2BF-2C4 行中的相同操作将显示错误消息。在第 2C7 行,调用 DOS 的 INT 21\AH=4Ch 函数终止 GS_DUMP.COM 实用程序的执行。

注释 1:GS_DUMP.COM 实用程序留下的错误级别代码可能构成一个单独的兴趣。特别是,GS_DUMP.COM 实用程序在尝试在 WINDOWS 操作系统的“DOS 窗口”中执行后,会留下最大的错误级别代码 8。注册以错误级别代码 8 (3.15-03) 终止可以防止执行那些不应该在“DOS 窗口”中启动的程序,例如,TURN_OFF.COM 实用程序 (9.05-02)。

9.11 MS-DOS 7 加载备选方案

[编辑 | 编辑源代码]

为了保护数据和主计算机的操作系统,当从外部驱动器或可移动介质(软盘、闪存卡或光盘)加载 DOS 时,任何 DOS 实验都是最安全的。这种在文章 9.11-01 中描述的 DOS 加载方式在紧急服务中很常见。但是,对于常规的 DOS 使用来说,它不够可靠,因为在这种情况下,任何可移动介质的使用寿命都不够长。

对于常规的 DOS 使用,多备选加载通过特殊的启动管理器或某些操作系统的加载程序来安排。文章 9.11-02 和 9.11-03 描述了将 WINDOWS-2000 和 WINDOWS-XP 加载程序以及 MS-DOS 7 本身用于此目的的情况,因为最初它是为了启动 WINDOWS-95/98 操作系统而设计的。当然,MS-DOS 7 作为启动管理器的功能很差,但它有一个有用的功能:与可加载 BIOS 扩展兼容。也许,对于备选加载,您将不得不选择 MS-DOS 7。

9.11-01 从可移动介质加载 MS-DOS 7

[编辑 | 编辑源代码]

当将有效的引导扇区和 DOS 的系统文件写入该软盘时,普通的 1.44 Mb 软盘将变成可引导的。在 MS-DOS 7 下,这些操作由 SYS.COM 实用程序 (6.24) 执行。在 Windows 下,您可以通过互联网下载可引导软盘的自解压缩文件镜像,例如,从主机 http://1gighost.com/ed/jamiephiladelphia/ 或从网站 http://anbcomp.com/files/bootdisk/。没有将 DOS 重新定位到 RAM 盘的版本(文件 boot95b.exe、boot98c.exe、boot98sc.exe)是比较好的,因为标准的可引导软盘采用了一种重新定位方法,这种方法在现代计算机中可能会失败。由于同样的原因,可引导软盘的形成不应被命令为标准的格式化过程。但是,提到的缺点并不存在于由 Windows XP 下的格式化过程准备的带有 MS-DOS 8 的可引导软盘中。

获得了 DOS 的标准可引导软盘后,您很可能对它的内容感到不满意。您将不得不自己准备最少的实用程序集。许多用于 MS-DOS 7 的实用程序可以从 Windows-95/98 版本中获得。在 http://www.netbootdisk.com/http://www.multiboot.ru/ 中可以找到带有更好软件集的可引导软盘镜像。本文档的第 6.25、9.01、9.04、9.09 部分中配置文件中提到的驱动程序和实用程序可以被视为作者对编译可引导软盘的软件集的建议。当然,在任何情况下,配置文件中的所有规范和路径都必须与驱动程序和实用程序在所选可引导介质中的实际放置完全一致。您尝试从软盘加载 MS-DOS 7 可能会失败,因为 BIOS 设置中的参数规范不足(有关在文章 1.01 中输入 BIOS 设置的信息)。您的软盘驱动器类型必须在 BIOS 设置的“主”页面中正确指定。此外,BIOS 设置的“引导”页面必须为软盘驱动器分配比固定驱动器更高的引导设备优先级。在旧的计算机中,引导设备优先级由磁盘的字母名称顺序定义;因此,在“引导”页面中,字母名称列表必须从字母“A”开始,该字母表示第一个软盘驱动器。在现代计算机中,第一个软盘驱动器必须是可移动驱动器列表中的第一个,并且从可移动驱动器加载的优先级必须高于从固定驱动器加载的优先级。

不幸的是,软盘上的活动操作系统太慢,会导致严重磨损。它会将软盘的寿命缩短到 10-20 个小时。如果将 DOS 从可启动软盘迁移到 RAM 盘(示例见 9.04、9.09),功能会变得更快更可靠。但是,即使在后一种情况下,软盘也不能被认为是一种理想的可启动介质。缓慢而嘈杂的 DOS 迁移过程会令人不快。除此之外,软盘的总容量现在似乎也不够用了。光盘由于其更高的读取速度、更大的容量和更长的使用寿命,似乎更具吸引力。但是,长寿命仅适用于通过矩阵复制技术生产的具有固定内容的光盘。

可写入光盘采用完全不同的存储原理:有机染料的衰减。每次读取光学写入的轨道都会降低对比度。因此,可写入光盘的活动使用寿命与软盘类似。关于读取速度的传言也是半真半假:光驱的轨道寻道时间约为 100 毫秒,而硬盘的轨道寻道时间约为 2 毫秒。由于使用寿命短且访问速度慢,可启动光盘需要 DOS 迁移,与软盘一样。

一些复杂性来自于光盘文件系统与 DOS 内核 "已知" 的文件系统之间的差异。因此,DOS 无法立即从光盘加载。需要将另一个磁盘的镜像(带有 "已知" 文件系统 FAT12 或 FAT-16 的可启动磁盘)写入光盘。计算机的 BIOS 系统必须能够从该镜像模拟一个逻辑磁盘,然后才能从该模拟的逻辑磁盘加载 MS-DOS 7。如果该镜像的原型是可启动软盘,则将字母名 "A" 分配给模拟的逻辑磁盘,而真正的软驱将被分配下一个字母名 "B"。几乎所有用于写入光盘的程序都能够将可启动软盘复制到镜像,并将该镜像写入光盘,从而使其可启动。为此,不需要对可启动软盘进行特殊准备。将 MS-DOS 7 迁移到 RAM 盘的配置非常适合。此类配置的示例见第 9.04 和 9.09 部分。当然,配置应该包含那些启用访问光盘空间(超出模拟逻辑磁盘)的驱动程序和 TSR(5.08-04、5.09-04),从而消除其容量不足的问题。

除非光驱被计算机的 BIOS 系统注册为可启动设备,否则 DOS 无法从光盘加载。在 BIOS 设置程序的 "BOOT" 页面上显示了已注册的可启动设备列表。在这个列表中,不同的 BIOS 版本指定设备类别(例如,CD-ROM)或特定类型的设备。设备在这个列表中的位置决定了它的优先级。如果光驱在列表中,则需要指定优先级顺序,这将强制 BIOS 在访问任何固定磁盘驱动器之前访问光驱。之后,您只需要在 BIOS 开始启动测试之前及时将可启动光盘插入驱动器即可。

在现代计算机中,由于接口参数规范错误,BIOS 系统可能会无法注册驱动器。要检查 IDE 接口的参数,需要打开 BIOS 设置程序的 "Main" 页面,然后按下 "IDE Configuration" 按钮。将会打开一个新页面,其中参数 "Onboard IDE operate Mode" 应该设置为 "Compatible Mode",而参数 "Combined Mode Option" 应该设置为与实际的 IDE 连接类型相对应的值。如今,大多数内置光驱使用平行 P-ATA 连接。如果计算机中没有串行 S-ATA 连接的设备,则参数 "Combined Mode Option" 应该设置为 "P-ATA only",否则应该首选 "P-ATA+S-ATA"。任何接口参数的更改都不会立即生效,而是在重启后生效。

要设置 USB 接口的参数,需要打开 BIOS 设置程序的 "Advanced" 页面,然后按下 "USB Configuration" 按钮。将会打开一个新页面,其中参数 "USB Function"(或 "USB Controller")必须设置为 "Enabled"。这足以通过驱动程序(5.07-05)访问 USB 设备。但是,那些允许通过 USB 总线连接可启动设备的 BIOS 系统提供了更多可选参数。在 AMI BIOS 的最新版本中,存在参数 "Legacy USB support";如果打算通过 USB 总线连接可启动设备,则必须启用此参数。应该提醒的是,启用 "Legacy USB support" 参数可能会导致 BIOS 和 USB 总线驱动程序(5.07-05)之间发生冲突,因此在这种情况下不应该加载 USB 总线驱动程序。如果 "USB Configuration" 页面中存在针对 USB 2.0 控制器的单独参数,则这些参数也应该设置为 "Enabled",并且数据传输速度应该设置为 "HiSpeed"。

由于 USB 总线允许复杂的连接配置,因此由于对 BIOS 启动测试施加的限制,带有 USB 接口的可启动设备的注册可能会失败。要消除这些限制,需要打开 BIOS 设置程序的 "Boot" 页面,然后按下 "Boot setting configuration" 按钮。将会打开一个新页面,其中 "Quick Boot" 选项应该被禁用。当然,BIOS 启动测试将延长约 3 秒。

当 BIOS 系统在 USB 总线上注册至少一个存储设备时,在现代计算机中,您将能够从 BIOS 设置程序的 "USB Configuration" 页面打开下一个页面 "USB Mass Storage Device Configuration"。在后一个页面中,列出了所有已注册的 USB 存储设备,并显示了每个设备应用的模拟方法。模拟方法规范不当可能是导致启动失败的另一个原因。默认情况下,模拟方法是自动确定的,但测试时驱动器中没有介质、存在未格式化的介质,甚至在测试过程中插入介质都可能导致错误。

当然,模拟方法必须与设备类别相对应:对于磁性硬盘驱动器,应该将其定义为 "Hard Disk",对于外部光驱,应该将其定义为 "CDROM",对于外部软驱,应该将其定义为 "Floppy"。但有时不清楚哪种设备类别应该对应于例如闪存卡。在这种情况下,应根据以下情况进行决定:容量为 512 MB 或更大的闪存卡始终格式化为硬盘。对于容量较小的闪存卡,可以应用特殊的模拟方法 - "Forced FDD",这意味着该卡将被 BIOS 呈现为 "Big Floppy",无论其实际格式如何。

如果您打算对存储介质进行格式化,则模拟方法必须与所需的格式类型相对应。不准确的模拟方法规范(例如 "Forced FDD" 和 "Auto")可能会导致不可预测的结果。新的未格式化的存储设备由 BIOS 注册,但不会被分配为逻辑磁盘的字母名。对于其初始格式化,应该首选现代程序,例如 "Partition Magic" 版本 8.01 或更高版本。其中一个主分区必须设置为活动分区。重启后,新分区将被分配字母名,然后可以使用 SYS.COM 实用程序(6.24)将活动分区设为可启动分区。如果格式化的介质确实是硬盘,则不需要将 MS-DOS 7 迁移到 RAM 盘的配置。

大多数 BIOS 系统不会控制那些存储设备,这些设备的介质没有模拟,而是真正格式化为 "Big Floppy",即没有 MBR。通常,此类介质可以通过驱动程序访问,例如 ASPIDISK.SYS(5.07-03、5.07-05)。可以使用文章 6.13 中注释 5 中提到的实用程序将 MBR 写入此类介质。重启后,BIOS 将接受带有 MBR 的介质作为 HDD。被 BIOS 控制后,此类介质可以被视为硬盘,可以被格式化并设为可启动。

当至少一个存储设备被 BIOS 系统识别为硬盘时,BIOS 设置程序从其 "Boot" 页面启用打开另一个页面 "Hard Disk Drives"。在那里显示了所有已注册的硬盘驱动器的列表。但是 BIOS 仅将这些硬盘驱动器中的第一个视为可启动设备。如果要从注册为硬盘驱动器的外部存储设备启动计算机,则应在 BIOS 设置程序的 "Hard Disk Drives" 页面中的驱动器列表中将该设备设置为第一位。同时,所选存储设备的规范将显示在 "Boot Device Priority" 页面上的可启动设备列表中。在那里,所选存储设备不一定必须设置为第一位,如果此时优先级更高的设备内部没有可移动存储介质。

模拟方法 "Hard Disk" 适合带有可移动介质的存储设备,但有一个要求:自计算机开机以来,必须始终在该存储设备中存在相同的可移动介质。同一个磁盘驱动器中的另一个可移动磁盘无法读取。如果存储设备被模拟为硬盘,则将分配一个硬盘的字母名。但如果计算机开机时该设备中不存在可移动存储介质,则该存储设备将根本不会获得字母名。这可能是有益的,如果您的 USB 适配器有多个插槽,用于不同类型的闪存卡,并且您不想为那些肯定不会使用的卡类型分配字母名。

模拟为 "Floppy" 或 "Forced FDD" 的存储设备会获得字母名,无论其可移动介质是否存在。作者测试过的那些现代 BIOS 系统只分配给这些存储设备字母名 A: 和 B:,并且不允许交换最初插入的可移动介质。如果计算机有几个这样的设备,则只分配给前两个设备字母名;其余这样的设备是不可访问的。如果您打算从 BIOS 注册为软驱的存储设备启动计算机,则必须在 BIOS 设置程序的 "Removable Drives" 页面的列表中将其设置为第一个。

现在,带有 USB 接口的闪存卡适配器和固态存储设备已经变得很普遍。此类设备可用于加载 MS-DOS 7 及其他操作系统。为此,带有 USB 接口的存储设备必须被 BIOS 控制,就像外部硬盘驱动器一样。应该首先尝试模拟方法 "Hard Disk"。如果存储设备变得可访问,则它被格式化为硬盘。否则,应该首选模拟方法 "Forced FDD"。在这种情况下,需要检查 BIOS 设置程序的 "Removable Drives" 页面列表中对应存储设备的位置。它必须在那里设置为第一位或第二位,否则 BIOS 不会给该存储设备分配字母名,然后就无法访问它。

如果存储设备被识别为硬盘,则需要检查其主分区是否具有活动(可启动)分区的状态。由于 "Partition Magic" 程序只处理真正的 HDD,因此在 "假" HDD(物理上由固态存储设备表示)中,应该检查分区的状态,并在必要时使用 FDISK.EXE 实用程序进行更改,该实用程序以参数 /fprmt/actok 启动(6.13)。许多 BIOS 版本不支持从带有 FAT-12 文件系统的 HDD 分区启动,但 FDISK.EXE 不可避免地将所有 16 MB 或更小的分区标记为 FAT-12。如果需要,可以读取分区表(9.02-02)并将其写入(9.02-03),将文件系统标识符从 01h 更改为 06h (A.13-6)。之后,应使用 FORMAT.COM 实用程序的 /z:1 参数(6.15)格式化相关的小分区,从而获得所需的 FAT-16 文件系统。

固态可启动介质准备的第二阶段包括使用 SYS.COM 实用程序 (6.24) 写入引导扇区和复制 DOS 系统文件。之后应准备 DOS 加载配置。首选配置是将 DOS 重新定位到 RAM 磁盘 (9.04, 9.09),因为许多类型的固态存储设备速度较慢,并且所有类型的此类设备都承受有限数量的覆盖周期。根据固态介质上的所需配置,应形成目录结构,并且目录应填充所需的文件。

准备可启动固态介质后,应首先在“硬盘驱动器”页面上的列表或“可移动驱动器”页面上的列表中设置相应的存储设备 - 这取决于应用哪种仿真方法。如有必要,在此阶段可以将仿真方法“硬盘”更改为“强制 FDD”。当相应的存储设备名称出现在 BIOS 设置程序的“启动设备优先级”页面上时,应将其设置在相对于所有其他设备的最高优先级位置,这些设备此时已准备好启动。然后,您需要关闭 BIOS 设置程序,保存最新设置。计算机重新启动后,将从准备好的固态介质开始 DOS 加载过程。

9.11-02 Windows-2000/XP 作为 MS-DOS 7 的引导管理器

[编辑 | 编辑源代码]

当 MS-DOS 7 从硬盘启动时,它需要一个具有 FAT-16 或 FAT-32 文件系统的 primary 分区。与 MS-DOS 7 不同,Windows-2000/XP 操作系统可以安装在 primary 分区和非 primary 分区中,并使用 FAT-32 或 NTFS 文件系统进行格式化。这两种组合都不应被视为无望的。即使计算机中的整个硬盘都格式化为一个 NTFS 分区,您也可以使用 Partition Magic 程序为 DOS 分区释放一些磁盘空间,然后任何合适的引导管理器(例如 System Commander)都可以安排可选加载任何选定的操作系统。下面描述的方法是一个更简单的方法,它只使用 Windows-2000/XP 操作系统的专有加载程序。

包含 FAT-32(或 FAT-16)文件系统的分区的分区结构可能继承自以前的操作系统。Windows-2000/XP 可以安装在 Windows-95/98 之上,要么失去加载以前操作系统的机会,要么保留加载以前操作系统的机会。如果您的计算机在打开后显示了一个包含“以前的操作系统”项的启动菜单,并且如果此以前的操作系统只是 Windows-95/98,那么您磁盘上的分区结构至少部分继承了。在这种情况下,为了安排可选加载 MS-DOS 7,您不必更正 Windows-2000/XP 的加载规范,而要更正以前 Windows-95/98 操作系统的保留配置文件。此类更正的示例显示在文章 9.11-03 中。

如果启动菜单未出现或不包含“以前的操作系统”行,则应确定 BIOS 规范中声称可启动的磁盘上的文件系统类型。最常见的是磁盘 C:。启动 Explorer 程序后,您需要突出显示可启动磁盘,使用鼠标右键打开上下文菜单,并在上下文菜单中选择“属性”项。将出现一个窗口,其中显示文件系统类型。如果此类型是“NTFS”,则 MS-DOS 7 无法安装在此分区中。但是,如果文件系统类型是“FAT”,则可以安装 MS-DOS 7。安装将需要更正 BOOT.INI 文件记录并将 MS-DOS 7 文件复制到该磁盘上。

官方规定的更正 BOOT.INI 文件记录的路径从“开始”菜单开始,然后依次经过“设置”项、“控制面板”、图标“性能和维护”、项“系统”、按钮“高级”、按钮“启动和恢复设置”到最后的按钮“编辑”。在同一个窗口中,应该设置一个非零的菜单指示时间。应保存 BOOT.INI 的更正版本,然后将打开的窗口用“确定”按钮单击两次关闭。此外,BOOT.INI 文件的内容可以通过 MSCONFIG.EXE 实用程序更正。它可以从“开始”菜单中的“运行”项打开的窗口中的命令行启动。

BOOT.INI 文件中的语法与 MSDOS.SYS (5.01-01) 和 CONFIG.SYS (9.04-01, 9.09-01) 文件中的语法相同。每行都表示一个单独的规范,该规范以一个名称开头,与它的值之间用等号分隔。文件节的标题用方括号括起来。BOOT.INI 文件中有两个节:第一个指定加载参数,第二个列出可以加载的操作系统。以下是一个 BOOT.INI 文件的示例,它能够加载任何 3 个操作系统 

[boot loader]
timeout=30
default=multi(0)disk(0)rdisk(0)partition(3)\WINDOWS
[operating systems]
multi(0)disk(0)rdisk(0)partition(3)\WINDOWS="Microsoft Windows XP...
multi(0)disk(0)rdisk(0)partition(2)\WINNT="Microsoft Windows 2000...
C:\bootsect.dos="Microsoft DOS 7.10"

在所示示例中,第 5 行和第 6 行被截断到页面的大小,在实际文件中,这些行更长。但这并不重要:无论如何,截断行的末尾不应更改。从行文本可以清楚地看出,显示的 BOOT.INI 文件能够加载安装在单个硬盘的第 3 个分区和第 2 个分区中的 Windows-XP 和 Windows-2000 操作系统,以便第一个分区保持空闲。由于第一个分区保持空闲,因此它可用于单独安装 MS-DOS 7。所示示例中的最后一行就是您必须自己添加的这一行,以便加载 MS-DOS 7,特别是从磁盘 C: 加载。双引号中的词语只是菜单项的名称,对加载过程没有影响。

BOOT.INI 文件的行由 NTLDR 加载程序解释。后者将“理解”您写入的这一行作为在磁盘 C: 的根目录中查找引导扇区 BOOTSECT.DOS 文件映像的命令。如果 BOOTSECT.DOS 文件不存在,则应在 MS-DOS 7 下重新创建它,MS-DOS 7 可以从软盘或其他可移动介质加载。首先,应将当前引导扇区保存到一个文件中,如文章 9.02-01 中所述。然后,应使用 SYS.COM 实用程序 (6.24) 覆盖引导扇区。新的引导扇区也必须类似地复制到文件中,这次复制到名为 BOOTSECT.DOS 的文件中。之后,必须从先前保存的文件恢复引导扇区的以前内容,也如文章 9.02-01 中推荐的那样。用于覆盖引导扇区的 SYS.COM 实用程序会同时将系统文件 COMMAND.COM 和 IO.SYS 复制到根目录。只有在执行引导扇区的代码后,IO.SYS 加载程序才会获得控制权。

接下来的任务是确保可以在其适当路径上找到 MS-DOS 7 的所有必要文件。在可启动磁盘的根目录中,必须存在以下文件 

IO.SYS – 隐藏系统文件:MS-DOS 7 的加载程序和核心;
MSDOS.SYS – 隐藏系统文件:加载参数 (5.01-01);
CONFIG.SYS – 配置文件 (9.01-01, 9.04-01, 9.11-03);
AUTOEXEC.BAT – 配置文件 (9.01-02, 9.04-02, 9.11-03);
COMMAND.COM – 只读文件:命令解释器 (6.04)。

此列表中的三个文件 - MSDOS.SYS、CONFIG.SYS、AUTOEXEC.BAT - 您必须自己编写。可以在列表中每行对应的括号内找到可以找到示例的文档。

除了根目录文件外,MS-DOS 7 还需要配置文件行中指定的驱动程序,以及各种程序。后者可以从第 6 章中描述的程序中选择。驱动程序和程序应存储在同一个磁盘上的目录结构中。本书中所有配置文件的示例都设计用于相同的目录结构 : 驱动程序存储在 \DOS\DRV 目录中,原始 MS-DOS 7 文件存储在 \DOS\MS7 目录中,文件管理器存储在 \DOS\VC4 目录中,所有其他文件存储在 \DOS\OTH 目录中。您可以安排其他目录结构,但无论如何,它必须与配置文件中的引用完全一致。

应遵循哪种特定的配置示例是一个选择问题。有时,文章 9.01 中显示的最简单版本就足够了。更常见的是,应该首选将 DOS 重新定位到 RAM 磁盘的某些版本 (9.04, 9.09)。为了与其他操作系统的实验,文章 9.11-03 中显示了另一个版本。您可以自由选择和编写最适合您自身任务的 MS-DOS 7 加载配置。

注释 1:当在 MS-DOS 7 或 Windows-95/98 之上安装 Windows-2000/XP 操作系统时,后者会将属性 H(隐藏)和 S(系统)分配给根目录中的 COMMAND.COM 解释器。因此,不会执行批处理文件中对命令解释器的调用。使用 ATTRIB.EXE 实用程序 (6.01) 可以删除提到的属性。

9.11-03 MS-DOS 7 作为引导管理器

[编辑 | 编辑源代码]

Windows-95/98 操作系统使用 MS-DOS 7 作为 primary 加载程序,但不提供其单独配置的机会。同时,这不仅对 MS-DOS 7 本身,而且对可选加载那些能够在 DOS 下启动的其他操作系统都是可能的,并且可能是很有益的。

下面是一个 CONFIG.SYS 文件,它能够加载 Windows-95/98、两种 MS-DOS 7 配置以及其他两个操作系统:QNX 和 Linux。作者已经测试了 QNX 和 Linux 的版本,它们需要具有 FAT-16 文件系统的可启动磁盘。如果您不需要比在 Windows-95/98 和 MS-DOS 7 之间进行选择更多的操作,那么可以省略与 QNX 和 Linux 相关的所有行,并且可启动磁盘上的文件系统也可以是 FAT-32。

此 CONFIG.SYS 以 [menu] 节开始。在菜单中选择一个项将进一步将解释引导到 [L08]–[L25] 节:每个节对应于其中一个备选方案。节以 AUTOEXEC.BAT 文件中相应的标签命名。节之间的空行仅用于视觉清晰度,可以省略。

[menu]
numlock off
menuitem=L08, Real mode MS-DOS 7
menuitem=L09, Protected mode MS-DOS 7
menuitem=L16, Microsoft's Windows-98
menuitem=L24, QNX v.6.0
menuitem=L25, Linux Slackware v.3.5
menudefault=L16,20

[L08]
device=\DOS\DRV\Himem.sys /v
device=\DOS\DRV\Umbpci.sys
include=S08
include=S09

[S08]
accdate c- d- edos=
high,umb,noauto
buffershigh=30,0
fileshigh=30,0
lastdrivehigh=Z
multitrack=On
fcbshigh=1,0
stackshigh=9,256

[L09]
device=\DOS\DRV\Himem.sys /v
device=\DOS\DRV\Emm386.exe ram v
include=S08
include=S09

[S09]
country=007,866,C:\DOS\DRV\Country.sys
devicehigh=\DOS\DRV\Dblbuff.sys
devicehigh=\DOS\DRV\Ifshlp.sys
devicehigh=\DOS\DRV\Setver.exe
devicehigh=\DOS\DRV\Atapimgr.sys /W:6 /T:5 /LUN
devicehigh=\DOS\DRV\Oakcdrom.sys /D:CD001
installhigh=\DOS\DRV\Shsucdx.com /D:CD001 /L:N /~+ /R /Q

[L16]
device=\WINDOWS\Himem.sys
include=S08
Country=007,866,C:\WINDOWS\COMMAND\Country.sys
devicehigh=\WINDOWS\Dblbuff.sys
devicehigh=\WINDOWS\Ifshlp.sys
devicehigh=\WINDOWS\Setver.exe
devicehigh=\WINDOWS\COMMAND\Display.sys con=(ega,,1)

[L24]
device=\QNX\boot\bin\loadqnx.sys C:\QNX\boot\fs\qnxbas.ifs

[L25]
device=\DOS\DRV\Himem.sys
include=S08
install=\linux\loadlin.exe @\linux\linparam.scr

[common]
installhigh=\DOS\DRV\Mouse.com
shell=C:\COMMAND.COM C:\ /E:2016 /L:511 /U:255 /p

所示版本的 CONFIG.SYS 文件中的 [L08][L09] 节将 MS-DOS 7 加载为一个单独的操作系统。[L09] 节通过 EMM386.EXE 驱动程序 (5.04-02) 对 UMB 内存区域提供普通类型的访问,但 [L08] 节通过 UMBPCI.SYS 驱动程序 (5.04-04) 提供另一种类型的访问,无需将 CPU 切换到保护模式。后一种备选方案对于使用实模式程序的实验是必要的,例如使用 DUSE.EXE (5.07-05) 和 GS_LIMIT.COM (9.10-01)。每个操作系统都应该有自己的目录树。单独的目录树的存在不是必需条件,但无论如何都是可取的:目录内容的独立性使多备选方案加载更加可靠。

加载 WINDOWS-95/98 的 [L16] 节包含许多通常默认情况下使用的规范,但这里明确显示了这些规范,以保持与 MS-DOS 7 的单独加载一致。[L16] 节中的路径对应于在将 WINDOWS-95/98 安装到可启动磁盘上时自动创建的普通目录结构。但是,如果您的计算机中的目录结构不同,则配置文件中的所有引用必须与实际结构相对应。

选择菜单项 `[L24]` 和 `[L25]` 会将控制权转移到类 Unix 操作系统的加载器,这些加载器不会将控制权返回给 MS-DOS 7 加载器。因此,`[common]` 部分中的命令将不会执行,唯一返回的方式是通过 SHUTDOWN 命令并重启。`[L24]` 和 `[L25]` 部分中的路径反映了在解压缩其发行包过程中为每个类 Unix 操作系统创建的目录结构。

选择菜单项 `[L08]`、`[L09]` 或 `[L16]` 可以继续执行 `[common]` 部分中的命令。在最后一行,SHELL 命令将控制权转移到 COMMAND.COM 解释器。之后,MS-DOS 7 加载的最后阶段开始 - 解释配置文件 AUTOEXEC.BAT 中的命令。

由于 `[L08]` 和 `[L09]` 的进一步处理是相同的,因此所有可选项缩减为两个,AUTOEXEC.BAT 文件变得相对简单。AUTOEXEC.BAT 文件的具体内容可能如下所示:

@echo off
prompt $p$g
set dsk=C:
if not exist %dsk%\Temp\nul md %dsk%\Temp
set Temp=%dsk%\Temp
set dircmd= /A /O:GNE /P
goto %CONFIG%
:L08
:L09
Lh %dsk%\DOS\DRV\Keyrus.com
path ;
set VC=%dsk%\DOS\VC4
path=%VC%;%dsk%\DOS\OTH;%dsk%\;%dsk%\DOS\MS7
Vc.com /TSR /no2E /noswap
goto L25
:L16
path=%dsk%\WINDOWS;%dsk%\WINDOWS\COMMAND
Mode.com con codepage prepare=((866) %dsk%\WINDOWS\COMMAND\Ega3.cpi)
Mode.com con codepage select=866
Lh Keyb.com ru,866,%dsk%\WINDOWS\COMMAND\Keybrd3.sys
echo.
echo Loading Windows-98. Wait...
Win.com
:L24
:L25

在这个版本的 AUTOEXEC.BAT 文件中,第 2-6 行表示公共部分,为普通的环境变量赋值。重要的是,在第 3 行和第 5 行中,为变量 DSK 和 TEMP 赋值时,不要留任何尾随空格。在第 7 行,跳转到由 CONFIG 环境变量的值定义的标签。此值只是由 IO.SYS 加载器在解释 CONFIG.SYS 文件的 `[menu]` 部分时隐式分配的选择的菜单项代码。

由于仅在选择 `L08`、`L09`、`L16` 之后才会执行 AUTOEXEC.BAT 文件,因此第 7 行中的跳转可以指向 `:L08`、`:L09` 和 `:L16` 标签。

标签 `:L08` 和 `:L09` 后面跟着一组用于加载 MS-DOS 7 的最终命令。这组命令定义了 MS-DOS 7 特定的路径,并启动 Volkov Commander 文件管理器。

标签 `:L16` 后面跟着另一组命令,表示加载 Windows-95/98 系统的最终操作。在此,PATH 变量获取了 Windows-95/98 特定的其他路径。需要注意的是使用了简单的国家/地区自适应方法,该方法可以在 Windows 操作系统的“DOS 窗口”中正确切换国家/地区代码页。在最后几行,调用了 Windows GUI 加载程序 - 文件 WIN.COM。不会显示 Windows 徽标,而是显示一条文本消息。当 GUI 加载完成后,此消息将隐藏在通常的 Windows 桌面下方。

注释 1:虽然 Windows XP 操作系统不是为在 MS-DOS 7 下启动而设计的,但可以通过免费工具 Dostowxp.com 来准备所需的初始启动条件。由 V. Ashumov 修改的最新版本可以启动 Windows Vista 和 Windows 7 的加载。此工具的原始版本和修改版本都可以在 http://www.multiboot.ru/files.htm 获得。因此,MS-DOS 7 能够类似于 9.11-03 文章中显示的其他操作系统启动示例,来启动预先安装的现代 Windows 操作系统版本。

注释 2:Linux 操作系统的安装是通过使用特定于特定计算机硬件所需的驱动程序集重新编译其核心来执行的。Internet 服务器 ftp://ftp.wolfmountaingroup.org/pub/linuxware/ 提供了软件包 linuxware-09072008.tar.gz,它可以将 Linux 操作系统的核心 2.4 重新编译成 DOS 常规应用程序的形式。此重新编译的核心在 DOS 下启动 Linux,保持 DOS 的结构完好无损,并在关闭后返回到 DOS。核心 2.4 用于 Mandrake Linux 版本 8-10 和许多其他现代 Linux 操作系统克隆。

华夏公益教科书