5、Windows驱动开发技术详解笔记(1) 入门基础驱动程序结构
NT式
1、Driver.h頭文件中包含了開發(fā)NT式驅(qū)動所需要的NTDDK.h,此外還定義了幾個標(biāo)志來指明函數(shù)和變量分配在分頁內(nèi)存還是非分頁內(nèi)存中。Windows驅(qū)動程序的入口函數(shù)是DriverEntry函數(shù)。WDM式的驅(qū)動程序要導(dǎo)入的頭文件是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; //設(shè)備名稱
36
37 UNICODE_STRING ustrSymLinkName; //符號鏈接名
38
39 } DEVICE_EXTENSION, *PDEVICE_EXTENSION;
40
41 // 函數(shù)聲明
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",因為我們導(dǎo)入的是C的函數(shù)的符號表。
2)在驅(qū)動中用到的變量或函數(shù)都需要指定分配在分頁或非分頁內(nèi)存中,分頁內(nèi)存在物理內(nèi)存不夠的情況下可能會被交換出去,對于一些需要高IRQL的例程絕對不能被交換出頁面,因此它們必須被定義為非分頁內(nèi)存。
3)DriverEntry需要放在INIT標(biāo)志的內(nèi)存中。
2、驅(qū)動程序的入口函數(shù)
代碼
1 /************************************************************************
2
3 * 函數(shù)名稱:DriverEntry
4
5 * 功能描述:初始化驅(qū)動程序,定位和申請硬件資源,創(chuàng)建內(nèi)核對象
6
7 * 參數(shù)列表:
8
9 pDriverObject:從I/O管理器中傳進(jìn)來的驅(qū)動對象
10
11 pRegistryPath:驅(qū)動程序在注冊表的中的路徑
12
13 * 返回值:返回初始化驅(qū)動狀態(tài)
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 //注冊其他驅(qū)動調(diào)用函數(shù)入口
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 //創(chuàng)建驅(qū)動設(shè)備對象
44
45 status = CreateDevice(pDriverObject);
46
47 KdPrint(("DriverEntry end\n"));
48
49 return status;
50
51 }
1)猶如控制臺程序需要main、Win32 程序需要WinMain、DLL 程序需要DllMain 一樣,驅(qū)動程序也有自己的入口點,即DriverEntry。DriverEntry 需要被加載到INIT 內(nèi)存區(qū)域中,這樣當(dāng)驅(qū)動被卸載后它可以退出內(nèi)存。
2)DriverEntry 是由內(nèi)核中的I/O 管理器負(fù)責(zé)調(diào)用的,它有兩個參數(shù)DriverObject 和RegistryPath(當(dāng)然形參的名字可以自己改變)。其中DriverObject 是由I/O管理器傳遞進(jìn)來的驅(qū)動對象,RegistryPath 則指向此驅(qū)動負(fù)責(zé)的注冊表。
3)我們可以看到DriverEntry 首先是定義了一些變量,然后調(diào)用IoCreateDevice 創(chuàng)建設(shè)備對象,緊接著調(diào)用IoCreateSymbolicLink 創(chuàng)建符號鏈接。HelloDDKDispatchRoutine等是驅(qū)動程序在向Windows 的I/O 管理器注冊一些回調(diào)函數(shù)。上面代碼的含義是:當(dāng)驅(qū)動程序?qū)⒈恍遁d時自動調(diào)用HelloDDKUnload例程;當(dāng)驅(qū)動程序接收到 IRP_MJ_CREATE 時自動調(diào)用HelloDDKDispatchRoutine。
現(xiàn)在可以把IRP_MJ_CREATE理解成類似ring3的“消息”,當(dāng)我們的驅(qū)動程序接收到不同的IRP就表明發(fā)生了不同的事件,然后我們及時給予處理。
4)KdPrint是宏,用來輸出。類似于MFC中的TRACE。
5)#pragma INITCODE來指明此函數(shù)加載到INIT內(nèi)存函數(shù)中。
6)在驅(qū)動對象DriverObject 中,有個函數(shù)指針數(shù)組MajorFunction,它里面的每一個元素都記錄著一個函數(shù)的地址對應(yīng)著相應(yīng)的IRP,我們可以通過簡單地設(shè)置這個數(shù)組將IRP 與相應(yīng)的派遣函數(shù)關(guān)聯(lián)起來。諸如IRP_MJ_CREATE 其實是使用#define 定義的一個宏,比如IRP_MJ_CREATE 實際上就是0x00,而IRP_MJ_CLOSE 則是0x02 等。由于在進(jìn)入DriverEntry 之前,I/O 管理器會將_IopInvalidDeviceRequest 的地址填滿整個MajorFunction 數(shù)組,因此除了我們自行設(shè)置過的IRP 之外,其他的IRP 都與系統(tǒng)默認(rèn)的_IopInvalidDeviceRequest 函數(shù)關(guān)聯(lián)。
3、創(chuàng)建設(shè)備例程(函數(shù))
代碼
1 /************************************************************************
2
3 * 函數(shù)名稱:CreateDevice
4
5 * 功能描述:初始化設(shè)備對象
6
7 * 參數(shù)列表:
8
9 pDriverObject:從I/O管理器中傳進(jìn)來的驅(qū)動對象
10
11 * 返回 值:返回初始化狀態(tài)
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 //創(chuàng)建設(shè)備名稱
30
31 UNICODE_STRING devName;
32
33 RtlInitUnicodeString(&devName,L"\\Device\\MyDDKDevice");
34
35 //創(chuàng)建設(shè)備
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 //創(chuàng)建符號鏈接
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),前面我們創(chuàng)建的設(shè)備對象雖然有個參數(shù)指定了設(shè)備名稱,但是這個設(shè)備名稱只能在內(nèi)核態(tài)可見,也就說ring3 的應(yīng)用層程序是看不見它的,因此驅(qū)動程序需要向ring3 公布一個符號鏈接,這個鏈接指向真正的設(shè)備名稱,而ring3 的應(yīng)用程序可以通過該符號鏈接找到驅(qū)動程序進(jìn)行通信。實際上我們經(jīng)常所說的C 盤、D 盤就是一個符號鏈接,它們在內(nèi)核中的真正設(shè)備對象是“\Device\HarddiskVolume1”和“\Device \HarddiskVolume2”。在內(nèi)核模式下,符號鏈接是以“\??\”( 或“\DosDevices\”)開頭的,如C 盤就是“\??\C:”,
而在用戶模式下,則是以“\\.\”開頭的,如C 盤就是“\\.\C:”。
4、卸載驅(qū)動例程
代碼
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;//由驅(qū)動對象得到設(shè)備對象
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 //刪除設(shè)備對象
28
29 pNextObj = pNextObj->NextDevice;
30
31 IoDeleteDevice( pDevExt->pDevice );
32
33 }
34
35 }
卸載驅(qū)動例程是我們在DriverEntry 中自己定義的,當(dāng)驅(qū)動被卸載時I/O管理器負(fù)責(zé)調(diào)用該例程,它主要做一些掃尾處理的工作。
KdPrint
由于驅(qū)動程序工作于內(nèi)核態(tài),不像控制臺的程序一樣可以使用printf 輸出一些信息,也不像Win32 程序可以通過MessageBox 來彈出一個對話框,它要想輸出一些信息,就需要調(diào)用DbgPrint 函數(shù),不過這個函數(shù)輸出的信息我們無法直接看到,需要使用一些專門的工具,比如DbgView (KmdManager)等。
有些內(nèi)容我們只想在調(diào)試版輸出,在發(fā)行版忽略,因此DDK 中定義了一個宏KdPrint,它在發(fā)行版不被編譯,只在調(diào)試版才會運行。KdPrint是這樣定義的:
#define KdPrint(_x_) DbgPrint _x_,在使用時最外層要有兩個連續(xù)的括號。
5、派遣例程
代碼
1 /************************************************************************
2
3 * 函數(shù)名稱:HelloDDKDispatchRoutine
4
5 * 功能描述:對讀IRP進(jìn)行處理
6
7 * 參數(shù)列表:
8
9 pDevObj:功能設(shè)備對象
10
11 pIrp:從IO請求包
12
13 * 返回 值:返回狀態(tài)
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方式
進(jìn)入相應(yīng)目錄,Build
Source如下:
TARGETNAME=HelloDDK
TARGETTYPE=DRIVER
TARGETPATH=OBJ //編譯輸出目錄
INCLUDES=$(BASEDIR)\inc;\
$(BASEDIR)\inc\ddk;\
SOURCES=Driver.cpp\
VC方式:參見[1]第一章。
驅(qū)動安裝
用DriverStudio中的工具:DriverMonitor。
WDM式驅(qū)動
/************************************************************************
* 函數(shù)名稱:DriverEntry
* 功能描述:初始化驅(qū)動程序,定位和申請硬件資源,創(chuàng)建內(nèi)核對象
* 參數(shù)列表:
pDriverObject:從I/O管理器中傳進(jìn)來的驅(qū)動對象
pRegistryPath:驅(qū)動程序在注冊表的中的路徑
* 返回 值:返回初始化驅(qū)動狀態(tài)
*************************************************************************/
#pragma INITCODE
extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,
IN PUNICODE_STRING pRegistryPath)
{
KdPrint(("Enter DriverEntry\n"));
pDriverObject->DriverExtension->AddDevice = HelloWDMAddDevice; //相比NT,此回調(diào)函數(shù)的作用是創(chuàng)建設(shè)備對象并由PNP管理器調(diào)用。
...
/************************************************************************
* 函數(shù)名稱:HelloWDMAddDevice
* 功能描述:添加新設(shè)備
* 參數(shù)列表:
DriverObject:從I/O管理器中傳進(jìn)來的驅(qū)動對象
PhysicalDeviceObject:從I/O管理器中傳進(jìn)來的物理設(shè)備對象
* 返回 值:返回添加新設(shè)備狀態(tài)
*************************************************************************/
#pragma PAGEDCODE
NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject,
IN PDEVICE_OBJECT PhysicalDeviceObject)
{
PAGED_CODE();
KdPrint(("Enter HelloWDMAddDevice\n"));
...
PAGED_CODE();//宏,只有check版本中有效,當(dāng)此例程所在的中斷請求超過APC_LEVEL時,會產(chǎn)生一個斷言。
***
對IRP_MN_REMOVE_DEVICE的處理,類似于NT式驅(qū)動中的卸載例程,而在WDM式驅(qū)動中,卸載例程幾乎不做處理。
******
Source文件:
TARGETNAME=HelloWDM
TARGETTYPE=DRIVER
DRIVERTYPE=WDM
TARGETPATH=OBJ
INCLUDES=$(BASEDIR)\inc;\
$(BASEDIR)\inc\ddk;\
SOURCES=HelloWDM.cpp\
編譯:
同NT
安裝:
用EzDriverInstall安裝或控制面版中添加硬件。
實際上,常見的Windows 驅(qū)動程序是可以分成兩類的:一類是不支持即插即用功能的NT 式驅(qū)動程序,另一類是支持即插即用的WDM 式驅(qū)動程序。NT 式驅(qū)動的安裝是基于服務(wù)的,可以通過修改注冊表進(jìn)行,也可以直接通過服務(wù)函數(shù)如CreateService 進(jìn)行安裝;但WDM 式驅(qū)動不同,它安裝的時候需要通過編寫一個inf 文件進(jìn)行控制。除此之外,它們所使用的頭文件也不大相同,例如NT 式驅(qū)動往往需要導(dǎo)入一個名為“ntddk.h”的頭文件,而WDM 式驅(qū)動需要的卻是“wdm.h”頭文件。我們在學(xué)習(xí)的過程中所編寫的大多屬于NT 式驅(qū)動,除非我們需要自己的設(shè)備支持即插即用,才需要考慮編寫
WDM 式驅(qū)動程序。
參考
【1】Windows 驅(qū)動開發(fā)技術(shù)詳解
【2】http://msdn.microsoft.com/en-us/library/ff565757%28VS.85%29.aspx
總結(jié)
以上是生活随笔為你收集整理的5、Windows驱动开发技术详解笔记(1) 入门基础驱动程序结构的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 吃莲子吃多少合适?
- 下一篇: KVM虚拟化技术(一)虚拟化简介