9、Windows驱动开发技术详解笔记(5) 基本语法回顾

简介:     5、在驱动中获取系统时间 1)获取启动毫秒数  在ring3 我们可以通过一个GetTickCount 函数来获得自系统启动开始的毫秒数,在ring0也有一个与之对应的KeQueryTickCount 函数。

 

 

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驱动学习笔记,灰狐

目录
相关文章
|
3月前
|
监控 Ubuntu Linux
视频监控笔记(五):Ubuntu和windows时区同步问题-your clock is behind
这篇文章介绍了如何在Ubuntu和Windows系统中通过设置相同的时区并使用ntp服务来解决时间同步问题。
92 4
视频监控笔记(五):Ubuntu和windows时区同步问题-your clock is behind
|
3月前
|
监控 关系型数据库 MySQL
PowerShell 脚本编写 :自动化Windows 开发工作流程
PowerShell 脚本编写 :自动化Windows 开发工作流程
108 0
|
3月前
|
Ubuntu Linux Python
如何利用wsl-Ubuntu里conda用来给Windows的PyCharm开发
如何在WSL(Windows Subsystem for Linux)的Ubuntu环境中使用conda虚拟环境来为Windows上的PyCharm开发设置Python解释器。
251 0
|
4月前
|
存储 安全 程序员
Windows任务管理器开发原理与实现
Windows任务管理器开发原理与实现
|
5月前
|
vr&ar C# 图形学
WPF与AR/VR的激情碰撞:解锁Windows Presentation Foundation应用新维度,探索增强现实与虚拟现实技术在现代UI设计中的无限可能与实战应用详解
【8月更文挑战第31天】增强现实(AR)与虚拟现实(VR)技术正迅速改变生活和工作方式,在游戏、教育及工业等领域展现出广泛应用前景。本文探讨如何在Windows Presentation Foundation(WPF)环境中实现AR/VR功能,通过具体示例代码展示整合过程。尽管WPF本身不直接支持AR/VR,但借助第三方库如Unity、Vuforia或OpenVR,可实现沉浸式体验。例如,通过Unity和Vuforia在WPF中创建AR应用,或利用OpenVR在WPF中集成VR功能,从而提升用户体验并拓展应用功能边界。
97 0
|
5月前
|
开发者 C# Windows
WPF与游戏开发:当桌面应用遇见游戏梦想——利用Windows Presentation Foundation打造属于你的2D游戏世界,从环境搭建到代码实践全面解析新兴开发路径
【8月更文挑战第31天】随着游戏开发技术的进步,WPF作为.NET Framework的一部分,凭借其图形渲染能力和灵活的UI设计,成为桌面游戏开发的新选择。本文通过技术综述和示例代码,介绍如何利用WPF进行游戏开发。首先确保安装最新版Visual Studio并创建WPF项目。接着,通过XAML设计游戏界面,并在C#中实现游戏逻辑,如玩家控制和障碍物碰撞检测。示例展示了创建基本2D游戏的过程,包括角色移动和碰撞处理。通过本文,WPF开发者可更好地理解并应用游戏开发技术,创造吸引人的桌面游戏。
253 0
|
5月前
|
C# Windows 开发者
当WPF遇见OpenGL:一场关于如何在Windows Presentation Foundation中融入高性能跨平台图形处理技术的精彩碰撞——详解集成步骤与实战代码示例
【8月更文挑战第31天】本文详细介绍了如何在Windows Presentation Foundation (WPF) 中集成OpenGL,以实现高性能的跨平台图形处理。通过具体示例代码,展示了使用SharpGL库在WPF应用中创建并渲染OpenGL图形的过程,包括开发环境搭建、OpenGL渲染窗口创建及控件集成等关键步骤,帮助开发者更好地理解和应用OpenGL技术。
382 0
|
5月前
|
开发者 iOS开发 C#
Uno Platform 入门超详细指南:从零开始教你打造兼容 Web、Windows、iOS 和 Android 的跨平台应用,轻松掌握 XAML 与 C# 开发技巧,快速上手示例代码助你迈出第一步
【8月更文挑战第31天】Uno Platform 是一个基于 Microsoft .NET 的开源框架,支持使用 C# 和 XAML 构建跨平台应用,适用于 Web(WebAssembly)、Windows、Linux、macOS、iOS 和 Android。它允许开发者共享几乎全部的业务逻辑和 UI 代码,同时保持原生性能。选择 Uno Platform 可以统一开发体验,减少代码重复,降低开发成本。安装时需先配置好 Visual Studio 或 Visual Studio for Mac,并通过 NuGet 或官网下载工具包。
477 0
|
5月前
|
iOS开发 Android开发 MacOS
从零到全能开发者:解锁Uno Platform,一键跨越多平台应用开发的神奇之旅,让你的代码飞遍Windows、iOS、Android、macOS及Web,技术小白也能秒变跨平台大神!
【8月更文挑战第31天】从零开始,踏上使用Uno Platform开发跨平台应用的旅程。只需编写一次代码,即可轻松部署到Windows、iOS、macOS、Android及Web(通过WASM)等多个平台。Uno Platform为.NET生态带来前所未有的灵活性和效率,简化跨平台开发。首先确保安装了Visual Studio或VS Code及.NET SDK,然后选择合适的项目模板创建新项目。项目结构类似传统.NET MAUI或WPF项目,包含核心NuGet包。通过简单的按钮示例,你可以快速上手并构建应用。Uno Platform让你的技术探索之旅充满无限可能。
109 0
|
5月前
|
Kubernetes Cloud Native 开发者
探索云原生技术:Kubernetes入门与实践探索Windows操作系统的隐藏功能
【8月更文挑战第31天】在数字化转型的浪潮中,云原生技术成为企业提升敏捷性、效率和可靠性的关键。本文将带你了解云原生的核心组件之一——Kubernetes(K8s),通过浅显易懂的语言和实际代码示例,引导你步入这一强大工具的世界。无论你是初学者还是有经验的开发者,本篇都将为你打开一扇通向高效资源管理与自动化部署的大门。