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

);

这是一个“延时执行”的过程。每次执行了后,下次就不会再被调用了。如果想要定时反复执行,就必须在每次CustomDpcDeferredRoutine)函数被调用的时候,再次调用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已经比结合使用KDPCKTIMER简便许多。但是还是有一些要注意的地方。真正的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 }

 

参考

1Windows 驱动开发技术详解

2http://msdn.microsoft.com/en-us/library/ff565757%28VS.85%29.aspx

3Windows驱动学习笔记,灰狐

posted @ 2010-10-18 22:09  浪里飞  阅读(2497)  评论(0编辑  收藏  举报