9、Windows驱动开发技术详解笔记(5) 基本语法回顾
时间:2010-10-18 来源:edwardlewiswe
1)获取启动毫秒数
在ring3 我们可以通过一个GetTickCount 函数来获得自系统启动开始的毫秒数,在ring0也有一个与之对应的KeQueryTickCount 函数。不幸的是,这个函数并不能直接返回毫秒数,它返回的是“滴答”数,而一个时钟“滴答”到底是多久,这在不同的系统中可能是不同的,因此我们还需要另外一个函数的辅助,即KeQueryTimeIncrement 函数。KeQueryTimeIncrement 函数可以返回一个“滴答”表示多少个100 纳秒,注意这里的单位是100 纳秒。
2)获取系统时间
在ring3 获取系统时间是非常简单的,我们直接使用GetLocalTime 就可以通过一个系统时间结构体SYSTEMTIME 来返回当前时间。到了ring0我们可以使用KeQuerySystemTime来获得当前时间,但它其实是一个格林威治时间,与ring3得到的LocalTime 不同,因此我们还需要使用ExSystemTimeToLocalTime
函数将这个格林威治时间转换成当地时间。事情到这里还没有结束,现在我们获得的当地时间不是一个容易阅读的格式,因此我们还要使用RltTimeToTimeFieldh 函数将其转换成容易阅读的格式。
3)两个小例程
/************************************************************************
* 函数名称:MyGetTickCount
* 功能描述:获取tick数目
* 参数列表:
* 返回 值:返回状态
*************************************************************************/
VOID
MyGetTickCount()
{
LARGE_INTEGER tick_count;
ULONG inc;
inc = KeQueryTimeIncrement();
KeQueryTickCount(&tick_count);
// 因为1 毫秒等于1000000 纳秒,而inc 的单位是100 纳秒
// 所以除以10000 即得到当前毫秒数
tick_count.QuadPart *= inc;
tick_count.QuadPart /= 10000;
KdPrint(("[Test] TickCount : %d", tick_count.QuadPart));
}
/************************************************************************
* 函数名称:MyGetCurrentTime
* 功能描述:获取当前系统时间
* 参数列表:
* 返回 值:返回状态
*************************************************************************/
VOID
MyGetCurrentTime()
{
LARGE_INTEGER CurrentTime;
LARGE_INTEGER LocalTime;
TIME_FIELDS TimeFiled;
static WCHAR Time_String[32] = {0};
// 这里得到的其实是格林威治时间
KeQuerySystemTime(&CurrentTime);
// 转换成本地时间
ExSystemTimeToLocalTime(&CurrentTime, &LocalTime);
// 把时间转换为容易理解的形式
RtlTimeToTimeFields(&LocalTime, &TimeFiled);
KdPrint(("[Test] NowTime : %4d-%2d-%2d %2d:%2d:%2d",
TimeFiled.Year, TimeFiled.Month, TimeFiled.Day,
TimeFiled.Hour, TimeFiled.Minute, TimeFiled.Second));
}
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,可通过该函数修改事件对象的状态。
参考
【1】Windows 驱动开发技术详解
【2】http://msdn.microsoft.com/en-us/library/ff565757%28VS.85%29.aspx
【3】Windows驱动学习笔记,灰狐