阅读:4808回复:6
内核驱动程序和用户应用程序的消息通讯机制
先帖一篇文章:
怎样在驱动层和应用层建立准消息机制 TigerZD 驱动程序与应用程序运行与不同的环境又紧密合作,但是应用程序通知驱动程序易(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语句块相应的消息即可。 |
|
沙发#
发布于:2007-06-14 09:41
顶!
|
|
板凳#
发布于:2007-08-24 17:39
![]() |
|
地板#
发布于:2008-10-11 21:21
这种方式不错,当年虎财的解决方法
|
|
|
地下室#
发布于:2008-10-12 03:40
Generally this is a good approach.
Two issues: 1. Synchronization should be considered to support concurrency. 2. This single pending IRP may be system's bottleneck. |
|
5楼#
发布于:2008-10-12 22:30
上面的代码只是一咱技术演示,不管是当年虎财,还是楼主基本都没有考虑并发.不过也许他限定了只能单个打开设备,作了同步。对于可能并发的情况,设备扩展或全局的一个变量是无法正确维持多个irp的
|
|
|
6楼#
发布于:2008-10-13 09:08
我觉得还是使用事件机制好。
只有实在没有办法了才使用这种机制。 不要在AP中创建事件,在AP中创建事件,当AP非正常退出时,事件无效,会导致蓝屏。 在驱动的DriverEntry中创建事件, 在驱动的DriverUnload中销毁事件, 把事件名使用IOCTL丢给AP, AP再通过事件名字使用NtOpenEvent打开事件, 这样就可以保证内核中的事件始终是有效的。 |
|
|