zoukankan      html  css  js  c++  java
  • 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驱动学习笔记,灰狐

  • 相关阅读:
    foundation框架—结构体
    OC语言BLOCK和协议
    OC语言description方法和sel
    OC语言类的本质和分类
    清除浮动的常用方法
    php动态读取数据清除最右边距
    css背景图片定位练习(二): background-position的百分比
    css背景图片定位练习(一)
    行高不设单位的好处 line-height:1.8
    background:transparent的作用
  • 原文地址:https://www.cnblogs.com/mydomain/p/1855127.html
Copyright © 2011-2022 走看看