延伸閱讀: 本站另一文 Video and Graphic 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)
寫出你的第一個驅動程式吧!
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(),而且格式是一樣的,定義如下:
NTSTATUS DriverEntry(PDRIVER_OBJECT, PUNICODE_STRING);
系統載入驅動程式時會呼叫DriverEntry()並帶入兩個參數,第一個PDRIVER_OBJECT是Driver Object指標,該指標會包含驅動程式物件的所有資訊,當然有些欄位是不允許使用者更改的,而另一個參數PUNICODE_STRING則是Registry Path,每個驅動程式在安裝時,都會產生一個註冊表項目,該項目就是當Windows啟動時要載入用的,所以註冊表亂更改時,驅動程式可能就不會被正確載入。
需要注意的是,就算有相同的裝置載入同一份驅動程式,則Driver Object也僅會只有一份,那這一些相同裝置的驅動程式資料不就會亂掉嗎?答案是:不會的,因為會有多份Device Oject,每個Device Object各代表不同的裝置。那DriverEntry()需要做哪一些事情呢?做法跟WDM是很類似的,都需要註冊Callback副程式,差别只是呼叫API不一樣,因為KMDF使用了事件、屬性、訊息當作處理的核心機制,其實有點像Windows的視窗程式寫法。範例:
NTSTATUS DriverEntry(PDRIVER_OBJECT pDrvObj, PUNICODE_STRING pRegPath){ WDF_DRIVER_CONFIG config; NTSTATUS status; WDF_DRIVER_CONFIG_INIT(&config, AddDevice); status = WdfDriverCreate(pDrvObj, pRegPath, WDF_NO_OBJECT_ATTRIBUTES, &config, WDF_NO_HANDLE); if(!NT_SUCCESS(status)){ DbgPrint("WdfDriverCreate failed 0x%x\n", status); return status; } return STATUS_SUCCESS;}
上面的程式只註冊AddDevice Callback副程式,其餘Callback交給預設的KMDF副程式處理,很簡短吧!該程式是透過WDF_DRIVER_CONFIG_INIT()做Callback初始化,該副程式看起來很簡單,但是怕該副程式隱藏很多細節,所以司徒將它的內容貼出來看一下
VOID FORCEINLINE WDF_DRIVER_CONFIG_INIT(OUT PWDF_DRIVER_CONFIG Config, IN PFN_WDF_DRIVER_DEVICE_ADD EvtDriverDeviceAdd){ RtlZeroMemory(Config, sizeof(WDF_DRIVER_CONFIG)); Config->Size = sizeof(WDF_DRIVER_CONFIG); Config->EvtDriverDeviceAdd = EvtDriverDeviceAdd;}
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()副程式定義如下:
NTSTATUS AddDevice(PDRIVER_OBJECT, PDEVICE_OBJECT);
傳入的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應用程式需要列舉判斷後才能開啟,所以會比較不好寫,但是優點則是名稱不會衝突。
範例
PDEVICE_OBJECT gNextDevice=NULL; NTSTATUS AddDevice(PDRIVER_OBJECT pOurDriver, PDEVICE_OBJECT pPhyDevice){ PDEVICE_OBJECT pOurDevice; UNICODE_STRING usDeviceName; UNICODE_STRING usSymboName; // step 1. create new device and symbolic link RtlInitUnicodeString(&usDeviceName, L"\\Device\\firstWDM"); RtlInitUnicodeString(&usSymboName, L"\\DosDevices\\firstWDM"); IoCreateDevice(pDrvObj, 0, &usDeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pOurDevice); IoCreateSymbolicLink(&usSymboName, &usDeviceName); // step 2. attach to driver stack and save the next object for later use gNextDevice = IoAttachDeviceToDeviceStack(pOurDevice, pPhyDevice); // step 3. initialize device object flags pOurDevice->Flags&= ~DO_DEVICE_INITIALIZING; pOurDevice->Flags|= DO_BUFFERED_IO; return STATUS_SUCCESS;}
- 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()副程式定義如下:
void DriverUnload(PDRIVER_OBJECT);
因為WDM驅動程式會收到PnP Remove Device的IRP,所以當系統要卸載驅動程式時,系統會呼叫PnP Callback副程式並帶入IRP_MN_REMOVE_DEVICE(IRP_MJ_PNP),WDM驅動程式一般會在那個地方釋放資源;
若是Legacy驅動程式,因為裝置物件是在DriverEntry()產生,加上又沒有IRP_MN_REMOVE_DEVICE IRP,所以必須在DriverUnload()釋放資源。範例:
void DriverUnload(PDRIVER_OBJECT pOurDriver){}
WDM驅動程式的DriverUnload()一般不做任何事情,因為釋放資源的地方已經改到IRP_MN_REMOVE_DEVICE的地方,原因在於WDM驅動程式的資源配置是在AddDevice()配置,所以釋放資源的地方就變成是收到IRP_MN_REMOVE_DEVICE時才移除之前配置的資源。
手動安裝 *.inf, *.sys 與.dll 檔案
KMDF是Windows系統底層驅動,文件名為:*.SYS
安裝一般驅動程式有 *.inf 及 *.sys 檔案,步驟如下:
1. 將驅動程式的INF 檔copy到System32 Inf 底下(如 C:\Windows\inf\ ,注意:這個目錄可能是隱藏的!), 將SYS檔copy 到driver 底下(如 C:\windows\system32\driver\),
2. 啟動Device Manager 或重開機就會自動安裝
UMDF是用戶層驅動,文件名為:*.DLL
安裝 DLL 元件,步驟如下:
將 DLL 組件先複製到 C:\Windows\System32 目錄下
然後進入命令提示字元執行 regsvr32 xxxxxx.dll 指令
系統檔案什麼是SysWow64? 什麼是system32 目錄?
老麟October 26, 2011
64位的Windows並不是簡單地把所有東西都編譯成64位就萬事大吉的。關於64位的CPU應該做成什麼樣子,Intel和AMD曾有各自的打算。 AMD的回答直接了當:新的64位處理器,應該能在提高更高處理能力的同時,保持對32位應用程序的兼容性。而Intel則希望藉此機會,把下一代的處理器,設計得更完美。於是,就有了AMD的x86-64(後被稱為amd64)的處理器和Intel的IA-64(安騰)處理器。和amd64不一樣的是,安騰處理器並沒有很好地提供對32位應用程序的支持。具體信息,讀者在網上應該很容易找到,也就不多說了。
Windows作為一個操作系統,自然希望用戶在運行64位操作系統時,也能像以前一樣,運行各種32位應用程序。這一點,在amd64處理器上,相對容易做到。而安騰,幾乎是另外一回事。 (後來Intel也生產了兼容amd64的處理器,但那是後話。)雖然我說“相對”容易做到,但也不是空手套白狼。當操作系統運行在64位時,怎麼才能保證已經存在的32位應用程序以為自己仍然運行在32位系統上呢?微軟的解決方案是:Wow64,全稱是32bit Windows On 64bit Windows(64位Windows上的32位Windows)。
你也可以這樣理解,雖然整個系統是運行在64位模式,但如果一個應該程序是32位的,Windows會在64位的基礎上,加載一個“32位的Windows”。這樣,這個32位應用程序就以為自己是運行在32位的系統之上的。於是,你也可以想像,這就意味著,64位的Windows,不但帶有64位操作系統應有的系統文件,還帶有32位系統應有的系統文件。
我們都知道的是,Windows系統的主要係統文件都是放在一個叫做System32的文件夾中的。為了能同時放下兩套系統文件,Windows會在64位的系統上,增加了一個文件夾,叫SysWow64。這便有了一個問題,System32和SysWow64裡面,哪個放的是64位的系統文件,哪個放的是32位的系統文件呢?如果你還記得Wow64指的是64位Windows上的32位Windows,那麼,你就能會想到,SysWow64裡放的是32位的系統文件。但你也可能會問,為什麼一個明明叫System32的文件夾裝的是64位的系統文件,而一個明明叫SysWow64的文件夾裝的卻是32位的系統文件呢?既然是64位的系統,為什麼不能有System64和System32這樣的文件夾呢? 這個問題問得很好。答案也很簡單:人在江湖,身不由己。
兼容性
如果我問你,可曾有多少機會接觸過安騰處理器呢?我想,對於一般人來講,應該是沒有的。那為什麼amd64會大行其道,而安騰處理器卻鮮為人知呢?還是因為一個軟硬件設計上的關鍵概念:兼容性。正是因為安騰處理器,沒有做好對已有的32位系統提供良好的支持,便其一直處於市場的邊緣。這和你不會買一台看不了模擬信號頻道的高清電視是一個道理。
之前我們談到的兼容性,是指在64位Windows上,兼容已經有的32位應用程序。現在考慮另一種兼容性。如果你寫了一個很牛的32位的應用程序,現在,你想把它變成64位的應用程序,以更充分地利用64位處理器所帶來的新的處理能力。你肯定覺得,這不就是讓64位編譯器編譯一遍就完了的事兒麼?可能你發現,這並不是骨感的現實。你突然發現,你的程序裡,為了某些你已經想不起來的原因,把System32這個文件夾,寫死在了你的程序裡。而這個System32中的32,讓你很不安。你嘗試著運行了你的程序,卻發現一切正常。為什麼呢?因為這是Windows系統的另一個兼容性方面的努力:讓一個已有的32位應用程序,不加修改或者盡可能少地加以修改,便可以被編譯成64位應用程序並在64位Windows上運行。
其實,把System32這樣的路徑,寫死在程序裡,並不是一個個案。所以,為了保證這些應用程序可以順利地過渡到64位,Windows最後還是決定讓64位的系統文件放在System32的文件夾下。而讓32位的系統文件,搬到了SysWow64中去。你肯定會想,那讓32位搬到SysWow64中去以後,那些寫死在32位應用程序中的System32怎麼辦?答:Windows會給他們轉向到SysWow64中去。那讓64位中的System32轉向到System64不也是一樣麼?真的一樣麼?不一樣麼?真的一樣麼?不一樣麼?真的不一樣。
作為64位Windows操作系統,當然是希望能充分發揮64位處理器的潛力,讓應用程序更有效率地運行。如果在運行64位應用程序時,總要檢查是否需要轉向,勢必影響程序運行效率。所以,不能給64位應用程序做沒有必要的轉向,如果說必須要轉,那就只能轉32位應用程序了。是的,沒有辦法,在64位操作系統中,32位應用程序要做一些小的犧牲。此外,為了保證32位應用程序不與64位應用程序相衝突,除了System32文件夾外,註冊表也需要為32位和64位提供兩套,也需要讓32位的應用程序在必要時重定向。
結論
所以SysWow64文件夾,是64位Windows,用來存放32位Windows系統文件的地方。
後記
兼容性是一個重要的事情。當然,也是一個很有意思的事情。如果你在Windows 7中運行”winver”,你就會發現,Windows 7原來是Windows 6.1。為什麼呢?事情是這樣的,Windows XP是Windows 5.2,Windows Vista開始變成了6.0,結果,很多應用程序只是檢查操作系統版本號的頭一位,發現不是5,於是就提示用戶說:“我們不支持Windows XP以前的系統”。這也是從Windows Vista的不成功中,學習到的一課。也許,以後永遠都沒有Windows 7.0也未可知啊。
如今的市場上已經找不到只支持32位指令集的X86處理器了,除了極個別老舊PC。當年64位X86處理器爭奪戰中Intel被AMD打個措手不及,後者聯合微軟搶先推出了X86_64指令集,Intel最終也接納了AMD的64位指令集。不過在伺服器市場上,Intel當時跟HP合作推出了安騰(Itanium)處理器,使用的是IA-64指令集體系,不過16年來安騰處理器越來越不受歡迎,Intel日前推出了代號Kittson的安騰9700系列處理器,這是最新但也是最後一代安騰處理器了,後續不再更新了。
工具書書本列表:
- Windows系統程式設計(第四版)
- Windows Device Driver Progamming驅動程式設計
- 深入淺出Windows驅動程式開發(平裝附光碟片)
- 這本在台灣已經絕版, 很難買到, 所幸作者為大陸人所以有簡體字版本,竹林蹊径 深入浅出Windows内核驱动开发.张佩,马勇,董鉴源
- 作者張佩的部落格: 易也技术
沒有留言:
張貼留言