- 收藏
- 点赞
- 分享
- 举报
怎样在内核驱动程序和用户应用程序的消息通讯机制
驱动程序与应用程序运行与不同的环境又紧密合作,但是应用程序通知驱动程序易(IOCTL等),驱动程序通知应用程序却不易。一般的方法是单纯通过EVENT来进行,但是这种方法有其缺点:
1、EVENT只有信号态和非信号态两种区别,不能有附带的参数,因此一个EVENT只能对应一种事件,同时很多时候EVENT并不是在信号态,此时就需要应用程序在线程中等待,一旦事件较多那么线程就会较多,不但线程同步困难,程序也不易读写。
2、Windows 98对EVENT操作没有完全支持,像IoCreateXxxEvent并不被支持,因此要获得应用程序创建的事件句柄并不简单,这样驱动程序的通用性也被破坏了。
基于以上原因,单纯使用EVENT并不好,那么应该怎么做呢?经过实践,我总结出了一个较好的方法,原理是利用OVERLAPPED的异步调用、同时它又可以带参数的特性。具体做法如下:
1、在驱动程序的设备扩展中定义一个IRP变量,并在适当时候(调用IoCreateDevice初始化设备扩展后)初始化其为NULL。如 PIRP UserMessageIrp;
2、定义一个IOCTL,如:
#define IOCTL_DRIVER_USERMESSAGE CTL_CODE(FILE_DEVICE_UNKNOWN, \
0x801,\
METHOD_BUFFERED, \
FILE_ANY_ACCESS)
3、应用程序在启动或要监控驱动程序的状态时发送该IOCTL到驱动程序,注意应用程序在用CreateFile打开设备连接时一定要带FILE_FLAG_OVERLAPPED参数。
HANDLE FileIOWaiter = CreateEvent( NULL, TRUE, FALSE, NULL);
if( FileIOWaiter==NULL)
return GetLastError();
OVERLAPPED ol;
ol.Offset = 0;
ol.OffsetHigh = 0;
ol.hEvent = FileIOWaiter;
ULONG PnpMessage,nBytes;
if(!DeviceIoControl(hDevice,//设备句柄
IOCTL_DRIVER_USERMESSAGE
NULL,
0,
&PnpMessage,
sizeof(PnpMessage),
&nBytes,
&ol))
{
if(GetLastError()==ERROR_IO_PENDING)
{
while(WaitForSingleObject(FileIOWaiter, 100)==WAIT_TIMEOUT)
{//驱动程序没有消息传过来,循环等待
if(bInstanceisExit == TRUE)//应用程序退出标志
{
CancelIo(hDevice);//见5
}
}
GetOverlappedResult(hDevice, &ol, &nBytes, FALSE);//
//驱动程序有消息传过来,见6,得到数据。
}
}
//处理得到的数据,接7。
4、驱动程序在接到此IOCTL时如下处理:
ioControlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;
switch( ioControlCode)
{
...
case IOCTL_DRIVER_USERMESSAGE
ntStatus =
Irp->IoStatus.Status = STATUS_PENDING;
Irp->IoStatus.Information = 0;
IoMarkIrpPending(Irp);
IoSetCancelRoutine(Irp,DriverUserMessageCancelIrp);//见5
deviceExtension->UserMessageIrp = Irp;
return STATUS_PENDING;
...
}
驱动程序与应用程序运行与不同的环境又紧密合作,但是应用程序通知驱动程序易(IOCTL等),驱动程序通知应用程序却不易。一般的方法是单纯通过EVENT来进行,但是这种方法有其缺点:
1、EVENT只有信号态和非信号态两种区别,不能有附带的参数,因此一个EVENT只能对应一种事件,同时很多时候EVENT并不是在信号态,此时就需要应用程序在线程中等待,一旦事件较多那么线程就会较多,不但线程同步困难,程序也不易读写。
2、Windows 98对EVENT操作没有完全支持,像IoCreateXxxEvent并不被支持,因此要获得应用程序创建的事件句柄并不简单,这样驱动程序的通用性也被破坏了。
基于以上原因,单纯使用EVENT并不好,那么应该怎么做呢?经过实践,我总结出了一个较好的方法,原理是利用OVERLAPPED的异步调用、同时它又可以带参数的特性。具体做法如下:
1、在驱动程序的设备扩展中定义一个IRP变量,并在适当时候(调用IoCreateDevice初始化设备扩展后)初始化其为NULL。如 PIRP UserMessageIrp;
2、定义一个IOCTL,如:
#define IOCTL_DRIVER_USERMESSAGE CTL_CODE(FILE_DEVICE_UNKNOWN, \
0x801,\
METHOD_BUFFERED, \
FILE_ANY_ACCESS)
3、应用程序在启动或要监控驱动程序的状态时发送该IOCTL到驱动程序,注意应用程序在用CreateFile打开设备连接时一定要带FILE_FLAG_OVERLAPPED参数。
HANDLE FileIOWaiter = CreateEvent( NULL, TRUE, FALSE, NULL);
if( FileIOWaiter==NULL)
return GetLastError();
OVERLAPPED ol;
ol.Offset = 0;
ol.OffsetHigh = 0;
ol.hEvent = FileIOWaiter;
ULONG PnpMessage,nBytes;
if(!DeviceIoControl(hDevice,//设备句柄
IOCTL_DRIVER_USERMESSAGE
NULL,
0,
&PnpMessage,
sizeof(PnpMessage),
&nBytes,
&ol))
{
if(GetLastError()==ERROR_IO_PENDING)
{
while(WaitForSingleObject(FileIOWaiter, 100)==WAIT_TIMEOUT)
{//驱动程序没有消息传过来,循环等待
if(bInstanceisExit == TRUE)//应用程序退出标志
{
CancelIo(hDevice);//见5
}
}
GetOverlappedResult(hDevice, &ol, &nBytes, FALSE);//
//驱动程序有消息传过来,见6,得到数据。
}
}
//处理得到的数据,接7。
4、驱动程序在接到此IOCTL时如下处理:
ioControlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;
switch( ioControlCode)
{
...
case IOCTL_DRIVER_USERMESSAGE
ntStatus =
Irp->IoStatus.Status = STATUS_PENDING;
Irp->IoStatus.Information = 0;
IoMarkIrpPending(Irp);
IoSetCancelRoutine(Irp,DriverUserMessageCancelIrp);//见5
deviceExtension->UserMessageIrp = Irp;
return STATUS_PENDING;
...
}
5、定义IRP的Cancel例程,这是在应用程序要退出而驱动程序并没有完成该IRP时调用。
VOID DriverUserMessageCancelIrp(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp)
{
PDEVICE_EXTENSION deviceExtension;
deviceExtension = (PDEVICE_EXTENSION)
DeviceObject->DeviceExtension;
IoReleaseCancelSpinLock(Irp->CancelIrql);
// If this is our queued read, then unqueue it
if( Irp==deviceExtension->UserMessageIrp)
{
deviceExtension->UserMessageIrp = NULL;
}
// Whatever Irp it is, just cancel it
Irp->IoStatus.Status = STATUS_CANCELLED;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp,IO_NO_INCREMENT);
}
6、在驱动程序需要通知应用程序时,举个例子,设备拔出时在处理IRP_MN_REMOVE_DEVICE时,在调用IoDetachDevice之前: (#define PNP_REMOVE_DEVICE 0 //驱动程序和应用程序共同定义的消息类型)
...
ntStatus = DriverProcessUserMessageIrp(DeviceObject,PNP_REMOVE_DEVICE);
...
//DriverProcessUserPnpIrp的定义如下:
NTSTATUS
DriverProcessUserMessageIrp(
IN PDEVICE_OBJECT DeviceObject,
ULONG ProcessReason)
{
NTSTATUS ntStatus;
PVOID ioBuffer;
ULONG outputBufferLength;
PIO_STACK_LOCATION irpStack;
PIRP Irp;
PDEVICE_EXTENSION deviceExtension;
deviceExtension = (PDEVICE_EXTENSION)
DeviceObject->DeviceExtension;
Irp = deviceExtension->UserMessageIrp;
if(Irp == NULL)
return STATUS_SUCCESS;//这种情况是在设备启动后,应用程序并没有发送
//过该IRP,设备又要卸载时出现。
irpStack = IoGetCurrentIrpStackLocation (Irp);
// get pointers and lengths of the caller's (user's) IO buffer
ioBuffer = Irp->AssociatedIrp.SystemBuffer;
outputBufferLength = irpStack->Parameters.DeviceIoControl.OutputBufferLength;
if(ioBuffer!=NULL && sizeof(ProcessReason)<=outputBufferLength)
{
RtlCopyMemory(ioBuffer,
&ProcessReason,
sizeof(ProcessReason));
}
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = sizeof(ProcessReason);
IoSetCancelRoutine(Irp,NULL);//取消Cancel例程。
IoCompleteRequest(Irp,IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
7、此时应用程序的OVERLAPPED的EVENT会置位,WaitForSingleObject结束等待,应用程序应如此处理:
switch(PnpMessage)//定义见3,是不是很像Windows消息处理例程?:)
{
...
case PNP_REMOVE_DEVICE:
//处理过程就不必写了吧。
break;
...
}
至此驱动程序和应用程序就完成了一次消息传递,其余可类似循环。
以上程序段在98和2000下都经过测试。
TigerZD .2002.7.11.(完)
以下是在sfilter的框架上实现上面的思路:
一、驱动程序: 1、定义消息类型、全局变量
#define SFILTER_DRIVER_USERMESSAGE \
(ULONG) CTL_CODE(FILE_DEVICE_DISK_FILE_SYSTEM, 0x999, METHOD_BUFFERED, FILE_ANY_ACCESS )
PIRP gUserMessageIrp = NULL;
typedef enum _USER_MSG_TYPES {
USER_MSG_1 = 0x00000001L,
USER_MSG_2 = 0x00000002L,
USER_MSG_3 = 0x00000003L
} USER_MSG_TYPES;
2、处理IRP_MJ_DEVICE_CONTROL分派例程SfDeviceIoControl
NTSTATUS
SfDeviceIoControl( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp )
{
NTSTATUS Status = STATUS_SUCCESS;
PIO_STACK_LOCATION IrpSp;
PAGED_CODE();
if (IS_MY_CONTROL_DEVICE_OBJECT(DeviceObject)) {
//
// A request is being made on our control device object
//
Irp->IoStatus.Information = 0;
IrpSp = IoGetCurrentIrpStackLocation( Irp );
if (IrpSp->Parameters.DeviceIoControl.IoControlCode ==
SFILTER_DRIVER_USERMESSAGE) {
KdPrint(("SFilter: SfDeviceIoControl SFCONTROL_DRIVER_USERMESSAGE!\n"));
if (gUserMessageIrp != NULL) {
KdPrint( ("Sfilter: gUserMessageIrp != NULL, so cancel the Irp!\n"));
Irp->IoStatus.Status = STATUS_CANCELLED;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_CANCELLED;
}
Irp->IoStatus.Status = STATUS_PENDING;
Irp->IoStatus.Information = 0;
IoMarkIrpPending(Irp);
IoSetCancelRoutine(Irp,SfUserMessageCancelIrp);
gUserMessageIrp = Irp;
return STATUS_PENDING;
}
Status = SfCommonDeviceIoControl( Irp->AssociatedIrp.SystemBuffer,
IrpSp->Parameters.DeviceIoControl.InputBufferLength,
Irp->AssociatedIrp.SystemBuffer,
IrpSp->Parameters.DeviceIoControl.OutputBufferLength,
IrpSp->Parameters.DeviceIoControl.IoControlCode,
&Irp->IoStatus );
Irp->IoStatus.Status = Status;
IoCompleteRequest( Irp, IO_NO_INCREMENT );
return Status;
}
IoSkipCurrentIrpStackLocation(Irp);
return IoCallDriver(((PDEVICE_EXTENSION)DeviceObject->DeviceExtension)->AttachedToDeviceObject, Irp);
}
其中SfCommonDeviceIoControl函数是参照FileSpy写的其他的消息处理过程。
VOID
SfUserMessageCancelIrp(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
UNREFERENCED_PARAMETER(DeviceObject);
KdPrint(("FileEncrypt! Enter SfUserMessageCancelIrp....\n"));
IoReleaseCancelSpinLock(Irp->CancelIrql);
// If this is our queued read, then unqueue it
if ( Irp == gUserMessageIrp) {
gUserMessageIrp = NULL;
}
// Whatever Irp it is, just cancel it
Irp->IoStatus.Status = STATUS_CANCELLED;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp,IO_NO_INCREMENT);
}
3、处理FastIoDeviceControl
BOOLEAN
SfFastIoDeviceControl (
IN PFILE_OBJECT FileObject,
IN BOOLEAN Wait,
IN PVOID InputBuffer OPTIONAL,
IN ULONG InputBufferLength,
OUT PVOID OutputBuffer OPTIONAL,
IN ULONG OutputBufferLength,
IN ULONG IoControlCode,
OUT PIO_STATUS_BLOCK IoStatus,
IN PDEVICE_OBJECT DeviceObject
)
{
……
if (DeviceObject == gEncryptControlDeviceObject) {
if (IoControlCode == SFILTER_DRIVER_USERMESSAGE) {
return FALSE;
}
……
}
……
}
4、编写消息处理函数SfProcessUserMessage
NTSTATUS
SfProcessUserMessage(
IN PDEVICE_OBJECT DeviceObject,
IN USER_MSG_TYPES UserMsg
)
{
PIO_STACK_LOCATION IrpSp;
PIRP Irp;
PVOID IoBuffer;
ULONG OutputBufferLength;
UNREFERENCED_PARAMETER(DeviceObject);
KdPrint(("Sfilter: Enter SfProcessUserMessage....\n"));
__try {
Irp = gUserMessageIrp;
//这种情况是在设备启动后,应用程序并没有发送过该IRP,设备又要卸载时出现。
if(Irp == NULL)
return STATUS_SUCCESS;
IrpSp = IoGetCurrentIrpStackLocation (Irp);
// Get pointers and lengths of the caller's (user's) IO buffer
IoBuffer = Irp->AssociatedIrp.SystemBuffer;
OutputBufferLength = IrpSp->Parameters.DeviceIoControl.OutputBufferLength;
if (IoBuffer != NULL && sizeof(UserMsg) <= OutputBufferLength) {
RtlCopyMemory(IoBuffer, &UserMsg, sizeof(UserMsg));
}
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = sizeof(UserMsg);
IoSetCancelRoutine(Irp,NULL);// 取消Cancel例程。
IoCompleteRequest(Irp,IO_NO_INCREMENT);
gUserMessageIrp = NULL;
} __except (EXCEPTION_EXECUTE_HANDLER) {
KdPrint("Sfilter! SfProcessUserMessage exception happen, exception code (0x%x)\n", GetExceptionCode()));
}
return STATUS_SUCCESS;
}
5、现在在驱动任何一个地方调用SfProcessUserMessage即可
二、应用程序: 1、定义和驱动程序相同的消息类型:
#define SFILTER_DRIVER_USERMESSAGE \
(ULONG) CTL_CODE(FILE_DEVICE_DISK_FILE_SYSTEM, 0x999, METHOD_BUFFERED, FILE_ANY_ACCESS )
typedef enum _USER_MSG_TYPES {
USER_MSG_1 = 0x00000001L,
USER_MSG_2 = 0x00000002L,
USER_MSG_3 = 0x00000003L
} USER_MSG_TYPES;
2、编写消息循环函数MessageLoop
BOOL MessageLoop(HANDLE hDevice, HANDLE hEvent)
{
ULONG uMsg, byReturn;
OVERLAPPED ol;
ol.Offset = 0;
ol.OffsetHigh = 0;
ol.hEvent = hEvent;
if(!DeviceIoControl( hDevice, SFCONTROL_DRIVER_USERMESSAGE,
NULL, 0, &uMsg, sizeof(uMsg), &byReturn, &ol)) {
DWORD dwError = GetLastError();
if (dwError == ERROR_IO_PENDING) {
// 驱动程序没有消息传过来,循环等待
while (WaitForSingleObject(hEvent, 100) == WAIT_TIMEOUT) {
//应用程序退出标志
if(gbInstanceIsExit == TRUE) {
CancelIo(hDevice);
}
}
//驱动程序有消息传过来,得到数据。
GetOverlappedResult(hDevice, &ol, &byReturn, TRUE);
// Process message
switch (uMsg) {
case USER_MSG_1:
::MessageBox(NULL, TEXT("USER_MSG_1"), NULL, MB_OK);
break;
case USER_MSG_2:
::MessageBox(NULL, TEXT("USER_MSG_2"), NULL, MB_OK);
break;
case USER_MSG_3:
::MessageBox(NULL, TEXT("USER_MSG_3"), NULL, MB_OK);
break;
default:
break;
}
} else if (dwError != ERROR_OPERATION_ABORTED) { // Error 995
TCHAR bufMsg[80];
wsprintf(bufMsg, TEXT("MessageLoop call DeviceIoControl failed 0x%x(%d)"), dwError, dwError);
WriteToLog(bufMsg);
return FALSE;
}
return TRUE;
}
return FALSE;
}
注:当应用程序退出是设置gbInstanceIsExit为true,则 驱动程序中的SfUserMessageCancelIrp将调用。 函数WriteToLog只是简单做些的日志记录。
3、调用MessageLoop如下:
HANDLE hEventMsg = NULL;
HANDLE hDeviceSys = INVALID_HANDLE_VALUE;
hEventMsg = CreateEvent( NULL, TRUE, FALSE, NULL);
if( pInitParam->hEvent == NULL) {
DWORD dwError = GetLastError();
TCHAR bufMsg[80];
wsprintf(bufMsg, TEXT("call CreateEvent failed! 0x%x(%d)"), dwError, dwError);
WriteToLog(bufMsg);
return dwError;
}
hDeviceSys = CreateFile(SYSSERVICENAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
if (pInitParam->hDevice == INVALID_HANDLE_VALUE || pInitParam->hDevice == NULL) {
DWORD dwError = GetLastError();
TCHAR bufMsg[80];
wsprintf(bufMsg, TEXT("call CreateFile failed! 0x%x(%d)"), dwError, dwError);
WriteToLog(bufMsg);
return dwError;
}
while (MessageLoop(hDeviceSys, hEventMsg))
; // do nothing
CloseHandle(hDeviceSys);
CloseHandle(hEventMsg);
注:CreateFile函数必须添加FILE_FLAG_OVERLAPPED标志。
4、现在整个框架已经建立,只有处理MessageLoop的switch语句块相应的消息即可。
Markdown 语法
- 加粗**内容**
- 斜体*内容*
- 删除线~~内容~~
- 引用> 引用内容
- 代码`代码`
- 代码块```编程语言↵代码```
- 链接[链接标题](url)
- 无序列表- 内容
- 有序列表1. 内容
- 缩进内容
- 图片![alt](url)
-
2008-08-23 16:16:51
-
2015-03-19 17:26:19
-
2018-12-12 08:59:23
-
2013-03-11 15:15:55
-
2008-08-23 16:07:24
-
2020-10-09 16:52:48
-
2012-12-04 11:52:00
-
2008-09-28 14:21:28
-
2008-08-23 16:12:03
-
2008-11-02 17:14:58
-
2014-12-03 17:17:06
-
2018-11-15 22:00:41
-
02008-11-24 10:13:32
-
2017-01-22 17:21:17
-
2016-06-09 09:30:49
-
2016-06-15 00:27:43
-
2018-07-06 16:47:54
-
2012-12-05 11:25:41
-
2018-12-20 15:57:12
-
5Hi3516CV610 如何使用SD卡升级固件
-
5cat /dev/logmpp 报错 <3>[ vi] [func]:vi_send_frame_node [line]:99 [info]:vi pic queue is full!
-
50如何获取vpss chn的图像修改后发送至vo
-
5FPGA通过Bt1120传YUV422数据过来,vi接收不到数据——3516dv500
-
50SS928 运行PQtools 拼接 推到设备里有一半画面会异常
-
53536AV100的sample_vdec输出到CVBS显示
-
10海思板子mpp怎么在vi阶段改变视频数据尺寸
-
10HI3559AV100 多摄像头同步模式
-
9海思ss928单路摄像头vio中加入opencv处理并显示
-
10EB-RV1126-BC-191板子运行自己编码的程序
举报类型
- 内容涉黄/赌/毒
- 内容侵权/抄袭
- 政治相关
- 涉嫌广告
- 侮辱谩骂
- 其他
详细说明