事件程序驱动代码html,技术分享 - NT驱动程序与用户层程序基于事件EVENT实现同步通信...

背景

之前我们在《NT驱动程序与用户层程序通信》这篇文章中讲了,用户层程序使用 DeviceIoControl 将 IOCTL 控制码、输入缓冲区、输出缓冲区传入到内核;内核响应 IRP_MJ_DEVICE_CONTRL 消息,并从 IRP 中获取传入的 IOCTL 控制码、输入缓冲区、输出缓冲区,以此实现数据的交互。

但是,当内核层想主动传递数据到用户层,用户层又怎样才能知道呢?因为只有用户层知道内核层有数据输出的时候,它才会调用 DeviceIoControl 函数去获取数据。所以,本文要介绍的就是基于事件 EVENT 实现的同步框架,可以解决这个的问题。现在,我就把实现思路和原理整理成文档,分享给大家。

函数介绍

CreateEvent 函数CreateEvent是一个Windows API函数。它用来创建或打开一个命名的或无名的事件对象。如果想为对象指定一个访问掩码,应当使用CreateEventEx函数。

函数声明

HANDLECreateEvent(

LPSECURITY_ATTRIBUTESlpEventAttributes,// 安全属性

BOOLbManualReset,// 复位方式

BOOLbInitialState,// 初始状态

LPCTSTRlpName// 对象名称

);

参数

lpEventAttributes[in]

一个指向SECURITY_ATTRIBUTES结构的指针,确定返回的句柄是否可被子进程继承。如果lpEventAttributes是NULL,此句柄不能被继承。

bManualReset[in]

指定将事件对象创建成手动复原还是自动复原。如果是TRUE,那么必须用ResetEvent函数来手工将事件的状态复原到无信号状态。如果设置为FALSE,当一个等待线程被释放以后,系统将会自动将事件状态复原为无信号状态。

bInitialState[in]

指定事件对象的初始状态。如果为TRUE,初始状态为有信号状态;否则为无信号状态。

lpName[in]

指定事件的对象的名称,是一个以0结束的字符串指针。名称的字符格式限定在MAX_PATH之内。名字是对大小写敏感的。如果lpName为NULL,将创建一个无名的事件对象。

返回值

如果函数调用成功,函数返回事件对象的句柄。

如果函数失败,函数返回值为NULL,如果需要获得详细的错误信息,需要调用GetLastError。

ObReferenceObjectByHandle 函数提供对象句柄访问许可。

如果访问被允许,返回相应的对象体的指针。

函数声明

NTSTATUSObReferenceObjectByHandle(

_In_HANDLEHandle,

_In_ACCESS_MASKDesiredAccess,

_In_opt_POBJECT_TYPEObjectType,

_In_KPROCESSOR_MODEAccessMode,

_Out_PVOID*Object,

_In_opt_POBJECT_HANDLE_INFORMATIONHandleInformation

);

参数

Handle [in]

为一个对象指定一个打开的句柄。

DesiredAccess [in]

指定访问对象的类型。其中,EVENT_MODIFY_STATE 表示允许使用 KeSetEvent 和 KeResetEvent 函数。

ObjectType [in, optional]

表明指向对象是什么类型的。其中,*ExEventObjectType 表示对象指针类型为 PKEVENT。

AccessMode [in]

访问模式分UserMode 和KernelMode。其中,KernelMode 表示内核模式。

Object [out]

指向映射句柄对象的指针。

HandleInformation [out, optional]

驱动程序设置为 NULL。

返回值

成功,则返回 STATUS_SUCCESS;否则,返回其它 NTSTATUS 错误码。

KeSetEvent 函数如果事件尚未发出信号,则 KeSetEvent 函数将事件对象设置为信号状态,并返回事件对象的先前状态。

函数声明

LONGKeSetEvent(

_Inout_PRKEVENTEvent,

_In_KPRIORITYIncrement,

_In_BOOLEANWait

);

参数

Event[in,out]

指向调用者为其提供存储的初始化事件对象的指针。

Increment[in]

如果设置事件导致等待满足,则指定要应用的优先级增量。其中,IO_NO_INCREMENT 表示不增加优先级。

Wait

指定是否通过调用 KeWaitXxx 函数之一来立即跟踪对KeSetEvent的调用。 如果为TRUE,则KeSetEvent调用之后必须调用KeWaitForMultipleObjects,KeWaitForMutexObject或KeWaitForSingleObject。 有关详细信息,请参阅以下备注部分。

返回值

如果事件对象的先前状态发出信号,则返回非零值。

实现原理

我们通过事件 EVENT 实现用户层与内核层的同步操作,具体的实现原理如下:

首先,我们在用户层程序中调用 CreateEvent 函数创建事件 EVENT 并获取事件 EVENT 的句柄。事件初始状态为无信号,而且自动复原。

然后,用户层程序调用 DeviceIoControl 函数将上述创建的 EVENT 句柄传递到内核层驱动程序中,并调用 WaitForSingleObject 函数等待事件 EVENT 的响应。直到事件 EVENT 响应后,程序才会进行下一步操作。

接着,内核层程序就通过 IRP_MJ_DEVICE_CONTROL 消息响应函数获取从用户层传入的事件 EVENT 的句柄。调用 ObReferenceObjectByHandle 内核函数获取内核事件 EVENT 对象。

然后,内核驱动程序可以调用 PsCreateSystemThread 创建多线程,继续执行操作。要想通知用户层程序进行下一步操作,只需调用 KeSetEvent 内核函数,将事件 EVENT 对象的状态设置为信号状态。那么,用户层程序中的事件 EVENT 就是一个有信号状态,WaitForSingleObject 函数就不会阻塞,而是继续往下执行。这样,就可以成功从内核层通知到用户层进行操作了。

最后,用户层调用 CloseHandle 关闭事件 EVENT 句柄;内核层调用 ObDereferenceObject 释放内核事件 EVENT 对象。

这个框架的核心原理就是,将用户层的事件句柄传入内核层的驱动程序中,并有内核驱动程序设置事件对象的信号状态,以此触发用户层的响应。

编码实现

用户层代码int_tmain(intargc,_TCHAR*argv[])

{

HANDLE hEvent=NULL;

HANDLE hDevice=NULL;

charszOutput[MAX_PATH]={0};

DWORD dwOutput=MAX_PATH;

DWORD dwRet=0;

BOOL bRet=FALSE;

// 创建事件, 设置自动复位,初始状态为无信号

hEvent=::CreateEvent(NULL,FALSE,FALSE,NULL);

if(NULL==hEvent)

{

printf("CreateEvent Error[%d]\n",::GetLastError());

}

// 打开设备

hDevice=::CreateFile(SYM_NAME,GENERIC_READ|GENERIC_WRITE,

FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,

FILE_ATTRIBUTE_ARCHIVE,NULL);

if(INVALID_HANDLE_VALUE==hDevice)

{

printf("CreateFile Error[%d]\n",::GetLastError());

}

// 数据交互, 向内核层中传入事件句柄

bRet=::DeviceIoControl(hDevice,IOCTL_MY_TEST,&hEvent,sizeof(hEvent),szOutput,dwOutput,&dwRet,NULL);

if(FALSE==bRet)

{

printf("DeviceIoControl Error[%d]\n",::GetLastError());

}

// 一直等待事件的响应

::WaitForSingleObject(hEvent,INFINITE);

// 数据交互, 从内核层中获取数据

bRet=::DeviceIoControl(hDevice,IOCTL_MY_OUTPUT,NULL,0,szOutput,dwOutput,&dwRet,NULL);

if(FALSE==bRet)

{

printf("DeviceIoControl Error[%d]\n",::GetLastError());

}

// 显示

printf("[From Kernel Output]%s\n",szOutput);

// 关闭设备句柄

::CloseHandle(hEvent);

::CloseHandle(hDevice);

system("pause");

return0;

}

内核层代码

IRP_MJ_DEVICECONTROL 消息处理函数// IRP_MJ_DEVICE_CONTROL 消息处理函数

NTSTATUSDriverControlHandle(PDEVICE_OBJECT pDevObj,PIRP pIrp)

{

NTSTATUS status=STATUS_SUCCESS;

// 获取当前 IRP 栈空间数据

PIO_STACK_LOCATION pIoStackLocation=IoGetCurrentIrpStackLocation(pIrp);

// 获取输入/输出缓冲区

PVOID pBuffer=pIrp->AssociatedIrp.SystemBuffer;

// 获取输入缓冲区数据长度

ULONG ulInputLength=pIoStackLocation->Parameters.DeviceIoControl.InputBufferLength;

// 获取输出缓冲区数据长度

ULONG ulOutputLength=pIoStackLocation->Parameters.DeviceIoControl.OutputBufferLength;

// 实际输出数据长度

ULONG ulInfo=0;

// 获取控制码

ULONG ulControlCode=pIoStackLocation->Parameters.DeviceIoControl.IoControlCode;

// 根据操作码分别进行操作

switch(ulControlCode)

{

caseIOCTL_MY_TEST:

{

// 获取传入的事件句柄

HANDLE hUserEvent=*(HANDLE*)pBuffer;

// 处理类型32位、64位下类型不匹配的情况

if(4==ulInputLength)

{

hUserEvent=(HANDLE)((SIZE_T)hUserEvent&0x0FFFFFFFF);

}

// 根据事件句柄获取内核事件对象

status=ObReferenceObjectByHandle(hUserEvent,EVENT_MODIFY_STATE,*ExEventObjectType,KernelMode,(PVOID)(&g_pKernelEvent),NULL);

if(!NT_SUCCESS(status))

{

DbgPrint("ObReferenceObjectByHandle Error[0x%X]\n",status);

g_pKernelEvent=NULL;

break;

}

// 创建多线程, 执行操作, 执行完毕后, 发送事件通知用户层

HANDLE hThread=NULL;

PsCreateSystemThread(&hThread,0,NULL,NtCurrentProcess(),NULL,ThreadProc,g_pKernelEvent);

break;

}

caseIOCTL_MY_OUTPUT:

{

RtlCopyMemory(pBuffer,g_szOutputBuffer,50);

ulInfo=50;

break;

}

default:

break;

}

pIrp->IoStatus.Status=status;

pIrp->IoStatus.Information=ulInfo;

IoCompleteRequest(pIrp,IO_NO_INCREMENT);

returnstatus;

}

多线程处理函数// 多线程处理函数

VOIDThreadProc(PVOIDStartContext)

{

DbgPrint("Enter ThreadProc\n");

// 获取内核对象

PKEVENT pKernelEvent=(PKEVENT)StartContext;

// 设置输出缓冲区

RtlCopyMemory(g_szOutputBuffer,"I am DemonGan From Kernel Event.",(1+strlen("I am DemonGan From Kernel Event.")));

// 发送事件, 将事件对象设置为信号状态

if(NULL!=pKernelEvent)

{

KeSetEvent(pKernelEvent,IO_NO_INCREMENT,FALSE);

}

// 释放事件对象

ObDereferenceObject(pKernelEvent);

pKernelEvent=NULL;

DbgPrint("Leave ThreadProc\n");

}

程序测试

在 Win7 32 位系统下,驱动程序正常执行:

553418b2fbbcc7fdff67398e73ee9e0f.png

在 Win10 64 位系统下,驱动程序正常执行:

7cc2f7c730d368b0f7618a8d0f0a7af3.png

总结

这个框架理解起来不是很复杂,关键是理解事件 EVENT 的同步处理,实现操作的先后顺序。

参考

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值