--全双工交互
文:柯小三(
[email protected])
注:转载请保持原文完整性
这一篇来得有点迟,上两个星期都没有再学习相关的东西,因为工作上的原因接触了苹果公司的Mac OS X系统,一个全新的系统,对我这一个*NIX基础相当薄弱的家伙来说,有点难底,我用了将近一个星期我才知道关机的扭在哪儿!几天之后,我也慢慢习惯了它,知道有哪些快捷键什么的,就跟我学Windows的驱动一样,知道了如何构造一个DriverEntry,然后又学习怎么加上几个基本的读写例程……,什么都需要一个过程,不是吗?回来了,先放过太高深的底层理论,用到了再另外学习也不迟;在我的前两篇介绍里面,先是构造了驱动框架,然后会为自己的驱动加上简单的读和写的例程,可是,我们也看到到,这个驱动只能通过ReadFile和WriteFile来操作,要么写要么读。在这一篇将会看到,我们再给它加上一点双工的东西读写同时进行,这就是我们在用户层程序中常常会用到的DeviceIoControl函数。好了,不废话了,入正题吧。
1。简介。
DeviceIoControl常被用于用户态应用与驱动之前的基本交互,传送控制命令字等。举例来说,我在这个驱动里面实现一个系统函数钩子,可能有几种选择,驱动加载后就启动Hook,要想停止Hook只需要卸载驱动即可,但是,如果操作很频繁或者是用户的账户类型没有管理员权限是不是就不方便?另外,就算使用我们现在会的读写例程来做来的话,也有不方便的地方:如果是要Hook多个函数或是需要带回执行后的状态信息呢?都是问题,那么,驱动里面的IRP_MJ_DEVICE_CONTROL就要派上用场了。它用户启态的DeviceIoControl调用而触发,它的原型是
BOOL DeviceIoControl( HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped );
看参数就可以知道,从第二个开始依次是控制码,输入地址,输入数据长度,输出地址,输出数据缓冲区长底,实际返回数据长度。最后一个暂且放那儿不说,它是用于异步的,咱先不急,只支持同步。我实在是不想用到缓冲区这个词,因为太容易混淆,不过这里所指的缓冲区都是用户态内存区域(这么说可以吧)。别的都好说,关键是这个命令字,正是因为它有个可变的范围从而使得咱们的驱动可以完成很多不同的功能。大部分文章都在这个命令字上,这是一个32位整数值,还记得在学习Read和Write例程的时候提及的IO方式吗?同样,使用DeviceIoControl不仅仅能够给驱动程序(暂且这么说吧)传递合适的命令字(不需要输入参数和相应的返回数据)还可以传入指定的参数,如果你愿意,你还可以带回相应的返回数据,这不是挺好吗?这样,便和Read和Write例程一样,就会有相应的IO方式之说。那么,DeviceIoControl的控制码就告诉IO管理器在给应用程序与驱动之间相应的数据传输使用相应的IO方式,如果你还记得的话,Read和Write各有3种IO方式,如果这么一算的话,DeviceIoControl是不是有就有了23=8种可能的组合?事实上不是,NT提供了4种组合,一般情况下,它只专注于其中的一种传输方式(个人观点),下面,我简单介绍一下这4种组合中数据是如何传递的:
① METHOD_DIRECT_IN:字面上看,它指示IO管理器将参数通过Mdl直接传入驱动,事实是不是这样呢?这个先放在一边儿别动它,我们先看其它的三种IO方式。
② METHOD_DIRECT_OUT:很明显,它指示IO管理器将返回数据放在一个系统Mdl中返回给用户,在这种情况下,输入参数是通过一个系统缓存传入,这个缓存地址在IRP->AssociatedIrp->SystemBuffer域,这个时候我们的应该使用IRP->MdlAddress来返回输出数据。
③ METHOD_BUFFERED:这是和个缓冲模式,也即IO管理器会为我们的输入输出在内核中创建一段内存(如果有输入或输出的话)供输入和输出共享使用,从面,这段内存的大小就是输入和输入数据长度取大者,当IRP_MJ_DEVICE_CONTROL例程被调用时,IRP->AssociatedIrp->SystemBuffer中存放的是用户层程序传递出来的输入数据,当例程返回给用户的时候,它里面又保存返回数据。从而,备份输入参数是程序员的责任。
④ METHOD_NEITHER:两者都不IO,这种IO方式应该是直接操作用户层程序提供的虚拟内存,所以,在使用这种IO方式的时候,尽可能使用ProbeForRead和ProbeForWrite结合SHE避免访问无效地址而蓝屏死机。
现在,可能是再提一下METHOD_DIRECT_IN这种IO方式了,我已经试过,如果根据METHOD_DIRECT_OUT来联想,以Mdl输入,以系统缓存输出的话,会导致异常产生,后来,发现,它和MDTHOD_DIRECT_OUT这种IO方式使用相同的IO方式!区别只是,当使用METHOD_DIRECT_IN的时候传入的Mdl对驱动只有READ访问权限,而对于MDHOD_DIRECT_OUT具有一个WRITE权限,是不是很容易理解呢?那么,到底为什么要这样呢,这样是因为驱动借助Mdl传出数据使可以返回更大的数据块给用户层程序(至于为什么,我也说不清,但是为什么这两种DirectIo方式差不多的原因了)。那,现在实际上只有三种IO方式了!在32位IO控制码字中,IO方式占低两位。
这个32位控制码的布局由宏CTL_CODE生成,这里就不再啰嗦了。一般情况下,在IRP_MJ_DEVICE_CONTROL的实现里面,只需要判断这个控制码字的值使用一个switch—case将控制权转移到合适的子例程中就行了。
但是有一点,需要说明的是,在这个例程返回之前一定要调用IoCompleteIrp来通知IO管理器,这个IRP已经处理结束了,别再继续了。因为在我的代码里并没有处理过IRP结构中的当前CurrentLocation域(大概是这个域,我没试过,可能是也可能不是),每当IRP被处理一次,此值被递减一次,系统可能并不直接操作这个域,而是通过完成IO来间接操纵的
所以,如果我们不完成,这个值将一直保持大于的状态,就返回不了了,从而,用户态程序得不到结束驱动也将无法卸载,只能重启了。
2.代码
这个例子还是跟前两个例子一样简单,在第二个例子中添加DeviceIoControl所需的命令码字:
#define EXAMPLE3_IOCTL_DIRECTIN_EXAMPLE CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_IN_DIRECT,FILE_READ_DATA|FILE_WRITE_DATA)
#define EXAMPLE3_IOCTL_DIRECTOUT_EXAMPLE CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_OUT_DIRECT,FILE_READ_DATA|FILE_WRITE_DATA)
#define EXAMPLE3_IOCTL_BUFFERRED_EXAMPLE CTL_CODE(FILE_DEVICE_UNKNOWN,0x802,METHOD_BUFFERED,FILE_READ_DATA|FILE_WRITE_DATA)
#define EXAMPLE3_IOCTL_NEITHERIO_EXAMPLE CTL_CODE(FILE_DEVICE_UNKNOWN,0x803,METHOD_NEITHER,FILE_READ_DATA|FILE_WRITE_DATA)
然后,再加上DeviceIoControl例程及所需的子例程即可。主要代码我就不在罗列了,不了解的只需要看一眼代码即可。
3. 总结:
使用DeviceIoControl可以让咱们的驱动(或者说是设备)与用户层程序有更大的通信交互空间,据我所知,操作系统也没有提供太多的接口给应用程序与硬件交互,那么,如果你的应用程序需要与硬件设备打交道,可能这是得经常用的了(尽管你也可以只用ReadFile和Write例程来操作,也应该是可行的)。
跟我的上一篇日志比起来,日子相差可能已经超过一月了。这一阵子,在弄别的,没有再来学习驱动,这期间收到不少跟我一样的初学者的留言,从而让我又找到再回头来学习驱动的动力。谢谢朋友们的支持,以后大家一起多多交流,共同进步。可能我接下来要学习的就是使用一些设备附加数据相关的东西,大家一起学习哦。同时,我也建议和我一样的初学者们,初学的时候,别钻得太深,差不多就行了,然后,在以后的应用中慢慢再学习可能会简单一些,比竟操作系统底层有太多不是我们一看就能明白的东西。
如有不妥之处,欢迎讨论,QQ:178041876
特别声明:未经作者许可,请勿转载,转载请注明出处
2009年4月16日