如何寫DOS下的裝置驅動程式(一)
基本上我寫的文章中的程式例項都是32位的,需要執行在保護模式下,但是不要祈求在DOS下可以寫32位的裝置驅動程式,因為DOS本身是16位真實模式下的作業系統,當然其驅動程式的機制也只能是真實模式下的,儘管在DOS下可以編防寫模式的程式,但這些程式亦可以通過DPMI呼叫真實模式下的驅動程式,只是效率低一些,所以,按照DOS的規則編寫裝置驅動程式一樣可以讓你的32位保護模式下的程式使用。
DOS下的裝置驅動程式(以下簡稱驅動程式),一般都需要使用匯編語言來編寫,所以,看這篇文章需要組合語言的知識,同時最好對MICROSOFT的彙編語法和格式比較熟悉。
1、DOS裝置驅動程式的原理
DOS裝置驅動程式採用一個單向連結串列串起來,第一個裝置永遠是NUL(一個空裝置),它有一個指標指向下一個裝置驅動程式,最後一個裝置驅動程式的指標為-1,表示是驅動程式鏈的結尾,當DOS需要呼叫裝置驅動程式時,他會從NUL驅動程式開始,根據裝置名在連結串列裡找到第一個匹配的裝置驅動程式,然後呼叫,所以在DOS裝置驅動程式的連結串列中,同樣裝置名的驅動程式可以有多個,但只有排在前面的有效。
當有一個新的裝置驅動程式需要加入到裝置鏈中時,總是把它插在NUL裝置的後面,這樣就可以保證一個新的裝置驅動程式覆蓋掉和自己重名的舊的裝置驅動程式,舊的裝置驅動程式因為在裝置鏈的後面而失效。
DOS在呼叫裝置驅動程式完成一個任務時,要呼叫兩次驅動程式,第一次呼叫驅動程式的“策略例程”,第二次呼叫驅動程式的“中斷例程”,關於這個問題,我們沒有必要去深究,反正是要呼叫兩次才能完成一個任務,而策略例程往往比較簡單,主要的工作由中斷例程完成。
所以,在後面我們會看到,編寫裝置驅動程式時不得不編寫兩部分程式,策略例程和中斷例程。
2、DOS驅動程式的型別
有兩種型別的驅動程式,一種叫字元裝置裝置驅動程式,比如:串列埠、印表機、鍵盤等,這些都歸為字元裝置;還有一種叫塊裝置驅動程式,比如:磁帶機、軟盤、硬碟、U盤等。這兩種型別的驅動程式寫法上有較大不同。
3、驅動程式的結構
簡單地說,一個DOS裝置驅動程式的結構分成三個部分:裝置頭、策略例程、中斷例程。
我們知道,DOS中可執行檔案的格式有兩種,一種是EXE檔案,可以編的比較大,還有一種是COM檔案,只能在64K以內,大多數的裝置驅動程式都是COM格式的檔案,一般為避免混淆,字尾採用sys。
下面我們針對結構的三個部分分別進行說明。
4、裝置頭
裝置頭一共有18個位元組,被分為五個欄位,下面是其定義(本文的程式書寫按照microsoft巨集彙編的格式):
;************************************
; DEVICE HEADER
;************************************
next_dev dd -1
attribute dw ????
strategy dw ????
interrupt dw ????
dev_name db 'abcdefgh'
next_dev:指向下一個驅動程式的指標,置為-1,表明後面沒有其它的驅動程式,DOS在裝入時會做出相應處理;如果我們要在一個程式寫多個裝置的驅動動程式,這個欄位可以指向下一個驅動程式。這是一個雙字欄位,第一個字(低地址)放偏移,第二個字放段地址。
attribute:屬性欄位,含義如下:
bit 含義(置1時)
0 標準輸入裝置
1 標準輸出裝置
2 NULL裝置
3 時鐘裝置
4 特殊的裝置
5-10 備用
11 支援OPEN/CLOSE/REMOVABLE MEDIA的裝置
12 備用
13 非IBM格式裝置
14 IOCTL
15 字元裝置(塊裝置置0)
strategy:策略例程的入口偏移地址
interrupt:中斷例程的入口偏移地址
dev_name:裝置名稱,一共8個位元組,DOS的裝置名不能超過8個字元,當裝置名不足8個字元時,後面補空格。對於裝置名稱,還得多說兩句,對於字元裝置,這個欄位如上所說,對於塊裝置,只有第一個字元有效,為塊裝置單元的數量,DOS不允許塊裝置驅動程式有名字。
5、策略例程
在DOS呼叫裝置驅動程式時,總是把ES:BX指向一個叫Request Header(請求頭)的,這個請求頭中有呼叫驅動程式時需要傳送的所有資訊,然後呼叫裝置驅動程式的策略例程,策略例程只完成一個任務,就是把這個請求頭的地址儲存起來,然後把控制交還給DOS,DOS得到控制權後再呼叫驅動程式的中斷例程,中斷例程根據請求頭中的各種資料完成指定的任務。
不同的呼叫,請求頭的結構會不同,但前面13個位元組總是相同的,如下:
rh struc
rh_len db ? ;length of packet
rh_unit db ? ;unit code(block device only)
rh_cmd db ? ;device driver command
rh_status dw ? ;returned by device driver
rh_res db 8 dup(?) ;reserved
rh ends
策略例程一般總是由下面程式碼組成:
rh_off dw ?
rh_seg dw ?
dev_strategy:
mov cs:rh_seg, es
mov cs:rh_off, bx
ret
正如前面說的,策略例程在儲存完請求頭的地址後,將控制返還給DOS。
6、中斷例程
這一部分是驅動程式的核心部分,幾乎驅動程式的所有功能都是由這部分完成的。我們從請求頭開始說起,請求頭的前四個欄位,rh_len不用說了,是請求頭的長度,因為不同的呼叫請求頭不一樣,所以,需要這個欄位告訴我們請求頭的邊界位置;rh_unit:塊裝置的裝置號,對於字元裝置沒有意義;rh_cmd:命令程式碼,中斷例程中有許多不同的功能模組,執行不同的操作,每個模組有一個唯一的命令程式碼,中斷例程根據這個程式碼決定執行那個功能模組;rh_status:這是我們要強調的一個欄位,在中斷例程執行完後,將把執行結果以狀態碼的形式通過這個欄位傳回給DOS,就是說,在中斷例程執行完即將返回DOS前要填這個欄位,而DOS可以根據這個欄位判斷命令的執行情況,從而決定下一步的操作。
rh_status的說明:
名字 位 說明
------------------------------------------------------------------------------------
ERROR 15 0表示沒有錯誤,如果為1,則要根據ERROR_CODE判斷錯誤型別
DONE 8 驅動程式必須設定,表示已經完成
BUSY 9 必要的話,由驅動程式設定,以防止重複或不適當的操作
ERROR_CODE 0-7 DOS標準的錯誤程式碼,後面有說明
10-14 保留
錯誤程式碼ERROR_CODE說明:
錯誤程式碼 說明 錯誤程式碼 說明
--------------------------------------------------------------------------------------
0 防寫 1 不認識的單元
2 驅動程式沒有準備好 3 不認識的命令
4 CRC錯誤 5 不正確的驅動程式請求結構長度
6 尋道錯誤 7 未知的介質
8 未找到扇區 9 列印缺紙
10 寫失敗 11 讀失敗
12 嚴重故障
下面我們再說說命令程式碼,DOS的驅動程式規範已經定義了一組命令碼,比如0為初始化(initialization)命令;1為檢查介質(media check)命令等等,下面是命令程式碼表。
命令程式碼 說明 命令程式碼 說明
----------------------------------------------------------------------------------------
00h 驅動程式初始化 01h 介質檢查
02h 建立BIOS引數塊 03h I/O控制讀
04h 讀 05h 非破壞性的讀
06h 輸入狀態 07h 清空輸入緩衝區
08h 寫 09h 帶校驗的寫
0Ah 輸出狀態 0Bh 重新整理輸出緩衝區
0Ch I/O控制的寫 0Dh 開啟
0Eh 裝置關閉 0Fh 可移動的介質
10h 輸出直到忙碌 11h 通用IOCTL
13h 通用IOCTL 17h 獲得邏輯裝置
18h 設定邏輯裝置 19h IOCTL查詢
DOS在呼叫驅動程式時,請求頭的rh_cmd欄位一定填的是上面的某個命令碼,在驅動程式的interrupt部分通常有一個含有驅動程式功能指標的表,命令程式碼用作該表的索引,以定位所需的功能,這個表大致是下面的樣子:
d_tdl:
dw s_init ;Initialization(初始化)
dw s_mchk ;Media check(檢查介質)
dw s_bpb ;BIOS parameter block(得到BPB)
dw s_ird ;IOCTL read(IOCTL讀)
dw s_read ;Read(讀)
............
其中的s_init、s_mchk等都是相應命令處理程式的標號。這個表有點向DOS的中斷向量表,只不過中斷呼叫都是遠呼叫,所以一個表項要4個位元組,而這個表是段內呼叫,一個表項只需2個位元組就夠了。
在下來,通常interrupt部分的開始都會有類似下面的這段程式碼,以保證根據命令程式碼執行相應的功能。
mov dx, cs:rh_ seg ;請求頭的段地址
mov es, dx
mov bx, cs:rh_off ;請求頭的偏移地址
mov al, es:[bx] + 2 ;從請求頭中得到命令程式碼
xor ah, ah ;ah=0
cmp ax, MAXCMD ;MAXCMD為允許的最大命令程式碼
jle ok ;合法的命令程式碼
mov ax, UNKNOWN ;非法的命令程式碼
jmp finish
ok:
shl ax, 1 ;命令程式碼乘以2,因為一個表項佔2個位元組
mov bx, ax ;bx=ax
jmp word ptr [bx+d_tbl] ;根據d_tbl表跳轉到指定功能執行
finish:
...............
再回過頭來說請求頭,不同命令的請求頭是不同的,但前13個位元組(就是前面定義的rh結構)是一樣的,不同的命令程式碼,在這13個位元組後面的資料含義都各有不同,描述起來確實篇幅太長,目前寫DOS裝置驅動程式的資料幾乎找不到,我從DOS程式設計師手冊中把相應的章節給摘出來做了一份文件,文字是從網上找的,其中的圖片是我從原文裡給加上的,不然打那麼多字會累死我的。
下載地址如下:
這份資料基本上比較完整,除了我已經介紹的東西外,還有一個結構十分重要,就是BPB(Bios Parameter Block)的資料結構,在這份資料中也有詳細描述,至多是比我囉嗦一點。
在這份材料裡,好像沒有關於介質描述符的說明,其實,這個描述符的大部分定義現在已經不用了,下面兩個還在用:0f0h--其它介質,0f8h--各種容量的硬碟,其它有:0f9h--0ffh均表示各種不同型別的軟盤介質,0f0h也用來表示1.44M和3.88M的3吋軟盤。
我介紹DOS下裝置驅動程式的寫法,其主要目的是希望能編寫出一個簡單的U盤的裝置驅動程式,為此目的,我在下一篇文章《如何寫DOS下的裝置驅動程式(下)》中準備給大家一個RAM DISK裝置驅動程式的例項,因為它應該和我們想要做的事情比較類似,但前提是我得能找到那個程式,不用這個驅動已經很多年了。