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 中介紹的最簡單的配置檔案開始是明智之舉。
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 中的示例)。
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 中的示例)。
由於 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.COM 和 KEYB.COM 會為顯示器啟用所需的國家內碼表以及相應的鍵盤佈局。如果您需要的不是俄語內碼表 866,則可以更改它,但事先應該在表 A.02-2 中檢查關聯的資料表(EGA3.CPI 和 KEYBRD3.SYS)是否包含您需要的資料。如果不是,則這些資料表也必須更改。當然,選擇第 437(美國)內碼表可以使您免於省略所有包含 MODE.COM 和 KEYB.SYS 的行,因為此內碼表預設啟用。
AUTOEXEC.BAT 檔案中的最後兩行用於啟動 VC.COM - Volkov Commander 檔案管理器(6.25)。其他檔案管理器,例如 Norton Commander(NC.EXE)和 Dos Navigator(DN.EXE)可以以類似的方式啟動。如果您不打算使用檔案管理器,則所示的最後兩行不需要。
如果有一組自適應配置檔案,可以從任何磁碟正確載入 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 檔案的普通操作。在執行過程中,找到的當前磁碟的字母名稱將自動插入到必須指定它的所有路徑中。
在每個邏輯磁碟中,第一個扇區是引導扇區。它包含引數塊 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
使用所描述的命令檔案,引導扇區恢復成為一項簡單的操作。然而,此操作不應用於引導扇區的移植。即使在相同格式的磁碟之間移植也會引發一些關於其序列號和卷標唯一性的問題,因為後者在根目錄中重複。通常,每個引導扇區都是唯一的,並且僅適用於其原始磁碟,而不是其他磁碟。
每次您開啟計算機時,它都會從其 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 中)。
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 下執行。
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 中介紹。以下文章介紹了一些不太簡單的批處理檔案示例。這些示例演示了批處理程式設計的技術,這些技術可能遠遠超出所顯示批處理檔案的功能。
根據計算機術語,歸檔意味著將多個檔案壓縮成一個組合的檔案檔案。這個含義來自很久以前,那時計算機不可靠,人員必須將多個檔案儲存為一個組合的資料流,寫入磁帶介質上。從那時起,大多數東西都發生了演變,但對歸檔的興趣並沒有消失。歸檔減少了磁碟簇中可用空間的損失,使複製和碎片整理速度更快。檔案大小壓縮對於通訊網路中的有限傳輸速度至關重要。將多個檔案打包成一個檔案非常方便,並且廣泛用於交付大型程式產品。
已知的歸檔演算法很多,但只有兩種演算法——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 中)。
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 實用程式之前強制將資料寫入磁碟,但完全停用寫回快取更安全。
很久以前,DOS 被開發為通用作業系統,它的命令集是根據這個目的形成的。但現在 DOS 的作用已經專業化,因此以前的命令集現在看來部分冗餘,部分不足。本文介紹了一個實用程式,它執行三個操作,這些操作補充了 DOS 命令集的最緊急的不足。特別是,這三個操作可以實現第 9.09 節中描述的自適應載入場景。
由於建議的實用程式用請求的其他資料替換了一些環境變數的當前值,因此它被命名為 REASSIGN.COM。REASSIGN.COM 檔案並不大——總共 992 位元組——因為它依賴於與 DOS 內部 SET 命令(3.26)的合作,並且沒有重複它的功能。SET 命令準備一個環境變數,並且準備好的變數值中的第一個字元定義了 REASSIGN.COM 實用程式的操作
- 報告最大空閒 XMS 記憶體塊的大小;
- 從鍵盤接受輸入;
- 報告當前磁碟的字母名稱。
準備好的值中的其餘字元被忽略,但它們的存在為新值保留了空間。如果此空間不足以容納返回的結果,則會顯示錯誤訊息。如果結果沒有佔用整個預留空間,則其剩餘部分將用空格字元(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 可接受的所有機器命令規範形式。

在任何情況下,包含錯誤的行都不會被彙編,以下的所有偏移量都會發生偏移,並且在出現單個錯誤之後,所有後續定址都會出錯。在採取任何進一步的措施之前,必須更正已註冊的錯誤。
資訊列表能夠揭示甚至那些 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 將表示直到最後一個處理的彙編指令都沒有偏移值位移。
當需要闡明某些命令或中斷處理程式的效果時,輸入 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 中完整的程式時,順序模組化組成的優勢充分體現出來。
當錯誤更正後,最後的列表不再顯示錯誤時,就該處理測試過程中剩下的所有錯誤了。
在最後一次組裝迭代中生成的執行檔案通常還很原始,無法立即啟動。首先,它應該被傳遞到偵錯程式。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 實用程式那樣小的程式。當您的程式成功透過所有測試時,您對該程式的任務就可以被認為已完成。
當使用 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 實用程式後,該記憶體區域(以前在段限制之外是無法訪問的)變得可訪問。

那些敢於實施所提供的示例的人將獲得一個獨特的機會,可以檢視 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)。
為了保護資料和主計算機的作業系統,當從外部驅動器或可移動介質(軟盤、快閃記憶體卡或光碟)載入 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。
當將有效的引導扇區和 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 載入過程。
當 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) 可以刪除提到的屬性。
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 作業系統克隆。