定时器的实现

使用IO定时器

IO定时器每隔1s就会触发一次,从而进入到定时器例程中,如果某个操作是每n秒执行一次(n为正整数)可以考虑在定时器例程中记录一个计数器大小就为n,每次进入定时器例程中时将计数器减一,当计数器为0时,表示到达n秒,这个时候可以执行操作。IO定时器只适合处理整数秒的情况 在使用IO定时器之前需要对定时器进行初始化,初始化函数为IoInitializeTimer,定义如下:

NTSTATUS 
  IoInitializeTimer(
    IN PDEVICE_OBJECT  DeviceObject, //设备对象指针
    IN PIO_TIMER_ROUTINE  TimerRoutine,//定时器例程
    IN PVOID  Context//传给定时器例程的函数
    );

初始化完成后可以使用IoStartTimer来启动定时器,使用IoStopTimer来停止定时器,下面是一个例子

#define PAGEDCODE code_seg("PAGE")
#define LOCKEDCODE code_seg()
#define INITCODE code_seg("INIT")

#define PAGEDDATA data_seg("PAGE")
#define LOCKEDDATA data_seg()
#define INITDATA data_seg("INIT")

typedef struct _tag_DEVICE_EXTENSION
{
    PDEVICE_OBJECT DeviceObject;
    UNICODE_STRING uDeviceName;
    UNICODE_STRING uSymbolickName;
    LONG lTimerCount; //定时器触发时间,以秒为单位
}DEVICE_EXTENSION, *PDEVICE_EXTENSION;

NTSTATUS DriverEntry(DRIVER_OBJECT  *DriverObject, PUNICODE_STRING  RegistryPath)
{
    NTSTATUS status;
    LONG i;
    PDEVICE_OBJECT pDeviceObject;
    UNREFERENCED_PARAMETER(RegistryPath);
    DriverObject->DriverUnload = DriverUnload;

    //设置派遣函数,这些代码在这就省略了
    status = CreateDevice(DriverEntry, &pDeviceObject);
    IoStartTimer(pDeviceObject);
    return status;
}

#pragma LOCKEDCODE
VOID IoTimer(DEVICE_OBJECT  *DeviceObject,PVOID  Context)
{
    LONG ret;
    PDEVICE_EXTENSION pDeviceExtension;
    UNICODE_STRING uProcessName;
    PEPROCESS pCurrProcess;
    UNREFERENCED_PARAMETER(Context);
    pDeviceExtension = (PDEVICE_EXTENSION)(DeviceObject->DeviceExtension);
    ASSERT(NULL != pDeviceExtension);
    //采用互锁操作将定时器数减一
    InterlockedDecrement(&pDeviceExtension->lTimerCount);
    //判断当前时间是否到达3秒
    ret = InterlockedCompareExchange(&pDeviceExtension->lTimerCount, TIME_OUT, 0);
    if(0 == ret)
    {
        DbgPrint("3s time out\n");
    }

    pCurrProcess = IoGetCurrentProcess();
    RtlInitUnicodeString(&uProcessName, (PTSTR)((ULONG)pCurrProcess + 0x174));
    DbgPrint("the current process %wZ", uProcessName);
}

#pragma INITCODE
NTSTATUS CreateDevice(PDRIVER_OBJECT pDriverObject,PDEVICE_OBJECT *ppDeviceObject)
{
    NTSTATUS status;
    UNICODE_STRING uDeviceName;
    UNICODE_STRING uSymbolickName;
    PDEVICE_EXTENSION pDeviceExtension;
    RtlInitUnicodeString(&uDeviceName, DEVICE_NAME);
    RtlInitUnicodeString(&uSymbolickName, SYMBOLICK_NAME);

    if(NULL != ppDeviceObject)
    {
        //创建设备对象并填充设备扩展中的变量
        ...
        IoInitializeTimer(*ppDeviceObject, IoTimer, NULL);

        status = IoCreateSymbolicLink(&uSymbolickName, &uDeviceName);
        if(!NT_SUCCESS(status))
        {
            //出错的话就做一些清理工作
            ...
            return status;
        }

        if(NULL != pDeviceExtension)
        {
            RtlInitUnicodeString(&pDeviceExtension->uSymbolickName, SYMBOLICK_NAME);
        }
        return status;
    }
    return STATUS_UNSUCCESSFUL;
}

需要注意的是IO定时器例程是位于DISPATCH_LEVEL,所以它不能使用分页内存,所以在函数前加上一句#pragma LOCKEDCODE,表示它在非分页内存中

DPC定时器

DPC定时器相比IO定时器来说更加灵活,它可以指定任何时间间隔。DPC内部使用KTIMER这个内核对象进行定时,每当时间到达设置的时间,那么系统就会将对应的DPC例程加入到DPC队列中,当系统读取DPC队列时,这个DPC例程就会被执行,使用DPC定时器的步骤一般是: 1. 分别调用KeInitializeTimer和KeInitializeDpc初始化KTIMER对象和DPC对象 2. 用KeSetTimer开启定时器 3. 在DPC例程中再次调用KeSetTimer开启定时器 4. 调用KeCancelTimer关闭定时器 由于每次执行KeSetTimer都只会触发一次DPC例程,所以如果想要周期性的调用DPC例程,需要在DPC例程中再次调用KeSetTimer。 这些函数的定义如下:

VOID 
  KeInitializeDpc(
        IN PRKDPC  Dpc, //DPC对象
    IN PKDEFERRED_ROUTINE  DeferredRoutine, //DPC例程
    IN PVOID  DeferredContext//传给DPC例程的参数
    );
BOOLEAN 
  KeSetTimer(
    IN PKTIMER  Timer,//定时器
    IN LARGE_INTEGER  DueTime, //隔多久触发这个DPC例程,这个值是正数则表示从1601年1月1日到触发这个DPC例程所经历的时间,为负数,则表示从当前时间,间隔多长时间后触发,单位为100ns
    IN PKDPC  Dpc OPTIONAL //传入上面初始化的DPC对象
    );

下面是一个使用的例子

typedef struct _tag_DEVICE_EXTENSION
{
    PDEVICE_OBJECT pDeviceObj;
    UNICODE_STRING uDeviceName;
    UNICODE_STRING uSymbolickName;
    KTIMER timer;
    KDPC Dpc;
}DEVICE_EXTENSION, *PDEVICE_EXTENSION;

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
{
    PDEVICE_EXTENSION pDeviceExtension;
    PDEVICE_OBJECT pDeviceObj;
    int i;
    NTSTATUS status;
    LARGE_INTEGER time_out;
    UNREFERENCED_PARAMETER(pRegistryPath);
    pDriverObject->DriverUnload = DriverUnload;
    //设置派遣函数
    ...
    status = CreateDevice(pDriverObject, &pDeviceObj);
    //失败处理
    ...
    //设置定时器
    time_out.QuadPart = -1 * 10000000; //1s = 1000000000ns
    status = KeSetTimer(&pDeviceExtension->timer,time_out, &pDeviceExtension->Dpc);
    return STATUS_SUCCESS;
}

VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
    //该函数主要用来清理相关资源
    ...
}

NTSTATUS DefauleDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    //默认返回成功
}

NTSTATUS CreateDevice(PDRIVER_OBJECT pDriverObj, PDEVICE_OBJECT *ppDeviceObj)
{
    PDEVICE_EXTENSION pDevEx;
    PDEVICE_OBJECT pDevObj;
    UNICODE_STRING uDeviceName;
    UNICODE_STRING uSymbolicName;
    NTSTATUS status;
    //创建设备对象,填充扩展设备内容
    ...
    //初始化KTIMER DPC
    KeInitializeTimer(&pDevEx->timer);
    KeInitializeDpc(&pDevEx->Dpc, TimerDpc, pDevObj);

    //设置连接符号
    ...
    return STATUS_SUCCESS;
}

VOID TimerDpc(
    __in struct _KDPC  *Dpc,
    __in_opt PVOID  DeferredContext,
    __in_opt PVOID  SystemArgument1,
    __in_opt PVOID  SystemArgument2
    )
{
    static int i = 0;
    PTSTR pProcessName;
    PEPROCESS pEprocess;
    LARGE_INTEGER time_out;
    PDEVICE_OBJECT pDevObj = (PDEVICE_OBJECT)DeferredContext;
    PDEVICE_EXTENSION pDevEx = (PDEVICE_EXTENSION)(pDevObj->DeviceExtension);
    ASSERT(NULL != pDevObj);

    pEprocess = PsGetCurrentProcess();
    pProcessName = (PTSTR)((ULONG)pEprocess + 0x174);

    DbgPrint("%d Call TimerDpc, Process: %s\n", i, pProcessName);

    time_out.QuadPart = -1 * 10000000; //1s = 1000000000ns
    KeSetTimer(&pDevEx->timer, time_out, &pDevEx->Dpc);
    i++;
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Windows Community

Windows Community Toolkit 4.0 - DataGrid - Part01

在上面一篇 Windows Community Toolkit 4.0 - DataGrid - Overview 中,我们对 DataGrid 控件做了一个概...

13220
来自专栏三流程序员的挣扎

Flutter 学习记2 - 首个应用

void main() 是入口方法,=> 用于单行方法,就是函数签名和函数体间的连接符号,感觉作用和 Kotlin 的单行函数体用 = 类似。既然这样,把代码修...

14610
来自专栏逸鹏说道

C# 温故而知新:Stream篇(五)下

对于重写的方法这里不再重复说明,大家可以参考我写的第一篇 以下是memoryStream独有的方法 virtual byte[] GetBuffer() 这个方...

371100
来自专栏SeanCheney的专栏

《利用Python进行数据分析·第2版》第6章 数据加载、存储与文件格式6.1 读写文本格式的数据6.2 二进制数据格式6.3 Web APIs交互6.4 数据库交互6.5 总结

访问数据是使用本书所介绍的这些工具的第一步。我会着重介绍pandas的数据输入与输出,虽然别的库中也有不少以此为目的的工具。 输入输出通常可以划分为几个大类:读...

60860
来自专栏Ryan Miao

Spring resource bundle多语言,单引号format异常

Spring resource bundle多语言,单引号format异常 source code 前言 十一假期被通知出现大bug,然后发现是多语言翻译问题。...

40180
来自专栏软件开发

JavaSE学习总结(二)——Java语言基础

一、Java程序预览 Java的语法与C非常类似,这里先使用几个非常简单的程序以点带面来区分C语Java的区分再细讲每个知识点。该文仅针对有编程基础的朋友参考。...

24780
来自专栏Spark学习技巧

重要 : 优化flink的四种方式

flink这个框架在逐步变为流处理的主流。本文,我们将针对flink性能调优讲四种不同的方法。

49920
来自专栏企鹅号快讯

python案例-爬取大学排名

一个好玩的爬虫 明天就要考试了,就是不想复习,就想去写代码,学习编程!2018,第一炮。 ? 技术路线:request-bs4 程序结构: 1.从网上获取大学排...

29250
来自专栏跟着阿笨一起玩NET

运行时自定义PropertyGrid显示属性项目

在PropertyGrid所显示的属性内容包括属性分类(Category)及组件属性,

21920
来自专栏Spark学习技巧

textFile构建RDD的分区及compute计算策略

1,textFile A),第一点,就是输入格式,key,value类型及并行度的意义。 def textFile( path: String, mi...

26670

扫码关注云+社区

领取腾讯云代金券