设为首页 收藏本站
查看: 1033|回复: 0

[经验分享] Windows驱动开发WDM (7)- 异步IRP?

[复制链接]

尚未签到

发表于 2015-12-16 08:37:04 | 显示全部楼层 |阅读模式
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,比如:



[cpp] view plaincopy

  • 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处理函数立刻返回。



[cpp] view plaincopy

  • NTSTATUS HelloWDMIOControl(IN PDEVICE_OBJECT fdo, IN PIRP Irp)  
  • {  
  •     KdPrint(("Enter HelloWDMIOControl\n"));  
  •   
  •     PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;  
  •     PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);  
  •   
  •   
  •     //得到IOCTRL码  
  •     ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;  
  •   
  •     NTSTATUS status;  
  •     ULONG info = 0;  
  •     switch (code)  
  •     {  
  •     case IOCTL_ENCODE:  
  •         {  
  •             //启动一个工作线程(用户线程,表示属于发起DeviceIoControl的那个进程)  
  •             HANDLE hThread;  
  •             PsCreateSystemThread(&hThread, 0, NULL, NtCurrentProcess(), NULL, EncodingThread, Irp);  
  •   
  •             //设置IRP为挂起状态,在工作线程里面会完成这个Irp  
  •             status = STATUS_PENDING;  
  •             Irp->IoStatus.Status = status;  
  •             Irp->IoStatus.Information = info;  
  •             IoMarkIrpPending(Irp);  
  •         }  
  •         break;  
  •     default:  
  •         status = STATUS_INVALID_VARIANT;  
  •         Irp->IoStatus.Status = status;  
  •         Irp->IoStatus.Information = info;  
  •         IoCompleteRequest(Irp, IO_NO_INCREMENT);  
  •         break;  
  •     }  
  •   
  •     KdPrint(("Leave HelloWDMIOControl\n"));  
  •     return status;  
  • }  
从上面的代码可以看到当驱动接到IOCTL_ENCODE的请求时,开启一个线程,然后调用IoMarkIrpPending函数,并且返回STATUS_PENDING的错误码,也就是说IRP_MJ_DEVICE_CONTROL的派遣函数立刻返回了(没有做任何caller要求的事情),caller要求的事情将在新创建的线程里面完成。
内核模式下,通过函数PsCreateSystemThread可以创建线程。内核模式下有两种线程:用户线程和系统线程。用户线程意思是说这个线程属于caller的相应进程,系统线程属于系统进程(一个特殊的进程,可以用任务管理器看到,通常进程ID是4)。这个例子里面使用了用户线程,通过PsCreateSystemThread的第四个参数决定,这里使用了NtCurrentProcess得到当前caller进程。
看一下工作线程函数EncodingThread:



[cpp] view plaincopy

  • void EncodingThread(IN void* pContext)  
  • {  
  •     //模拟延时3秒  
  •     KdPrint(("Start to wait for encoding, 3s\n"));  
  •     KEVENT event;  
  •     KeInitializeEvent(&event, NotificationEvent, FALSE);  
  •     LARGE_INTEGER timeout;  
  •     timeout.QuadPart = -3 * 1000 * 1000 * 10;//负数表示从现在开始计数,KeWaitForSingleObject的timeout是100ns为单位的。  
  •     KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, &timeout);//等待3秒  
  •   
  •     KdPrint(("Start to Encode\n"));  
  •     PIRP Irp = (PIRP)pContext;  
  •     PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);  
  •   
  •     //得到输入缓冲区大小  
  •     ULONG cbin = stack->Parameters.DeviceIoControl.InputBufferLength;  
  •   
  •     //得到输出缓冲区大小  
  •     ULONG cbout = stack->Parameters.DeviceIoControl.OutputBufferLength;  
  •   
  •     //获取输入缓冲区,IRP_MJ_DEVICE_CONTROL的输入都是通过buffered io的方式  
  •     char* inBuf = (char*)Irp->AssociatedIrp.SystemBuffer;  
  •     for (ULONG i = 0; i < cbin; i++)//将输入缓冲区里面的每个字节和m亦或  
  •     {  
  •         inBuf = inBuf ^ 'm';  
  •     }  
  •   
  •     //获取输出缓冲区,这里使用了直接方式,见CTL_CODE的定义,使用了METHOD_IN_DIRECT。所以需要通过直接方式获取out buffer  
  •     KdPrint(("user address: %x, this address should be same to user mode addess.\n", MmGetMdlVirtualAddress(Irp->MdlAddress)));  
  •     //获取内核模式下的地址,这个地址一定> 0x7FFFFFFF,这个地址和上面的用户模式地址对应同一块物理内存  
  •     char* outBuf = (char*)MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);  
  •   
  •     ASSERT(cbout >= cbin);  
  •     RtlCopyMemory(outBuf, inBuf, cbin);  
  •   
  •     //完成irp  
  •     Irp->IoStatus.Status = STATUS_SUCCESS;  
  •     Irp->IoStatus.Information = cbin;  
  •     IoCompleteRequest(Irp, IO_NO_INCREMENT);  
  •   
  •     KdPrint(("Encode thread finished\n"));  
  • }  
使用KeWaitForSingleObject等待3秒,然后处理相应的IRP,包括编码和IRP完成。也就是说当caller发起一个DeviceIoControl的时候,驱动会使用IoMarkIrpPending函数设置当前Irp为pending状态,同时开启一个线程,在线程里面等待3秒,然后处理请求,并且完成这个irp。

caller测试例子



[cpp] view plaincopy

  • // TestWDMDriver.cpp : Defines the entry point for the console application.  
  • //  
  •   
  • #include "stdafx.h"  
  • #include   
  •   
  • #define DEVICE_NAME L"\\\\.\\HelloWDM"  
  •   
  • int _tmain(int argc, _TCHAR* argv[])  
  • {  
  •     //设置overlapped标志,表示异步打开  
  •     HANDLE hDevice = CreateFile(DEVICE_NAME,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,NULL);  
  •   
  •     if (hDevice != INVALID_HANDLE_VALUE)  
  •     {  
  •         char* inbuf = "hello world";  
  •         char outbuf[12] = {0};  
  •         DWORD dwBytes = 0;  
  •   
  •         OVERLAPPED ol = {0};  
  •         ol.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);  
  •         BOOL b = DeviceIoControl(hDevice, CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_IN_DIRECT, FILE_ANY_ACCESS),   
  •                             inbuf, 11, outbuf, 11, &dwBytes, &ol);  
  •   
  •         printf("DeviceIoControl returned with Overlapped mode. ret value: %d, last error: %d, operated bytes: %d\n", b, GetLastError(), dwBytes);  
  •   
  •         DWORD dwStart = GetTickCount();  
  •         WaitForSingleObject(ol.hEvent, INFINITE);//等待驱动处理完编码要求  
  •   
  •         //将输出buffer的数据和'm'亦或,看看是否能够得到初始的字符串。  
  •         for (int i = 0; i < 11; i++)  
  •         {  
  •             outbuf = outbuf ^ 'm';  
  •         }  
  •   
  •         printf("Verify encode result, outbuf: %s, used: %d ms\n", outbuf, GetTickCount() - dwStart);  
  •   
  •         CloseHandle(hDevice);  
  •          
  •     }  
  •     else  
  •         printf("CreateFile failed, err: %x\n", GetLastError());  
  •   
  •     return 0;  
  • }  
首先使用CreateFile异步打开这个设备,然后传递一个OVERLAPPED结构给驱动,使用WaitForSingleObject等待驱动完成编码请求,最后打印log来验证异步操作是否成功。ok,看一下结果。
DSC0000.png
如图中的注释,异步操作成功了。caller的DeviceIoControl立刻返回了并且得到997的错误码。然后WaitForSingleObject花费了3秒钟才返回,返回的buffer数据里面装了驱动编码后的数据,通过将这些数据再次和'm'亦或得到的原始传入字符串。一切正常。和同步方式相比,异步方式下DeviceIoControl会立刻返回,然后有需要的话,caller可以做其他事情,用WaitForSingleObject(或者WaitForMultipleObjects)查看驱动是否完成了处理。

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

完整代码下载:http://download.csdn.net/detail/zj510/4813345
DDK编译驱动,VS2008编译调用例子。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-151755-1-1.html 上篇帖子: Windows驱动开发技术详解__IRP的同步 下篇帖子: windows 7 RDP 增强
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表