Windows Driver 開發學習筆記

眾所周知, 早期的Windows 95/98的設備驅動是VxD(Virtual Device Driver),其中x表示某一類設備。從Windows 2000開始,開發驅動程序必以WDM(Windows Driver Model)為基礎的,但是,如果使用DDK來開發WDM,其開發難度之大,根本不能奢望像用戶模式應用程序開發那樣容易,因此,一般用戶都是使用WinDriver、DriverStudio之類的第三方工具。

為改善這種局面,從Vista開始,微軟推出了新的驅動程序開發環境WDF(Windows Driver Foundation )。 WDF和WDM的關係有點類似於MFC和Windows SDK的關係,有編程經驗的人一看就知道為何WDF開發比WDM容易了。

微軟有寫了基本概要, 先看看原廠介紹吧! 然後根據每個小課程的練習與實作, 應該就會慢慢進步了解!
連結:Windows programming guide for driver technologies

先安裝環境吧!

設置編譯環境 – 開發環境安裝Visual Studio, Windows SDK, 以及Windows driver kit(WDK)

寫出你的第一個驅動程式吧!

Write your first driver

WDF 有KMDF和UMDF兩種模式:

  • 內核模式驅動程序 KMDF(Kernel-Mode Driver Framework):這類驅動程序作為內核模式操作系統組件的一部分執行,它們管理I/O、即插即用、內存、進程和線程、安全等。內核模式驅動程序通常為分層結構。關於KMDF更多的內容,可參閱 MSDN中“Getting Started with Kernel-Mode Driver Framework ”。
  • 用戶模式驅動程序 UMDF(User-Mode Driver Framework):這類驅動程序通常提供 Win32 應用程序與內核模式驅動程序或其他操作系統組件之間的接口。用戶模式驅動程序支持基於協議或基於串行總線(如攝像機和便攜音樂播放器)的設備。關於UMDF更多的內容,可參閱 MSDN中“ Introduction to UMDF“。
  • 無論內核模式的驅動程序或者用戶模式的驅動程序,都使用同一環境進行構建,這一環境稱為WDK;都採用同一套對像模型構建,採用同一個基礎承載,這個基礎就是WDF。由於WDF驅動模型提供了面向對象和事件驅動的驅動程序開發框架,大大降低了開發難度。從現在開始,掌握Windows設備驅動程序的開發人員,由過去的“專業”人士,將變為“普通”大眾。因此,像WinDriver、DriverStudio之類的第三方工具也隨之退出歷史舞台。
  • KMDF是Windows系統底層驅動,文件名為:*.SYS,Vista為2萬多外設提供了KMDF,其中也包括USB2.0,因此對於具有USB2.0協議的FX2,只需編寫與FX2相關的UMDF即可;UMDF是用戶層驅動,文件名為:*.DLL

程式進入點 DriverEntry

(轉自steward-fu)
Windows Driver Framework(WDF)是新型的驅動程式架構並且分成Kernel Mode Driver Framework(KMDF)和User Mode Driver Framework(UMDF)兩種架構,UMDF是跑在User Mode的Driver;而KMDF則是跑在Kernel Mode,所以KMDF的行為以及寫法會跟WDM很相似,畢竟KMDF是一個重新包裝WDM架構的驅動程式,所以很多WDM的東西還是可以共用的,但是KMDF的誕生畢竟是為了簡化WDM的複雜性,如果要寫KMDF驅動程式就遵照KMDF建議的方式製作,之後維護程式的人也比較清楚關係,畢竟很多人還是從KMDF開始學習驅動程式,並非是從WDM開始學習。

KMDF的驅動程式又分成PnP(Plug and Play)和Non-PnP兩種類型,PnP類型的驅動程式主要負責的職務是跟支援隨插即用的裝置溝通,其類型大致上有:USB、1394、PCI等隨插即用裝置,而相較於PnP類型的驅動程式,Non-PnP類型的驅動程式並不支援隨插即用的IRP(I/O Request Packet),系統當然也不會發送隨插即用的IRP到Non-PnP驅動程式,因為Non-PnP主要支援非隨插即用裝置,因此相關的隨插即用資源是無法存取的,但是還是可以存取Legacy硬體I/O,而KMDF的PnP跟Non-PnP驅動程式,其實就是WDM和Legacy類型的驅動程式。

不管是KMDF(PnP、Non-PnP)、WDM或Legacy驅動程式,它們的程式進入點一律是DriverEntry(),而且格式是一樣的,定義如下:

系統載入驅動程式時會呼叫DriverEntry()並帶入兩個參數,第一個PDRIVER_OBJECT是Driver Object指標,該指標會包含驅動程式物件的所有資訊,當然有些欄位是不允許使用者更改的,而另一個參數PUNICODE_STRING則是Registry Path,每個驅動程式在安裝時,都會產生一個註冊表項目,該項目就是當Windows啟動時要載入用的,所以註冊表亂更改時,驅動程式可能就不會被正確載入。

需要注意的是,就算有相同的裝置載入同一份驅動程式,則Driver Object也僅會只有一份,那這一些相同裝置的驅動程式資料不就會亂掉嗎?答案是:不會的,因為會有多份Device Oject,每個Device Object各代表不同的裝置。那DriverEntry()需要做哪一些事情呢?做法跟WDM是很類似的,都需要註冊Callback副程式,差别只是呼叫API不一樣,因為KMDF使用了事件、屬性、訊息當作處理的核心機制,其實有點像Windows的視窗程式寫法。範例:

上面的程式只註冊AddDevice Callback副程式,其餘Callback交給預設的KMDF副程式處理,很簡短吧!該程式是透過WDF_DRIVER_CONFIG_INIT()做Callback初始化,該副程式看起來很簡單,但是怕該副程式隱藏很多細節,所以司徒將它的內容貼出來看一下

WDF_DRIVER_CONFIG_INIT()看來只有做記憶體初始化跟Config結構的初始化,算是很簡單的初始化而已。
初始化完Config結構後,程式接著使用WdfDriverCreate()產生Framework Driver Object,這個地方就跟WDM不一樣,WDM不需要產生Driver Object,而KMDF因為核心是採用WDM的架構,所以需要建立另一組資料給KMDF使用,另外要注意的是DriverEntry()回傳值的部分,因為回傳值會決定載入驅動程式的成功或失敗。KMDF相較於WDM的架構,真是越來越簡潔,但是,沒有WDM基礎的使用者,司徒覺得對於除錯以及詳細原理會越來越難搞清楚,畢竟包裝太多WDM的東西,看似簡單,卻是隱藏很多細節,對於初學者或經驗不足的使用者,細節完全不懂時,很難Debug比較難的問題。

AddDevice

當系統找到符合的裝置(透過INF檔案安裝)且驅動程式被系統載入後,AddDevice()就會被系統呼叫,而AddDevice()是在DriverEntry()裡面註冊的,所以系統才會知道AddDevice()位於何處,名稱不一定要用AddDevice,但是參數跟回傳值必須遵照Microsoft的定義,否則會有問題。
AddDevice()副程式定義如下:

傳入的PDRIVER_OBJECT是該驅動程式的Driver Object,而PDEVICE_OBJECT則是位於下層的驅動程式Device Object,WDM驅動程式的架構是使用堆疊方式做驅動程式的添加、刪除,例如:如果使用者寫的是USB驅動程式,則下層可能就是USB Bus驅動程式,如果使用者寫的驅動程式只是一個虛擬的純軟體驅動程式,那麼下層驅動程式就是I/O Manager,由於使用堆疊的架構,所以WDM驅動程式又可以加入Upper Filter、Lower Filter Driver,Filter Driver的目的是提供I/O Request Packet(IRP)修改的功能,達到不需更改原始的驅動程式就可以做錯誤修正。

那在AddDevice()需要做什麼事情呢?一般會產生一個新的Device Object並為該Device Object建造一條Symbolic Link,該Symbolic Link就是提供給User Mode的應用程式開啟(僅能使用CreateFile() API開啟),還記得呼叫CreateFile()時會提供一個名字嗎?若記得的話,此名字就是驅動程式的Symbolic Link名稱。那問題又來了,有沒有可能裝置會使用同一個Symbolic Link名字呢?答案是,肯定會發生的,所以Microsoft建議大家使用GUID的方式註冊,而系統將會自動產生一個唯一的名稱給該註冊的裝置,如果是這樣的話,那User Mode的應用程式如何開啟驅動程式呢?這時候就必須使用Setup API做GUID列舉並取得Symbolic Link名稱,哪一種方式比較好呢?如果是使用Symbolic Link註冊名稱,User Mode應用程式比較好寫,因為名稱已經知道了,反之,使用GUID註冊的話,User Mode應用程式需要列舉判斷後才能開啟,所以會比較不好寫,但是優點則是名稱不會衝突。
範例

  • Step 1 產生一個Device Object(可自己決定名稱),然後建立一條Symbolic Link(可自己決定名稱),Device Object名稱一般是放在Windows特殊資料夾中的Device資料夾,使用者可以使用WinObj程式去查看有哪些Device Object,而Symbolic Link的名稱則是放在DosDevices資料夾(GLOBAL??),那應用程式該如何把完整路徑名稱給CreateFile()呢?答案是加上\.\\關鍵字,有印象開啟COM Port時需要使用這樣CreateFile(“\\.\\\\COM1”, …);的方式嗎?這就是代表完整路徑的意思,在寫COM Port程式時,不一定說是要大於COM9才能加\.\\路徑,其實從COM1就可以開始使用。
  • Step 2 把剛剛產生完的Device Object附加到下層Device Object的堆疊,這樣才可以開始處理I/O Request Packet(IRP)。
  • Step 3 初始化相關旗標,讓PnP Manager知道Device Object已經初始化完畢,比較需要注意的是DO_BUFFERED_IO旗標,因為在做裝置讀寫時,User Mode應用程式跟驅動程式是否共用同一塊Buffer是取決於該旗標,如果使用者設定成DO_BUFFERED_IO,則代表驅動程式有自己獨立一塊Buffer,驅動程式讀取完硬體資料後,會複製到它自己的Buffer,然後再複製到User Mode應用程式的Buffer,所以速度會比較慢一些,如果要共用同一塊Buffer的話,則把旗標設定成DO_DIRECT_IO即可。

DriverUnload

當驅動程式準備被系統卸載時,DriverUnload()會被系統呼叫,這是驅動程式最後可以釋放資源的地方,若沒有適當的釋放資源,則驅動程式無法被卸載,遇到這種狀況時,系統會提示需要重新開機才可以正確卸載驅動程式。DriverUnload()副程式定義如下:

因為WDM驅動程式會收到PnP Remove Device的IRP,所以當系統要卸載驅動程式時,系統會呼叫PnP Callback副程式並帶入IRP_MN_REMOVE_DEVICE(IRP_MJ_PNP),WDM驅動程式一般會在那個地方釋放資源;

若是Legacy驅動程式,因為裝置物件是在DriverEntry()產生,加上又沒有IRP_MN_REMOVE_DEVICE IRP,所以必須在DriverUnload()釋放資源。範例:

WDM驅動程式的DriverUnload()一般不做任何事情,因為釋放資源的地方已經改到IRP_MN_REMOVE_DEVICE的地方,原因在於WDM驅動程式的資源配置是在AddDevice()配置,所以釋放資源的地方就變成是收到IRP_MN_REMOVE_DEVICE時才移除之前配置的資源。

發表迴響

Copy Protected by Chetan's WP-Copyprotect.