9、Windows驱动开发技术详解笔记(5) 基本语法回顾
?
?
5、在驅動中獲取系統時間1)獲取啟動毫秒數
?在ring3 我們可以通過一個GetTickCount 函數來獲得自系統啟動開始的毫秒數,在ring0也有一個與之對應的KeQueryTickCount 函數。不幸的是,這個函數并不能直接返回毫秒數,它返回的是“滴答”數,而一個時鐘“滴答”到底是多久,這在不同的系統中可能是不同的,因此我們還需要另外一個函數的輔助,即KeQueryTimeIncrement 函數。KeQueryTimeIncrement 函數可以返回一個“滴答”表示多少個100 納秒,注意這里的單位是100 納秒。
2)獲取系統時間
在ring3 獲取系統時間是非常簡單的,我們直接使用GetLocalTime 就可以通過一個系統時間結構體SYSTEMTIME 來返回當前時間。到了ring0我們可以使用KeQuerySystemTime來獲得當前時間,但它其實是一個格林威治時間,與ring3得到的LocalTime 不同,因此我們還需要使用ExSystemTimeToLocalTime
函數將這個格林威治時間轉換成當地時間。事情到這里還沒有結束,現在我們獲得的當地時間不是一個容易閱讀的格式,因此我們還要使用RltTimeToTimeFieldh 函數將其轉換成容易閱讀的格式。
3)兩個小例程
代碼 1 /************************************************************************2
3 * 函數名稱:MyGetTickCount
4
5 * 功能描述:獲取tick數目
6
7 * 參數列表:
8
9 * 返回 值:返回狀態
10
11 *************************************************************************/
12
13 VOID
14
15 MyGetTickCount()
16
17 {
18
19 LARGE_INTEGER tick_count;
20
21 ULONG inc;
22
23 inc = KeQueryTimeIncrement();
24
25 KeQueryTickCount(&tick_count);
26
27 ?// 因為1 毫秒等于1000000 納秒,而inc 的單位是100 納秒
28
29 ?// 所以除以10000 即得到當前毫秒數
30 ?
31 tick_count.QuadPart *= inc;
32
33 tick_count.QuadPart /= 10000;
34
35 KdPrint(("[Test] TickCount : %d", tick_count.QuadPart));
36
37 }
38
39 /************************************************************************
40
41 * 函數名稱:MyGetCurrentTime
42
43 * 功能描述:獲取當前系統時間
44
45 * 參數列表:
46
47 * 返回 值:返回狀態
48
49 *************************************************************************/
50
51 VOID
52
53 MyGetCurrentTime()
54
55 {
56
57 LARGE_INTEGER CurrentTime;
58
59 LARGE_INTEGER LocalTime;
60
61 TIME_FIELDS TimeFiled;
62
63 static WCHAR Time_String[32] = {0};
64
65 // 這里得到的其實是格林威治時間
66
67 KeQuerySystemTime(&CurrentTime);
68
69 // 轉換成本地時間
70
71 ExSystemTimeToLocalTime(&CurrentTime, &LocalTime);
72
73 // 把時間轉換為容易理解的形式
74
75 RtlTimeToTimeFields(&LocalTime, &TimeFiled);
76
77 KdPrint(("[Test] NowTime : %4d-%2d-%2d %2d:%2d:%2d",
78
79 TimeFiled.Year, TimeFiled.Month, TimeFiled.Day,
80
81 TimeFiled.Hour, TimeFiled.Minute, TimeFiled.Second));
82
83 }
?
4)定時器
使用定時器
KeSetTimer()
http://msdn.microsoft.com/en-us/library/ff553286%28VS.85%29.aspx
這個函數的原型如下:
BOOLEAN
KeSetTimer(
IN PKTIMER Timer, // 定時器
IN LARGE_INTEGER DueTime, // 延后執行的時間
IN PKDPC Dpc OPTIONAL // 要執行的回調函數結構
);
這是因為需要提供一個回調函數。初始化Dpc的函數原型如下:
VOID
KeInitializeDpc(
IN PRKDPC Dpc,
IN PKDEFERRED_ROUTINE DeferredRoutine,
IN PVOID DeferredContext
);
這是一個“延時執行”的過程。每次執行了后,下次就不會再被調用了。如果想要定時反復執行,就必須在每次CustomDpc(DeferredRoutine)函數被調用的時候,再次調用KeSetTimer,來保證下次還可以執行。
注意的是,CustomDpc將運行在APC中斷級。因此并不是所有的事情都可以做(在調用任何內核系統函數的時候,請注意WDK說明文檔中標明的中斷級要求。)因此要完全實現定時器的功能,需要自己封裝一些東西。下面的結構封裝了全部需要的信息:
// 內部時鐘結構
typedef struct MY_TIMER_
{
KDPC dpc;
KTIMER timer;
PKDEFERRED_ROUTINE func;
PVOID private_context;
} MY_TIMER,*PMY_TIMER;
代碼 1 // 初始化這個結構:
2
3 void MyTimerInit(PMY_TIMER timer, PKDEFERRED_ROUTINE func)
4
5 {
6
7 // 請注意,我把回調函數的上下文參數設置為timer,為什么要
8
9 // 這樣做呢?
10
11 KeInitializeDpc(&timer->dpc,sf_my_dpc_routine,timer);
12
13 timer->func = func;
14
15 KeInitializeTimer(&timer->timer);
16
17 return (wd_timer_h)timer;
18
19 }
20
21 // 讓這個結構中的回調函數在n毫秒之后開始運行:
22
23 BOOLEAN MyTimerSet(PMY_TIMER timer,ULONG msec,PVOID context)
24
25 {
26
27 LARGE_INTEGER due;
28
29 // 注意時間單位的轉換。這里msec是毫秒。
30
31 due.QuadPart = -10000*msec;
32
33 // 用戶私有上下文。
34
35 timer->private_context = context;
36
37 return KeSetTimer(&timer->timer,due,&mytimer->dpc);
38
39 };
40
41 // 停止執行
42
43 VOID MyTimerDestroy(PMY_TIMER timer)
44
45 {
46
47 KeCancelTimer(&mytimer->timer);
48
49 };
?
使用結構PMY_TIMER已經比結合使用KDPC和KTIMER簡便許多。但是還是有一些要注意的地方。真正的OnTimer回調函數中,要獲得上下文,必須要從timer->private_context中獲得。此外,OnTimer中還有必要再次調用MyTimerSet(),來保證下次依然得到執行。
VOID
MyOnTimer (
IN struct _KDPC *Dpc,
IN PVOID DeferredContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
)
{
// 這里傳入的上下文是timer結構,用來下次再啟動延時調用
PMY_TIMER timer = (PMY_TIMER)DeferredContext;
// 獲得用戶上下文
PVOID my_context = timer->private_context;
// 在這里做OnTimer中要做的事情
……
// 再次調用。這里假設每1秒執行一次
MyTimerSet(timer,1000,my_context);
};
6、在驅動中創建內核線程
1)創建
在ring3 我們可以使用CreateThread這個Win32 API 創建線程,在ring0也有與之對應的內核函數PsCreateSystemThread。
這個函數與CreateThread的使用很相似,它可以通過第一個參數返回線程的句柄,最后兩個參數分別指定線程函數的地址和參數,在ring3我們就是這么做的。
我們使用CreateThread創建的線程只屬于當前進程(不過CreateRemoteThread函數可以在指定進程中創建線程),而PsCreateSystemThread 函數默認情況下創建的卻是一個系統進程,它屬于進程名為“system”,PID=4的這個進程。不過PsCreateSystemThread也是可以創建用戶線程的,這取決于它的第四個參數ProcessHandle,如果它為空,則創建的即系統線程;如果它是一個進程句柄,則創建的就是屬于該指定進程的用戶線程。
線程函數是一個非常重要的部分,它決定了該線程具有什么樣的功能。線程函數必須按照如下規范聲明:
VOID ThreadProc(IN PVOID context);
這個VOID指針參數通過強制轉換可以達到很多特殊效果,給予了我們很大的自由度。我們還需要注意的一點,在內核里創建的線程必須自己調用PsTerminateSystemThread 來結束自身,它不能像ring3 的線程那樣可以在執行完畢后自動結束。
NTSTATUS
PsCreateSystemThread(
OUT PHANDLE ThreadHandle,
IN ULONG DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN HANDLE ProcessHandle OPTIONAL,
OUT PCLIENT_ID ClientId OPTIONAL,
IN PKSTART_ROUTINE StartRoutine,
IN PVOID StartContext);
這個函數的參數也很多。經驗如下:ThreadHandle用來返回句柄。放入一個句柄指針即可。DesiredAccess總是填寫0。后面三個參數都填寫NULL。最后的兩個參數一個用于改線程啟動的時候執行的函數。一個用于傳入該函數的參數。
2)線程同步
雖然多線程并不是真正的并發運行,但由于CPU分配的時間片很短,看起來它們就像是并發運行的一樣。
此前我們曾經介紹過自旋鎖,它就是一種典型的同步方案,不過在線程同步的時候通常不使用它,而是使用事件通知,此外還有類似ring3的臨界區、信號燈等方法。
下面我們介紹使用KEVENT事件對象進行同步的方法。
在使用KEVENT 事件對象前,需要首先調用內核函數KeInitialize Event 對其初始化,這
個函數的原型如下所示:
VOID
KeInitializeEvent(
IN PRKEVENT Event,
IN EVENT_TYPE Type,
IN BOOLEAN State);
第一個參數Event 是初始化事件對象的指針;第二個參數Type表明事件的類型。事件分兩種類型:一類是“通知事件”,對應參數為NotificationEvent,另一類是“同步事件”,對應參數為SynchronizationEvent;第三個參數State 如果為TRUE,則事件對象的初始化狀態為激發狀態,否則為未激發狀態。
如果創建的事件對象是“通知事件”,當事件對象變為激發態時,需要我們手動將其改回未激發態。如果創建的事件對象是“同步事件”,當事件對象為激發態時,如果遇到相應的KeWaitForXXXX 等內核函數,事件對象會自動變回到未激發態。設置事件的函數是KeSetEvent,可通過該函數修改事件對象的狀態。
3)一個例子
代碼 1 KEVENT kEvent;2
3 VOID
4
5 CreateThreadTest()
6
7 {
8
9 HANDLE hThread;
10
11 NTSTATUS status;
12
13 UNICODE_STRING ustrTest;
14
15 // 初始化
16
17 KeInitializeEvent(&kEvent, SynchronizationEvent, TRUE);
18
19 RtlInitUnicodeString(&ustrTest, L"This is a string for test!");
20
21 // 創建系統線程
22
23 status = PsCreateSystemThread(&hThread, 0, NULL, NULL, NULL, MyThreadFunc,
24
25 (PVOID)(&ustrTest));
26
27 if (!NT_SUCCESS(status))
28
29 {
30
31 KdPrint(("[Test] CreateThread Test Failed!"));
32
33 }
34
35 ZwClose(hThread);
36
37 // 等待事件
38
39 KeWaitForSingleObject(&kEvent, Executive, KernelMode, FALSE, 0);
40
41 }
42
43 // 線程函數
44
45 VOID
46
47 MyThreadFunc( IN PVOID context )
48
49 {
50
51 PUNICODE_STRING str = (PUNICODE_STRING)context;
52
53 KdPrint(("[Test] %d : %wZ", (int)PsGetCurrentProcessId(), str));
54
55 // 設置事件對象
56
57 KeSetEvent(&kEvent, 0, FALSE);
58
59 // 結束線程
60
61 PsTerminateSystemThread(STATUS_SUCCESS);
62
63 }
?
參考
【1】Windows 驅動開發技術詳解
【2】http://msdn.microsoft.com/en-us/library/ff565757%28VS.85%29.aspx
【3】Windows驅動學習筆記,灰狐
轉載于:https://www.cnblogs.com/mydomain/archive/2010/10/18/1855127.html
總結
以上是生活随笔為你收集整理的9、Windows驱动开发技术详解笔记(5) 基本语法回顾的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: wave格式分析,wave音频文件格式分
- 下一篇: 深入php内核一(概述)