Windows NT 裝置驅動程式開發基礎

來源:互聯網
上載者:User

Windows NT 裝置驅動程式開發基礎

一、背景介紹

1.1 Windows NT作業系統的組成
1.1.1 使用者模式(User Mode)與核心模式(Kernel Mode)
從Intel 80386開始,出於安全性和穩定性的考慮,該系列的CPU可以運行於ring0
~ring3從高到低四個不同的許可權級,對資料也提供相應的四個保護層級。運行於較
低層級的代碼不能隨意調用進階別的代碼和訪問較進階別的資料,而且也只有ring0
層的代碼可以直接進行對物理硬體的訪問。由於Windows NT是一個支援多平台的操作
系統,為了與其他平台相容,它只利用了CPU的兩個運行層級。一個被稱為核心模式,
對應80x86的ring0層,作業系統的核心部分,包括裝置驅動程式都運行在該模式;另
一個被稱為使用者模式,對應80x86的ring3層,作業系統的使用者介面部分以及所有的用
戶應用程式都運行在該層級。

1.1.2 Windows NT作業系統的結構
圖1簡要地描述了Windows NT的系統組成。

圖一
可以看到,在物理硬體(Hardware)與系統核心(Kernel)之間有一個硬體抽象
層(Hardware Abstraction Layer),它屏蔽了不同平台硬體的差異,向作業系統的
上層提供了一套統一的介面。我們還可以看到,裝置驅動程式(Device Driver)
是被I/O管理器(I/O Manager)包圍起來的,即驅動程式與作業系統上層的通訊全部都
要通過I/O管理器。這給驅動程式的編寫帶來了很大的便利,因為很多諸如接收使用者的請
求 、與使用者程式交換資料、記憶體映射、掛接中斷、同步等等麻煩的工作都由I/O管理器代
勞了。

1.1.3 Windows NT裝置驅動程式的分類

根據是否直接操作硬體,可以把驅動程式分成兩大類:核心模式的驅動程式和專用驅
動程式。

核心模式的驅動程式根據硬體的通訊協定,直接對硬體進行連接埠訪問、中斷響應、DM
A傳輸。它包括:串、並行口,鍵盤,檔案系統,SCSI,網路等驅動程式;專用驅動程式
包括視頻,列印,多媒體,虛擬DOS等驅動程式,他們在實現上與前者有很大區別。我在
實習期間所做的工作以及本文以下的討論都局限於核心模式的驅動程式。
1.2 Windows NT下核心模式裝置驅動程式的結構和運行

一般來說,裝置驅動程式的任務主要有二:第一,接受來自使用者程式的讀寫請求,把
使用者的資料傳送給裝置,或把從裝置接收到的資料傳送給使用者;第二,輪詢裝置或處理
來自裝置的插斷要求,完成資料轉送。

1.2.1 驅動程式與使用者程式的通訊

I/O管理器把每一個裝置對上層都抽象成了檔案,所以在Win32使用者程式中只要通過以
下幾條簡單的檔案操作API函數就可以實現與驅動程式中的某個裝置通訊(請注意,一個
驅動程式可以驅動多個裝置):

函數名 功能

CreateFile 開啟一個裝置,準備進行資料轉送。返回一個與裝置相關的控制代碼。

CloseHandle 關閉一個由CreateFile開啟的裝置。

ReadFile 從裝置讀取資料。

WriteFile 向裝置寫資料。

DeviceIoControl 對裝置進行一些自訂的操作,比如更改設定等。

表一

1.2.2 DriverEntry過程

這是每一個裝置驅動程式的入口,每次該程式啟動時被系統自動調用。大部分的裝置
初始化的工作都在這個過程中完成。包括設定響應各種使用者請求的過程的入口,使I/O管
理器能知道當使用者的開啟、關閉、讀寫等請求到來時各應調用那些過程來處理。驅動程
序中只有本過程的名字/"DriverEntry/"是固定的,以下列出的所有過程都要由本過程向系
統註冊。

如果該驅動程式不響應任何請求的話,只要一個DriverEntry過程就可以構成一個能運
行的驅動程式。

1.2.3 Unload和ShutDown過程

Unload過程負責在驅動程式被停止前做一些必要的處理。比如釋放資源,記錄最終狀
態等。ShutDown過程在系統即將關閉時被調用,與前者的區別在於不用釋放任何資源。

 

1.2.4 DispatchOpen和DispatchClose過程

這兩個過程在使用者調用CreateFile和CloseHandle時被調用,為即將到來的讀寫操作做
備,或做一些讀寫完成後的必要處理。

1.2.5 DispatchRead, DispatchWrite與StartIo過程

這前兩個過程在使用者調用ReadFile和WriteFile時被調用。它們先做一些檢驗使用者請求
合法性的工作,然後啟動一個被稱為StartIo的過程開始實際的與硬體間的資料轉送。I
/O管理器還通過IRP為它們提供了一個指向使用者緩衝區的指標,用於與使用者程式交換資料
。詳情請見1.3.2

1.2.6 接受自訂的其他請求

這兩個過程在使用者調用DeviceIoControl時被調用。它通過IRP獲得使用者的請求號,以

及一個指向使用者緩衝區的指標,可以與使用者程式進行通訊。

1.2.7 中斷處理過程(ISR)

這些過程在中斷髮生時被系統調用。

1.2.8 延遲過程(Deferred Procedure)

這些過程用來在較低的運行層級完成較高運行層級過程(如中斷處理過程)的一
些任務。詳情請見1.3.3
1.3 實現細節

1.3.1 核心代碼運行層級

Windows NT為它的核心模式的代碼分配了不同的層級。在同一個CPU上,層級低的過程
可以被任何層級更大的過程中斷。層級由低到高排列如下:

層級名稱 運行於該層級的過程

PASSIVE_LEVEL DriverEntry, Unload, ShutDown, DispatchXxx。

APC_LEVEL 在某些特殊情況下,大儲存量裝置的驅動程式運行於該層級。

DISPATCH_LEVEL StartIo, AdapterControl, ControllerControl, IoTimer,Dpc。

DIRQLs 各種中斷處理常式。

表二
1.3.2 幾個對象

i) I/O請求包(IRP)

I/O管理器每收到一個來自使用者的請求就建立一個該結構,並將其作為參數傳給驅
動程式的DispatchXxx、StartIo過程。該結構中存放有請求的類型,使用者緩衝區的首地
址,使用者請求資料的長度等資訊。驅動程式處理完這個請求後,也在該結構中添入處理
結果的有關資訊,調用IoCompleteRequest將其返回給I/O管理器,使用者程式的請求隨即
返回。

ii) DPC

當驅動程式中要用到Dpc過程時,需要建立該對象。具體作用請見1.3.3。

iii) 驅動程式對象(DriverObject)

該對象在驅動程式被啟動時由I/O管理器建立,儲存有該程式處理各種請求的過程
入口、該程式所驅動的全部裝置對象的鏈表等。

iv) 裝置對象(DeviceObject)

每發現一個可以驅動的裝置,驅動程式調用IoCreateDevice建立一個該對象。該

對象有一個指標DeviceExtension指向一塊由驅動程式定義的結構,其中儲存有關此裝置
的如連接埠號碼,中斷向量等全部資訊。

v) 中斷對象(Interrupt)

該對象在驅動程式調用IoConnectInterrupt時建立,存有中斷及處理的過程的資訊。
當一個中斷髮生時,I/O管理器用它尋找對應的處理過程。

1.3.3 延遲程序呼叫(Deferred Procedure Call)

由於中斷處理過程運行於較高的DIRQL級,它們能屏蔽許多層級小於或等於它們的過程
的執行,如果它們佔用CPU時間過長,很容易使系統效能下降。因此中斷處理過程應將一
些不是很緊急的任務放在被稱為Dpc的過程中,在完成資料轉送等緊急任務後將一個DPC
對象放在系統DPC隊列的末尾,然後退出,盡量早地讓出CPU。系統將在完成所有DIRQL級
的任務後處理DPC隊列,在DISPATCH_LEVEL執行每一個DPC 對象指定的Dpc過程,完成中
處理斷過程未盡的任務。
1.3.4 尋找硬體資訊

i) 系統自動搜尋到的裝置

在系統啟動時,組件NTDETECT會自動地搜尋電腦上已有的硬體,包括串、並行
口,鍵盤,滑鼠,以及大多數PCI和EISA裝置。並將它們的資訊,包括匯流排類型,匯流排號
,用到的連接埠號碼及數量、中斷向量號、DMA通道號、佔用記憶體等按一定格式添入註冊表的
//HKEY_LOCAL_MACHINE//Hardware//description//System// 鍵之下。在驅動程式中可以用I
oQueryDeviceDescription以及一個回呼函數ConfigCallback來尋找符合要求的裝置,並
擷取它的配置資訊。

ii) 系統不能自動搜尋到的裝置

一些ISA的裝置無法被系統自動檢測到,只有在安裝驅動程式時在註冊表中人工添入它
們的配置資訊。驅動程式啟動時可以用RtlQueryRegistryvaluess等函數查詢註冊表獲得
這些資訊。

 

1.3.5 有關記憶體
80386以上的32位CPU可以管理多達4GB的實體記憶體。它將這些記憶體分為許多大小為64K
B的段和4KB的頁來管理,並通過段描述符和頁表將物理地址映射成系統地址供程式訪問
。由於Windows NT使用虛擬記憶體技術,可能某些系統地址對應的物理地址處於硬碟上,
每當程式讀寫這些地址時會產生一個缺頁異常,使CPU將這些記憶體調入實體儲存體器中。這
部分記憶體被稱為分頁記憶體(Paged)。與之對應的是非分頁式記憶體(Nonpaged),這部分內
存保證是物理駐留的。驅動程式中運行層級大於等於DISPATCH_LEVEL的過程不能訪問分
頁記憶體,否則引起系統崩潰。

1.3.6 緩衝的I/O與直接I/O

在驅動程式建立了一個裝置後,可以通過設定DeviceObject的Flags域的值來將裝置設
置成緩衝的I/O或直接的I/O。

如果該值被設為DO_BUFFERED_IO,每當I/O管理器收到一個讀寫請求,就在記憶體的非分
頁區分配一塊與使用者區大小相同的地區,並將首指標存放於Irp對象的AssociatedIrp.S
ystemBuffer中,驅動程式就通過這個緩衝區與使用者交換資料。每當一個讀請求被完成時
I/O管理器自動將該緩衝區中的內容複寫到使用者區,並釋放該地區。

如果使用者區大於一頁(在80x86上為4096位元組),一般將該值設為DO_DIRECT_IO。
這時每當I/O管理器收到一個讀寫請求,先鎖定使用者區的實體記憶體,然後為其建立一個內
存描述表(MDL),並將該表的首指標存放於Irp對象的MdlAddress中,驅動程式可以通過
調用MmGetSystemAddressformdl獲得使用者區在系統空間中的地址。每當一個讀請求被完
成時I/O管理器自動將該地區解鎖。

1.3.7 定時

為了防止當裝置出現某種故障時導致讀寫請求逾時,或需要定時輪詢某些裝置的狀態
,驅動程式需要設定一些定時器。驅動程式中有兩種方法可以設定定時器。一種是調用
IoInitializeTimer將一個定時器過程IoTimer與一個裝置對象聯絡起來。在調用IoStar
tTimer後,系統將每一秒鐘調用一次IoTimer,直至驅動程式調用IoStopTimer。如果需
要設定更小間隔的定時器,需要用到被稱為CustomTimerDpc的一種延遲程序呼叫機制。
它可以設定系統每隔一定時間將一個設定好的DPC對象放到DPC隊列的末尾,執行一個指
定的定時器Dpc過程。這個時間間隔可以精確到100ns。

1.3.8 同步

如果驅動程式有可能在某時刻有多個部分在同時運行,比如有中斷處理過程,或
存在多個裝置等,對公用資料或代碼的訪問就需要同步。方法有

i) 自旋鎖(SpinLock)

驅動程式可以在初始化時調用KeInitializeSpinLock建立該對象。在任何程式碼片段
訪問被保護的資料之前,先調用KeAcquireSpinLock試圖獲得該對象的所有權,如果成功
,該段代碼被系統提升至DISPATCH_LEVEL,進行資料訪問。訪問完畢後須調用KeRelease
SpinLock釋放所有權,運行層級也被恢複。此方法只適用於同步運行層級小於等於DISP
ATCH_LEVEL的代碼,主要用於多CPU的情形。此外,還有一種中斷自旋鎖用於與中斷處理
過程同步,可以將較低層級的代碼提升到需要與之同步的中斷DIRQL。

ii) 控制器(Controller)

該對象主要用於同步一個驅動程式中的多個裝置,保證它們能順序地訪問特定的
代碼或資料。該對象在驅動程式初始化調用IoCreateController被建立。裝置在StartI
o過程中調用IoAllocateController請求獲得Controller對象的獨佔權。使用完後調用I
oFreeController釋放。驅動程式停止時調用IoDeleteController從記憶體刪除該對象。該
對象有一個指標ControllerExtension指向一塊由驅動程式定義的結構,其中儲存有此驅
動程式的公用資料。

iii) 適配器(Adapter)

該對象用於同步多個裝置(不一定在一個驅動程式中)對DMA通道的使用。該對象
在系統啟動偵測硬體時自動被建立。驅動程式在初始化時調用HalGetAdapter獲得該對象
的指標。裝置在StartIo過程中調用IoAllocateAdapterChannel請求獲得DMA通道的獨佔
權,然後開始傳輸資料。使用完後調用IoFreeControllerChannel釋放DMA通道。

iv) DPC

由於DPC隊列中的對象總是被系統順序地處理,所以也可以將需要同步的代碼做成
Dpc過程,需要調用時將相應的DPC對象放到隊列的末尾即可。

v) 其他

同使用者模式的應用程式類似,驅動程式也可以使用多線程,也提供了一套用來同步的
對象,如Event, Mutex, Semaphore, Timer, Thread。其中Event對象可以被命名,不同

的驅動程式可以利用同名的Event對象同步對公用資料的訪問。

1.3.9 分層

I/O管理器一個有用的功能是允許把一個驅動程式堆在另一個驅動程式之上。這樣在分
編寫如網路驅動等有協議棧程式時,可以為各層編寫相對獨立的代碼。當驅動程式需要
在不同的平台上移植時,只需重新編寫最下層的硬體驅動程式即可。高層驅動程式的另
一個功能是可以對使用者請求進行予處理,比如把較大的請求分割成較小的請求分多次傳
給給下層的程式。

1.3.10 裝置名稱及其符號串連

Windows NT系統維護著一個對象名字空間,把所有在系統內註冊過的對象的名字分類
存在一個樹狀空間裡,用Win32 SDK提供的WinObj工具可以瀏覽這個空間。如果希望裝置
能被使用者的CreateFile函數開啟,就需要在調用IoCreateDevice建立該裝置對象時賦予
它一個名字,位於//Device//下,並調用IoCreateSymbolicLink在//DosDevices//下建立一
個符號串連。這樣,使用者程式就能用CreateFile(/"////////.////符號串連名/",……)開啟該設
備,並獲得其控制代碼。

1.4 驅動程式的編譯連結,調試、安裝和啟動。

Windows NT下編寫驅動程式的環境被稱為為DDK(Device Driver Kit) For Micro
soft Windows NT,這是一個命令列下的工作環境。但是在安裝DDK之前需要安裝Win32
SDK(Software Development Kit)以及 Microsoft Visual C++。

編譯連結器為Build.exe,他從設定檔Sources中讀出待編譯的程式的配置,包
括源檔案、目標檔案等,從環境變數Include中得到引用檔案的地址,然後調用Visual
C++的編譯連結器Nmake.exe進行實際的編譯連結工作。記錄檔build.log,build.wrn
,build.err 中分別記錄了編譯連結中執行的命令列,遇到的錯誤,遇到的警告。編譯
完成後的檔案尾碼為.sys

安裝過程分兩步:第一,將編譯成的.sys檔案拷貝到Windows NT的System32//Dri
vers//下;第二,在註冊表的HKEY_LOCAL_MACHINE//SYSTEM//CurrentControlSet//Service
s//下建立與.sys檔案同名的鍵,然後在之下建立名為Start,Type, ErrorControl的三
個REG_DWORD類型的數值鍵。其中Start的索引值控制該驅動程式在系統啟動的哪個階段被
啟動。小於3的數設定該驅動程式在系統啟動的某個階段被自動啟動;3表示需要管理員

手動啟動;4表示該程式被禁用。設定完畢後需要重新啟動系統。

手動啟動和停止一個驅動程式需要使用控制台(ControlPanel)中的裝置(Device
)表徵圖。

由於驅動程式的結構比較複雜,而且調試核心模式的代碼需要兩台安裝有Windows NT
的電腦,比較麻煩,所以在編寫一個較複雜的驅動程式的過程中應分步來進行測試。
在完成任何一部分工作後都應進行測試,以便及早地發現錯誤。根據本人的經驗,驅動
程式中的大多數錯誤都是由於不正確地訪問記憶體造成的。比如使用未被初始化的指標,
釋放已經被釋放的記憶體,在DISPATCH_LEVEL或以上的運行層級引用分頁的記憶體。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.