天天看點

6、Windows驅動開發技術詳解筆記(2) 基本文法回顧

1、字元串

Unicode 字元串有一個結構體定義如下:

typedef struct _UNICODE_STRING {

USHORT Length; // 字元串的長度(位元組數)

USHORT MaximumLength; // 字元串緩沖區的長度(位元組數)

PWSTR Buffer; // 字元串緩沖區

} UNICODE_STRING, *PUNICODE_STRING;

需要注意的是,當我們定義了一個UNICODE_STRING 變量之後,它的Buffer 域還沒有配置設定空間,是以我們不能直接指派,好的做法是使用微軟提供的Rtl 系列函數。

UNICODE_STRING str;

RtlInitUnicodeString(&str, L"my first string!");

或者如下所示:

#include <ntdef.h>

UNICODE_STRING str = RTL_CONSTANT_STRING(L"my first string!");

與ring3 不同,我們的UNICODE 字元串并不是以“\0”來表示字元串結束的,而是依靠UNICODE_STRING 的Length 域來确定。

1)字元串的很多操作都有相應的函數,例如字元串的複制可以使用RtlCopyUnicodeString函數,字元串的比較可以使用RtlCompareUnicodeString 函數,字元串轉換成大寫可以使用RtlUpcaseUnicodeString 函數(沒有轉換成小寫的),字元串與整數數字互相轉換分别可以使用RtlUnicodeStringToInteger 和RtlIntegerToUnicodeString 函數。

比如在輸出日志記錄的時候,我們往往同時涉及數字、字元等資訊,在C語言中我們可以使用sprintf 和swprintf 函數來完成任務,這兩個函數在驅動中仍然可以使用,但很不安全,因為有許多C 語言的運作時函數都是基于Win32 API 的,在驅動中絕對不能使用,如果我們不清楚哪些可以使用哪些不能使用,就都不要使用,而使用微軟推薦的Rtl 系列函數。對應sprintf 的功能函數是RtlStringCbPrintfW,它需要包含頭檔案“ntstrsafe.h”和靜态連接配接庫“ntsafestr.lib”。

<a href="http://msdn.microsoft.com/en-us/library/ff561817%28VS.85%29.aspx">http://msdn.microsoft.com/en-us/library/ff561817%28VS.85%29.aspx</a>

WCHAR buf[512] = { 0 };

UNICODE_STRING dst;

NTSTATUS status;

……

// 字元串初始化為空串。緩沖區長度為512*sizeof (WCHAR)

RtlInitEmptyString(dst,dst_buf,512*sizeof(WCHAR));

// 調用RtlStringCbPrintfW 來進行列印

status = RtlStringCbPrintfW(

dst-&gt;Buffer,L”file path = %wZ file size = %d \r\n”,

&amp;file_path,file_size);

//這裡調用wcslen 沒問題,這是因為RtlStringCbPrintfW列印的字元串是以空結束的。

dst-&gt;Length = wcslen(dst-&gt;Buffer) * sizeof (WCHAR);

RtlStringCbPrintfW在目标緩沖區記憶體不足的時候依然可以列印,但是多餘的部分被截

去了。傳回的status值為STATUS_BUFFER_OVERFLOW。調用這個函數之前很難知道究竟需要多長的緩沖區。一般都采取倍增嘗試。每次都傳入一個為前次嘗試長度為2 倍長度的新緩沖區,直到這個函數傳回STATUS_SUCCESS為止。

值得注意的是UNICODE_STRING 類型的指針,通常用%wZ可以列印出字元串。在不

能保證字元串為空結束的時候,必須避免使用%ws或者%s。其他的列印格式字元串與傳統

C 語言中的printf函數完全相同。可以盡情使用。

2)核心模式下各種開頭函數的差別

函數開頭含義

Cc

Cache manager

Cm

Configuration manager

Ex

Executive support routines

FsRtl

File system driver run-time library

Hal

Hardware abstraction layer

Io

I/O manager

Ke

Kernel

Lpc

Local Procedure Call

Lsa

Local security authentication

Mm

Memory manager

Nt

Windows 2000 system services (most of which are exported as Win32 functions),例如NtCreateFile 往往導出為CreateFile

Ob

Object manager

Po

Power manager

Pp

PnP manager

Ps

Process support

Rtl

Run-time library

Se

Security

Wmi

Windows Management Instrumentation

Zw

Mirror entry point for system services (beginning with Nt) that sets previous access mode to kernel, which eliminates parametervalidation, since Nt system services validate parameters only if previous access mode is user see Inside Microsoft Windows 2000

3)大部分的Win32 API 都是通過Native API 實作的,Native API 函數一般都是Win32 API函數前面加上Nt 兩個字元,例如CreateFile 函數對應着NtCreateFile 函數,這些Nt 函數都是在“ntdll.dll”實作的,而多數Win32 API 都是在“kernel.dll”導出的,也有少部分GDI或視窗相關的函數是在“gdi32.dll”和“user32.dll”導出的。

Native API 從使用者模式穿越進入到核心模式調用系統服務,這個穿越過程是通過軟中斷的方式進入的。這個軟中斷的實作方法在不同版本的Windows 實作方式略有不同,在Win 2K下是通過“int 2eh”實作的,在Win XP 是通過“sysenter”指令完成的。

軟中斷會将Native API 的參數和系統服務号的參數一起傳進核心模式,不同的NativeAPI 會對應不同的系統服務号,這個過程是由SSDT 輔助完成的。

系統服務函數一般和Native API 具有相同的名字,例如都是NtCreateFile,但它們的實作不同,系統服務調用是在“ntoskrnl.exe”導出的。

4)建立檔案

6、Windows驅動開發技術詳解筆記(2) 基本文法回顧
6、Windows驅動開發技術詳解筆記(2) 基本文法回顧

代碼

1 NTSTATUS

2

3 CreateFileTest( IN PUNICODE_STRING FileName )

4

5 {

6

7 HANDLE hFile = NULL;

8

9 NTSTATUS status;

10

11 IO_STATUS_BLOCK Io_Status_Block;

12

13  // 初始化檔案路徑

14  

15 OBJECT_ATTRIBUTES obj_attrib;

16

17 InitializeObjectAttributes( &amp;obj_attrib,

18

19 FileName,

20

21 OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,

22

23 NULL,

24

25 NULL );

26

27  // 建立檔案

28  

29 status = ZwCreateFile( &amp;hFile,

30

31 GENERIC_ALL,

32

33  &amp;obj_attrib,

34

35 &amp;Io_Status_Block,

36

37 NULL,

38

39 FILE_ATTRIBUTE_NORMAL,

40

41 FILE_SHARE_READ,

42

43 FILE_CREATE,

44

45 FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,

46

47 NULL,

48

49 0 );

50

51 if (NT_SUCCESS(status))

52

53 {

54

55 status = STATUS_SUCCESS;

56

57 }

58

59 else

60

61 {

62

63 status = Io_Status_Block.Status;

64

65 }

66

67 KdPrint(("hFile = %08X", hFile));

68

69 // 關閉句柄

70

71 if (hFile)

72

73 {

74

75 ZwClose(hFile);

76

77 }

78

79 return status;

80

81 }

5)長長整形:

typedef __int64 LONGLONG;

typedef union _LARGE_INTEGER {

struct {

ULONG LowPart;

LONG HighPart;

};

} u;

LONGLONG   QuadPart;

} LARGE_INTEGER;

這個共用體的友善之處在于,既可以很友善的得到高32位,低32位,也可以友善的得到整個64位。進行運算和比較的時候,使用QuadPart即可。

2、連結清單

1)記憶體的配置設定與釋放

傳統的C 語言中,配置設定記憶體常常使用的函數是malloc,但在驅動開發過程中這個函數不再有效。驅動中配置設定記憶體,最常用的是調用ExAllocatePoolWithTag 或ExAllocatePool。

// 定義一個記憶體配置設定标記

#define MEM_TAG ‘MyTt’

// 目标字元串,接下來它需要配置設定空間。

UNICODE_STRING dst = { 0 };

// 配置設定空間給目标字元串。根據源字元串的長度。

dst.Buffer = (PWCHAR)ExAllocatePoolWithTag(NonpagedPool,src-&gt;Length,M EM_TAG);

if(dst.Buffer == NULL)

{

// 錯誤處理

status = STATUS_INSUFFICIENT_RESOUCRES;

}

dst.Length = dst.MaximumLength = src-&gt;Length;

ExAllocatePoolWithTag 的第一個參數NonpagedPool 表明配置設定的記憶體是非分頁記憶體,這樣它們可以永遠存在于實體記憶體,而不會被分頁交換到硬碟上去;第二個參數是長度;第三個參數是一個所謂的“記憶體配置設定标記”。

記憶體配置設定标記用于檢測記憶體洩漏。一般每個驅動程式定義一個自己的記憶體标記,也可以在每個子產品中定義單獨的記憶體标記。記憶體标記是随意的32位數字,即使沖突也不會有什麼問題。此外也可以配置設定可分頁記憶體,使用PagedPool辨別第一個參數即可。ExAllocatePoolWithTag配置設定的記憶體可以使用 ExFreePool來釋放。

ExFreePool 隻需要提供需要釋放的指針即可。舉例如下:

ExFreePool(dst.Buffer);

dst.Buffer = NULL;

dst.Length = dst.MaximumLength = 0;

注意,ExFreePool不能用來釋放一個棧空間的指針,否則系統立刻崩潰。諸如下面的代碼将會招緻立刻藍屏的災難:

UNICODE_STRING src = RTL_CONST_STRING(L”My source string!”);

ExFreePool(src.Buffer);

請務必保持ExAllocatePoolWithTag 或ExAllocatePool 和ExFreePool 的成對關系。Windows核心提供了一個雙向連結清單結構LIST_ENTRY,此外還有一些其他的結構,比如SINGLE_LIST_ENTRY(單向連結清單)。

LIST_ENTRY 是一個雙向連結清單結構,通常的做法是我們自定義一個結構體,将LIST_ENTRY作為該結構體的一個子域,将LIST_ENTRY作為結構體的第一個子域是最簡單的做法。如下所示:

typedef struct _MYDATASTRUCT{

LIST_ENTRY ListEntry;

ULONG number;

} MYDATASTRUCT, *PMYDATASTRUCT;

如果把它放在後面,這時候,如果我們想擷取節點的位址,需要有一個計算偏移的過程,DDK 裡面提供了一個宏CONTAINING_RECORD 可以在指定結構中找到節點位址的指針。

<a href="http://msdn.microsoft.com/en-us/library/ff554296%28VS.85%29.aspx">http://msdn.microsoft.com/en-us/library/ff554296%28VS.85%29.aspx</a>

如:

for(p = my_list_head.Flink; p != &amp;my_list_head.Flink; p = p-&gt;Flink)

PMY_FILE_INFOR elem =

CONTAINING_RECORD(p,MY_FILE_INFOR, list_entry);

// 在這裡做需要做的事…

其中的CONTAINING_RECORD是一個WDK中已經定義的宏,作用是通過一個LIST_ENTRY結構的指針,找到這個結構所在的節點的指針。定義如下:

#define CONTAINING_RECORD(address, type, field) ((type *)( \

(PCHAR)(address) - \

(ULONG_PTR)(&amp;((type *)0)-&gt;field)))

從上面的代碼中可以總結如下的資訊:

LIST_ENTRY中的資料成員Flink指向下一個LIST_ENTRY。整個連結清單中的最後一個LIST_ENTRY的Flink不是空。而是指向頭節點。得到LIST_ENTRY之後,要用CONTAINING_RECORD來得到連結清單節點中的資料。

2)使用自旋鎖

6、Windows驅動開發技術詳解筆記(2) 基本文法回顧
6、Windows驅動開發技術詳解筆記(2) 基本文法回顧

1 KSPIN_LOCK my_spin_lock;

3 KIRQL irql;

5 // 初始化

7 KeInitializeSpinLock(&amp;my_spin_lock);

9 KeAcquireSpinLock(&amp;my_spin_lock,&amp;irql);

11 // do something …

13 KeReleaseSpinLock(&amp;my_spin_lock,irql);

14

15 一個例子程式:

17 VOID

19 LinkListTest()

21 {

23 LIST_ENTRY linkListHead; // 連結清單

25 PMYDATASTRUCT pData; // 節點資料

27 ULONG i = 0; // 計數

28

29 KSPIN_LOCK spin_lock; // 自旋鎖

31 KIRQL irql; // 中斷級别

33 // 初始化

35 InitializeListHead(&amp;linkListHead);

37 KeInitializeSpinLock(&amp;spin_lock);

39 //向連結清單中插入10 個元素

41 KdPrint(("[Test] Begin insert to link list"));

43 // 鎖定,注意這裡的irql 是個指針

45 KeAcquireSpinLock(&amp;spin_lock, &amp;irql);

47 for (i=0 ; i&lt;10 ; i++)

49 {

51 pData = (PMYDATASTRUCT)ExAllocatePool(PagedPool,sizeof(MYDATASTRUCT));

53 pData-&gt;number = i;

55 InsertHeadList(&amp;linkListHead,&amp;pData-&gt;ListEntry);

59 // 解鎖,注意這裡的irql 不是指針

61 KeReleaseSpinLock(&amp;spin_lock, irql);

63 //從連結清單中取出所有資料并顯示

65 KdPrint(("[Test] Begin remove from link list\n"));

67 // 鎖定

69 KeAcquireSpinLock(&amp;spin_lock, &amp;irql);

71 while(!IsListEmpty(&amp;linkListHead))

75 PLIST_ENTRY pEntry = RemoveTailList(&amp;linkListHead);

77 // 擷取節點位址

79 pData = CONTAINING_RECORD(pEntry, MYDATASTRUCT, ListEntry);

81 // 讀取節點資料

82

83 KdPrint(("[Test] %d\n",pData-&gt;number));

84

85 ExFreePool(pData);

86

87 }

88

89 // 解鎖

90

91 KeReleaseSpinLock(&amp;spin_lock, irql);

92

93 }

需要注意的是,像上述代碼中在函數中定義一個鎖的做法是沒有實際意義的,因為它是一個局部變量,被定義在棧中,每當有線程調用該函數時,都會重新初始化一個鎖,是以它就失去了本來的作用。

在實際的程式設計中,我們應該把鎖定義為一個全局變量、靜态(static )變量或者将其定義在堆中。不過在驅動中應該盡量避免使用全局變量,良好的做法是将需要全局通路的變量

定義為裝置擴充結構體DEVICE_EXTENSION 的一個子域。另外,我們還可以為每個連結清單都定義并初始化一個鎖,在需要向該連結清單插入或移除節點時不使用前面介紹的普通函數,而是使用如下方法:

ExInterlockedInsertHeadList(&amp;linkListHead, &amp;pData-&gt;ListEntry, &amp;spin_lock);

pData = (PMYDATASTRUCT)ExInterlockedRemoveHeadList(&amp;linkListHead, &amp;spin_lock);

此時在向連結清單中插入或移除節點時會自動調用關聯的鎖進行加鎖操作,可以有效地保證多線程安全性,加裁驅動除了使用DriverStudio外,還可以使用KmdManager等工具。

NT式驅動的安裝是基于服務的,可以通過修改系統資料庫進行,也可以直接通過服務函數如CreateService 進行安裝;但WDM 式驅動不同,它安裝的時候需要通過編寫一個inf 檔案進行控制。除此之外,它們所使用的頭檔案也不大相同,例如NT 式驅動往往需要導入一個名為“ntddk.h”的頭檔案,而WDM 式驅動需要的卻是“wdm.h”頭檔案。我們在學習的過程中所編寫的大多屬于NT 式驅動,除非我們需要自己的裝置支援即插即用,才需要考慮編寫WDM 式驅動程式。

繼續閱讀