5、Windows驱动开发技术详解笔记(1) 入门基础驱动程序结构
NT式
1、Driver.h頭文件中包含了開發NT式驅動所需要的NTDDK.h,此外還定義了幾個標志來指明函數和變量分配在分頁內存還是非分頁內存中。Windows驅動程序的入口函數是DriverEntry函數。WDM式的驅動程序要導入的頭文件是WDM.h。
代碼
1 #ifdef __cplusplus
2
3 extern "C"
4
5 {
6
7 #endif
8
9 #include <NTDDK.h>
10
11 #ifdef __cplusplus
12
13 }
14
15 #endif
16
17 #define PAGEDCODE code_seg("PAGE")
18
19 #define LOCKEDCODE code_seg()
20
21 #define INITCODE code_seg("INIT")
22
23 #define PAGEDDATA data_seg("PAGE")
24
25 #define LOCKEDDATA data_seg()
26
27 #define INITDATA data_seg("INIT")
28
29 #define arraysize(p) (sizeof(p)/sizeof((p)[0]))
30
31 typedef struct _DEVICE_EXTENSION {
32
33 PDEVICE_OBJECT pDevice;
34
35 UNICODE_STRING ustrDeviceName; //設備名稱
36
37 UNICODE_STRING ustrSymLinkName; //符號鏈接名
38
39 } DEVICE_EXTENSION, *PDEVICE_EXTENSION;
40
41 // 函數聲明
42
43 NTSTATUS CreateDevice (IN PDRIVER_OBJECT pDriverObject);
44
45 VOID HelloDDKUnload (IN PDRIVER_OBJECT pDriverObject);
46
47 NTSTATUS HelloDDKDispatchRoutine(IN PDEVICE_OBJECT pDevObj,
48
49 IN PIRP pIrp);
說明:
1)采用C++編程,所以需要用extern "C",因為我們導入的是C的函數的符號表。
2)在驅動中用到的變量或函數都需要指定分配在分頁或非分頁內存中,分頁內存在物理內存不夠的情況下可能會被交換出去,對于一些需要高IRQL的例程絕對不能被交換出頁面,因此它們必須被定義為非分頁內存。
3)DriverEntry需要放在INIT標志的內存中。
2、驅動程序的入口函數
代碼
1 /************************************************************************
2
3 * 函數名稱:DriverEntry
4
5 * 功能描述:初始化驅動程序,定位和申請硬件資源,創建內核對象
6
7 * 參數列表:
8
9 pDriverObject:從I/O管理器中傳進來的驅動對象
10
11 pRegistryPath:驅動程序在注冊表的中的路徑
12
13 * 返回值:返回初始化驅動狀態
14
15 *************************************************************************/
16
17 #pragma INITCODE
18
19 extern "C" NTSTATUS DriverEntry (
20
21 IN PDRIVER_OBJECT pDriverObject,
22
23 IN PUNICODE_STRING pRegistryPath )
24
25 {
26
27 NTSTATUS status;
28
29 KdPrint(("Enter DriverEntry\n"));
30
31 //注冊其他驅動調用函數入口
32
33 pDriverObject->DriverUnload = HelloDDKUnload;
34
35 pDriverObject->MajorFunction[IRP_MJ_CREATE] = HelloDDKDispatchRoutine;
36
37 pDriverObject->MajorFunction[IRP_MJ_CLOSE] = HelloDDKDispatchRoutine;
38
39 pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloDDKDispatchRoutine;
40
41 pDriverObject->MajorFunction[IRP_MJ_READ] = HelloDDKDispatchRoutine;
42
43 //創建驅動設備對象
44
45 status = CreateDevice(pDriverObject);
46
47 KdPrint(("DriverEntry end\n"));
48
49 return status;
50
51 }
1)猶如控制臺程序需要main、Win32 程序需要WinMain、DLL 程序需要DllMain 一樣,驅動程序也有自己的入口點,即DriverEntry。DriverEntry 需要被加載到INIT 內存區域中,這樣當驅動被卸載后它可以退出內存。
2)DriverEntry 是由內核中的I/O 管理器負責調用的,它有兩個參數DriverObject 和RegistryPath(當然形參的名字可以自己改變)。其中DriverObject 是由I/O管理器傳遞進來的驅動對象,RegistryPath 則指向此驅動負責的注冊表。
3)我們可以看到DriverEntry 首先是定義了一些變量,然后調用IoCreateDevice 創建設備對象,緊接著調用IoCreateSymbolicLink 創建符號鏈接。HelloDDKDispatchRoutine等是驅動程序在向Windows 的I/O 管理器注冊一些回調函數。上面代碼的含義是:當驅動程序將被卸載時自動調用HelloDDKUnload例程;當驅動程序接收到 IRP_MJ_CREATE 時自動調用HelloDDKDispatchRoutine。
現在可以把IRP_MJ_CREATE理解成類似ring3的“消息”,當我們的驅動程序接收到不同的IRP就表明發生了不同的事件,然后我們及時給予處理。
4)KdPrint是宏,用來輸出。類似于MFC中的TRACE。
5)#pragma INITCODE來指明此函數加載到INIT內存函數中。
6)在驅動對象DriverObject 中,有個函數指針數組MajorFunction,它里面的每一個元素都記錄著一個函數的地址對應著相應的IRP,我們可以通過簡單地設置這個數組將IRP 與相應的派遣函數關聯起來。諸如IRP_MJ_CREATE 其實是使用#define 定義的一個宏,比如IRP_MJ_CREATE 實際上就是0x00,而IRP_MJ_CLOSE 則是0x02 等。由于在進入DriverEntry 之前,I/O 管理器會將_IopInvalidDeviceRequest 的地址填滿整個MajorFunction 數組,因此除了我們自行設置過的IRP 之外,其他的IRP 都與系統默認的_IopInvalidDeviceRequest 函數關聯。
3、創建設備例程(函數)
代碼
1 /************************************************************************
2
3 * 函數名稱:CreateDevice
4
5 * 功能描述:初始化設備對象
6
7 * 參數列表:
8
9 pDriverObject:從I/O管理器中傳進來的驅動對象
10
11 * 返回 值:返回初始化狀態
12
13 *************************************************************************/
14
15 #pragma INITCODE
16
17 NTSTATUS CreateDevice (
18
19 IN PDRIVER_OBJECT pDriverObject)
20
21 {
22
23 NTSTATUS status;
24
25 PDEVICE_OBJECT pDevObj;
26
27 PDEVICE_EXTENSION pDevExt;
28
29 //創建設備名稱
30
31 UNICODE_STRING devName;
32
33 RtlInitUnicodeString(&devName,L"\\Device\\MyDDKDevice");
34
35 //創建設備
36
37 status = IoCreateDevice( pDriverObject,
38
39 sizeof(DEVICE_EXTENSION),
40
41 &(UNICODE_STRING)devName,
42
43 FILE_DEVICE_UNKNOWN,
44
45 0, TRUE,
46
47 &pDevObj );
48
49 if (!NT_SUCCESS(status))
50
51 return status;
52
53 pDevObj->Flags |= DO_BUFFERED_IO;
54
55 pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
56
57 pDevExt->pDevice = pDevObj;
58
59 pDevExt->ustrDeviceName = devName;
60
61 //創建符號鏈接
62
63 UNICODE_STRING symLinkName;
64
65 RtlInitUnicodeString(&symLinkName,L"\\??\\HelloDDK");
66
67 pDevExt->ustrSymLinkName = symLinkName;
68
69 status = IoCreateSymbolicLink( &symLinkName,&devName );
70
71 if (!NT_SUCCESS(status))
72
73 {
74
75 IoDeleteDevice( pDevObj );
76
77 return status;
78
79 }
80
81 return STATUS_SUCCESS;
82
83 }
******
RtlInitUnicodeString
http://msdn.microsoft.com/en-us/library/ms648420%28VS.85%29.aspx
IoCreateDevice
http://msdn.microsoft.com/en-us/library/ff548397%28VS.85%29.aspx
1),前面我們創建的設備對象雖然有個參數指定了設備名稱,但是這個設備名稱只能在內核態可見,也就說ring3 的應用層程序是看不見它的,因此驅動程序需要向ring3 公布一個符號鏈接,這個鏈接指向真正的設備名稱,而ring3 的應用程序可以通過該符號鏈接找到驅動程序進行通信。實際上我們經常所說的C 盤、D 盤就是一個符號鏈接,它們在內核中的真正設備對象是“\Device\HarddiskVolume1”和“\Device \HarddiskVolume2”。在內核模式下,符號鏈接是以“\??\”( 或“\DosDevices\”)開頭的,如C 盤就是“\??\C:”,
而在用戶模式下,則是以“\\.\”開頭的,如C 盤就是“\\.\C:”。
4、卸載驅動例程
代碼
1 #pragma PAGEDCODE
2
3 VOID HelloDDKUnload (IN PDRIVER_OBJECT pDriverObject)
4
5 {
6
7 PDEVICE_OBJECT pNextObj;
8
9 KdPrint(("Enter DriverUnload\n"));
10
11 pNextObj = pDriverObject->DeviceObject;//由驅動對象得到設備對象
12
13 while (pNextObj != NULL)
14
15 {
16
17 PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
18
19 pNextObj->DeviceExtension;
20
21 //刪除符號鏈接
22
23 UNICODE_STRING pLinkName = pDevExt->ustrSymLinkName;
24
25 IoDeleteSymbolicLink(&pLinkName);
26
27 //刪除設備對象
28
29 pNextObj = pNextObj->NextDevice;
30
31 IoDeleteDevice( pDevExt->pDevice );
32
33 }
34
35 }
卸載驅動例程是我們在DriverEntry 中自己定義的,當驅動被卸載時I/O管理器負責調用該例程,它主要做一些掃尾處理的工作。
KdPrint
由于驅動程序工作于內核態,不像控制臺的程序一樣可以使用printf 輸出一些信息,也不像Win32 程序可以通過MessageBox 來彈出一個對話框,它要想輸出一些信息,就需要調用DbgPrint 函數,不過這個函數輸出的信息我們無法直接看到,需要使用一些專門的工具,比如DbgView (KmdManager)等。
有些內容我們只想在調試版輸出,在發行版忽略,因此DDK 中定義了一個宏KdPrint,它在發行版不被編譯,只在調試版才會運行。KdPrint是這樣定義的:
#define KdPrint(_x_) DbgPrint _x_,在使用時最外層要有兩個連續的括號。
5、派遣例程
代碼
1 /************************************************************************
2
3 * 函數名稱:HelloDDKDispatchRoutine
4
5 * 功能描述:對讀IRP進行處理
6
7 * 參數列表:
8
9 pDevObj:功能設備對象
10
11 pIrp:從IO請求包
12
13 * 返回 值:返回狀態
14
15 *************************************************************************/
16
17 #pragma PAGEDCODE
18
19 NTSTATUS HelloDDKDispatchRoutine(IN PDEVICE_OBJECT pDevObj,
20
21 IN PIRP pIrp)
22
23 {
24
25 KdPrint(("Enter HelloDDKDispatchRoutine\n"));
26
27 NTSTATUS status = STATUS_SUCCESS;
28
29 // 完成IRP
30
31 pIrp->IoStatus.Status = status;
32
33 pIrp->IoStatus.Information = 0; // bytes xfered
34
35 IoCompleteRequest( pIrp, IO_NO_INCREMENT );
36
37 KdPrint(("Leave HelloDDKDispatchRoutine\n"));
38
39 return status;
40
41 }
派遣例程是處理IRP的。
編譯
DDK方式
進入相應目錄,Build
Source如下:
TARGETNAME=HelloDDK
TARGETTYPE=DRIVER
TARGETPATH=OBJ //編譯輸出目錄
INCLUDES=$(BASEDIR)\inc;\
$(BASEDIR)\inc\ddk;\
SOURCES=Driver.cpp\
VC方式:參見[1]第一章。
驅動安裝
用DriverStudio中的工具:DriverMonitor。
WDM式驅動
/************************************************************************
* 函數名稱:DriverEntry
* 功能描述:初始化驅動程序,定位和申請硬件資源,創建內核對象
* 參數列表:
pDriverObject:從I/O管理器中傳進來的驅動對象
pRegistryPath:驅動程序在注冊表的中的路徑
* 返回 值:返回初始化驅動狀態
*************************************************************************/
#pragma INITCODE
extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,
IN PUNICODE_STRING pRegistryPath)
{
KdPrint(("Enter DriverEntry\n"));
pDriverObject->DriverExtension->AddDevice = HelloWDMAddDevice; //相比NT,此回調函數的作用是創建設備對象并由PNP管理器調用。
...
/************************************************************************
* 函數名稱:HelloWDMAddDevice
* 功能描述:添加新設備
* 參數列表:
DriverObject:從I/O管理器中傳進來的驅動對象
PhysicalDeviceObject:從I/O管理器中傳進來的物理設備對象
* 返回 值:返回添加新設備狀態
*************************************************************************/
#pragma PAGEDCODE
NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject,
IN PDEVICE_OBJECT PhysicalDeviceObject)
{
PAGED_CODE();
KdPrint(("Enter HelloWDMAddDevice\n"));
...
PAGED_CODE();//宏,只有check版本中有效,當此例程所在的中斷請求超過APC_LEVEL時,會產生一個斷言。
***
對IRP_MN_REMOVE_DEVICE的處理,類似于NT式驅動中的卸載例程,而在WDM式驅動中,卸載例程幾乎不做處理。
******
Source文件:
TARGETNAME=HelloWDM
TARGETTYPE=DRIVER
DRIVERTYPE=WDM
TARGETPATH=OBJ
INCLUDES=$(BASEDIR)\inc;\
$(BASEDIR)\inc\ddk;\
SOURCES=HelloWDM.cpp\
編譯:
同NT
安裝:
用EzDriverInstall安裝或控制面版中添加硬件。
實際上,常見的Windows 驅動程序是可以分成兩類的:一類是不支持即插即用功能的NT 式驅動程序,另一類是支持即插即用的WDM 式驅動程序。NT 式驅動的安裝是基于服務的,可以通過修改注冊表進行,也可以直接通過服務函數如CreateService 進行安裝;但WDM 式驅動不同,它安裝的時候需要通過編寫一個inf 文件進行控制。除此之外,它們所使用的頭文件也不大相同,例如NT 式驅動往往需要導入一個名為“ntddk.h”的頭文件,而WDM 式驅動需要的卻是“wdm.h”頭文件。我們在學習的過程中所編寫的大多屬于NT 式驅動,除非我們需要自己的設備支持即插即用,才需要考慮編寫
WDM 式驅動程序。
參考
【1】Windows 驅動開發技術詳解
【2】http://msdn.microsoft.com/en-us/library/ff565757%28VS.85%29.aspx
總結
以上是生活随笔為你收集整理的5、Windows驱动开发技术详解笔记(1) 入门基础驱动程序结构的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 吃莲子吃多少合适?
- 下一篇: 切菜的木制砧板,在南方,比如福建,用一段