Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2676512
  • 博文数量: 877
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 5921
  • 用 户 组: 普通用户
  • 注册时间: 2013-12-05 12:25
个人简介

技术的乐趣在于分享,欢迎多多交流,多多沟通。

文章分类

全部博文(877)

文章存档

2021年(2)

2016年(20)

2015年(471)

2014年(358)

2013年(26)

分类: Windows平台

2015-05-15 18:54:35

Windows驱动开发WDM (7)- 异步IRP
http://blog.csdn.net/zj510/article/details/8226907

同步IRP是很简单的,比如caller调用DeviceIoControll,那么DeviceIoControll的IRP会发到相应的驱动,驱动把这个IRP完成,然后caller的DeviceIoControll才返回。同步的缺点很明显,比如驱动需要花10秒处理这个IRP,那么caller就得等待10秒钟,有时候这是个浪费。这是个很常见的问题,解决方案就是采用异步的方式。

 

异步IRP

caller和驱动通信的方式主要就是3个API, ReadFile,WriteFile和DeviceIoControl。这3个函数都支持异步方式(OVERLAPPED)。当采用异步方式的时候,这3个函数会立刻返回,同时得到错误码997(io pending).这个时候caller就可以去做其他事情了,通过WaitForSingleObject等待驱动的处理返回。

如果要使用异步方式,CreateFile打开设备的时候,就需要设置一个标志FILE_FLAG_OVERLAPPED,比如:

  1. HANDLE hDevice = CreateFile(DEVICE_NAME,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,NULL);  

 

驱动实现异步方式处理

将驱动代码稍作改变,当驱动接收到caller编码请求的时候,启动一个线程,然后在IRP处理函数里面调用IoMarkIrpPending,并且返回STATUS_PENDING,这样等于IRP_MJ_DEVICE_CONTROL处理函数立刻返回。

  1. NTSTATUS HelloWDMIOControl(IN PDEVICE_OBJECT fdo, IN PIRP Irp)  
  2. {  
  3.     KdPrint(("Enter HelloWDMIOControl\n"));  
  4.   
  5.     PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;  
  6.     PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);  
  7.   
  8.   
  9.     //得到IOCTRL码  
  10.     ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;  
  11.   
  12.     NTSTATUS status;  
  13.     ULONG info = 0;  
  14.     switch (code)  
  15.     {  
  16.     case IOCTL_ENCODE:  
  17.         {  
  18.             //启动一个工作线程(用户线程,表示属于发起DeviceIoControl的那个进程)  
  19.             HANDLE hThread;  
  20.             PsCreateSystemThread(&hThread, 0, NULL, NtCurrentProcess(), NULL, EncodingThread, Irp);  
  21.   
  22.             //设置IRP为挂起状态,在工作线程里面会完成这个Irp  
  23.             status = STATUS_PENDING;  
  24.             Irp->IoStatus.Status = status;  
  25.             Irp->IoStatus.Information = info;  
  26.             IoMarkIrpPending(Irp);  
  27.         }  
  28.         break;  
  29.     default:  
  30.         status = STATUS_INVALID_VARIANT;  
  31.         Irp->IoStatus.Status = status;  
  32.         Irp->IoStatus.Information = info;  
  33.         IoCompleteRequest(Irp, IO_NO_INCREMENT);  
  34.         break;  
  35.     }  
  36.   
  37.     KdPrint(("Leave HelloWDMIOControl\n"));  
  38.     return status;  
  39. }  

从上面的代码可以看到当驱动接到IOCTL_ENCODE的请求时,开启一个线程,然后调用IoMarkIrpPending函数,并且返回STATUS_PENDING的错误码,也就是说IRP_MJ_DEVICE_CONTROL的派遣函数立刻返回了(没有做任何caller要求的事情),caller要求的事情将在新创建的线程里面完成。

内核模式下,通过函数PsCreateSystemThread可以创建线程。内核模式下有两种线程:用户线程和系统线程。用户线程意思是说这个线程属于caller的相应进程,系统线程属于系统进程(一个特殊的进程,可以用任务管理器看到,通常进程ID是4)。这个例子里面使用了用户线程,通过PsCreateSystemThread的第四个参数决定,这里使用了NtCurrentProcess得到当前caller进程。

看一下工作线程函数EncodingThread:

  1. void EncodingThread(IN void* pContext)  
  2. {  
  3.     //模拟延时3秒  
  4.     KdPrint(("Start to wait for encoding, 3s\n"));  
  5.     KEVENT event;  
  6.     KeInitializeEvent(&event, NotificationEvent, FALSE);  
  7.     LARGE_INTEGER timeout;  
  8.     timeout.QuadPart = -3 * 1000 * 1000 * 10;//负数表示从现在开始计数,KeWaitForSingleObject的timeout是100ns为单位的。  
  9.     KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, &timeout);//等待3秒  
  10.   
  11.     KdPrint(("Start to Encode\n"));  
  12.     PIRP Irp = (PIRP)pContext;  
  13.     PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);  
  14.   
  15.     //得到输入缓冲区大小  
  16.     ULONG cbin = stack->Parameters.DeviceIoControl.InputBufferLength;  
  17.   
  18.     //得到输出缓冲区大小  
  19.     ULONG cbout = stack->Parameters.DeviceIoControl.OutputBufferLength;  
  20.   
  21.     //获取输入缓冲区,IRP_MJ_DEVICE_CONTROL的输入都是通过buffered io的方式  
  22.     char* inBuf = (char*)Irp->AssociatedIrp.SystemBuffer;  
  23.     for (ULONG i = 0; i < cbin; i++)//将输入缓冲区里面的每个字节和m亦或  
  24.     {  
  25.         inBuf[i] = inBuf[i] ^ 'm';  
  26.     }  
  27.   
  28.     //获取输出缓冲区,这里使用了直接方式,见CTL_CODE的定义,使用了METHOD_IN_DIRECT。所以需要通过直接方式获取out buffer  
  29.     KdPrint(("user address: %x, this address should be same to user mode addess.\n", MmGetMdlVirtualAddress(Irp->MdlAddress)));  
  30.     //获取内核模式下的地址,这个地址一定> 0x7FFFFFFF,这个地址和上面的用户模式地址对应同一块物理内存  
  31.     char* outBuf = (char*)MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);  
  32.   
  33.     ASSERT(cbout >= cbin);  
  34.     RtlCopyMemory(outBuf, inBuf, cbin);  
  35.   
  36.     //完成irp  
  37.     Irp->IoStatus.Status = STATUS_SUCCESS;  
  38.     Irp->IoStatus.Information = cbin;  
  39.     IoCompleteRequest(Irp, IO_NO_INCREMENT);  
  40.   
  41.     KdPrint(("Encode thread finished\n"));  
  42. }  

使用KeWaitForSingleObject等待3秒,然后处理相应的IRP,包括编码和IRP完成。也就是说当caller发起一个DeviceIoControl的时候,驱动会使用IoMarkIrpPending函数设置当前Irp为pending状态,同时开启一个线程,在线程里面等待3秒,然后处理请求,并且完成这个irp。
 

 caller测试例子

  1. // TestWDMDriver.cpp : Defines the entry point for the console application.  
  2. //  
  3.   
  4. #include "stdafx.h"  
  5. #include   
  6.   
  7. #define DEVICE_NAME L"\\\\.\\HelloWDM"  
  8.   
  9. int _tmain(int argc, _TCHAR* argv[])  
  10. {  
  11.     //设置overlapped标志,表示异步打开  
  12.     HANDLE hDevice = CreateFile(DEVICE_NAME,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,NULL);  
  13.   
  14.     if (hDevice != INVALID_HANDLE_VALUE)  
  15.     {  
  16.         char* inbuf = "hello world";  
  17.         char outbuf[12] = {0};  
  18.         DWORD dwBytes = 0;  
  19.   
  20.         OVERLAPPED ol = {0};  
  21.         ol.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);  
  22.         BOOL b = DeviceIoControl(hDevice, CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_IN_DIRECT, FILE_ANY_ACCESS),   
  23.                             inbuf, 11, outbuf, 11, &dwBytes, &ol);  
  24.   
  25.         printf("DeviceIoControl returned with Overlapped mode. ret value: %d, last error: %d, operated bytes: %d\n", b, GetLastError(), dwBytes);  
  26.   
  27.         DWORD dwStart = GetTickCount();  
  28.         WaitForSingleObject(ol.hEvent, INFINITE);//等待驱动处理完编码要求  
  29.   
  30.         //将输出buffer的数据和'm'亦或,看看是否能够得到初始的字符串。  
  31.         for (int i = 0; i < 11; i++)  
  32.         {  
  33.             outbuf[i] = outbuf[i] ^ 'm';  
  34.         }  
  35.   
  36.         printf("Verify encode result, outbuf: %s, used: %d ms\n", outbuf, GetTickCount() - dwStart);  
  37.   
  38.         CloseHandle(hDevice);  
  39.           
  40.     }  
  41.     else  
  42.         printf("CreateFile failed, err: %x\n", GetLastError());  
  43.   
  44.     return 0;  
  45. }  

首先使用CreateFile异步打开这个设备,然后传递一个OVERLAPPED结构给驱动,使用WaitForSingleObject等待驱动完成编码请求,最后打印log来验证异步操作是否成功。ok,看一下结果。


如图中的注释,异步操作成功了。caller的DeviceIoControl立刻返回了并且得到997的错误码。然后WaitForSingleObject花费了3秒钟才返回,返回的buffer数据里面装了驱动编码后的数据,通过将这些数据再次和'm'亦或得到的原始传入字符串。一切正常。和同步方式相比,异步方式下DeviceIoControl会立刻返回,然后有需要的话,caller可以做其他事情,用WaitForSingleObject(或者WaitForMultipleObjects)查看驱动是否完成了处理。

总体来讲,异步方式可以提升系统的性能,但是代码就要复杂一些。

 

完整代码下载:

DDK编译驱动,VS2008编译调用例子。

阅读(845) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~