看雪论坛
发新帖
7

[系统与硬件] [原创][成果3.5]驱动和应用层的三种通信方式

sislcb 2008-1-4 11:57 71885
驱动程序和客户应用程序经常需要进行数据交换,但我们知道驱动程序和客户应用程序可能不在同一个地址空间,因此操作系统必须解决两者之间的数据交换。
驱动层和应用层通信,主要是靠DeviceIoControl函数,下面是该函数的原型:
BOOL DeviceIoControl (
HANDLE hDevice, // 设备句柄
DWORD dwIoControlCode, // IOCTL请求操作代码
LPVOID lpInBuffer, // 输入缓冲区地址
DWORD nInBufferSize, // 输入缓冲区大小
LPVOID lpOutBuffer, // 输出缓冲区地址
DWORD nOutBufferSize, // 输出缓冲区大小
LPDWORD lpBytesReturned, // 存放返回字节数的指针
LPOVERLAPPED lpOverlapped // 用于同步操作的Overlapped结构体指针
);

dwIoControlCode
要进行操作的控制码。驱动程序可以通过CTL_CODE宏来组合定义一个控制码,并在IRP_MJ_DEVICE_CONTROL的实现中进行控制码的操作。在驱动层,irpStack->Parameters.DeviceIoControl.IoControlCode表示了这个控制码。

IOCTL请求有四种缓冲策略,下面一一介绍。
1、 输入输出缓冲I/O(METHOD_BUFFERED)
2、 直接输入缓冲输出I/O(METHOD_IN_DIRECT)
3、 缓冲输入直接输出I/O(METHOD_OUT_DIRECT)
4、 上面三种方法都不是(METHOD_NEITHER)

为了对这些类型更详细的描述,请看msdn上的解释,我抄录如下:

"缓冲"方法(METHOD_BUFFERED)
备注:在下面的讨论中,"输入"表示数据从用户模式的应用程序到驱动程序,"输出"表示数据从驱动程序到应用程序。

对于读取请求,I/O 管理器分配一个与用户模式的缓冲区大小相同的系统缓冲区。IRP 中的 SystemBuffer 字段包含系统地址。UserBuffer 字段包含初始的用户缓冲区地址。当完成请求时,I/O 管理器将驱动程序已经提供的数据从系统缓冲区复制到用户缓冲区。对于写入请求,会分配一个系统缓冲区并将 SystemBuffer 设置为地址。用户缓冲区的内容会被复制到系统缓冲区,但是不设置 UserBuffer。对于 IOCTL 请求,会分配一个容量大小足以包含输入缓冲区或输出缓冲区的系统缓冲区,并将 SystemBuffer 设置为分配的缓冲区地址。输入缓冲区中的数据复制到系统缓冲区。UserBuffer 字段设置为用户模式输出缓冲区地址。内核模式驱动程序应当只使用系统缓冲区,且不应使用 UserBuffer 中存储的地址。

对于 IOCTL,驱动程序应当从系统缓冲区获取输入并将输出写入到系统缓冲区。当完成请求时,I/O 系统将输出数据从系统缓冲区复制到用户缓冲区。

"直接"方法(METHOD_IN/OUT_DIRECT)
对于读取和写入请求,用户模式缓冲区会被锁定,并且会创建一个内存描述符列表 (MDL)。MDL 地址会存储在 IRP 的 MdlAddress 字段中。SystemBuffer 和 UserBuffer 均没有任何含义。但是,驱动程序不应当更改这些字段的值。

对于 IOCTL 请求,如果在 METHOD_IN_DIRECT 和 METHOD_OUT_DIRECT 中同时有一个输出缓冲区,则分配一个系统缓冲区(SystemBuffer 又有了地址)并将输入数据复制到其中。如果有一个输出缓冲区,且它被锁定,则会创建 MDL 并设置 MdlAddress。UserBuffer 字段没有任何含义。

"两者都不"方法(METHOD_NEITHER)
对于读取和写入请求,UserBuffer 字段被设置为指向初始的用户缓冲区。不执行任何其他操作。SystemAddress 和 MdlAddress 没有任何含义。对于 IOCTL 请求,I/O 管理器将 UserBuffer 设置为初始的用户输出缓冲区,而且,它将当前 I/O 栈位置的 Parameters.DeviceIoControl.Type3InputBuffer 设置为用户输入缓冲区。利用该 I/O 方法,由驱动程序来确定如何处理缓冲区:分配系统缓冲区或创建 MDL。

通常,驱动程序在访问用户数据时不应当将 UserBuffer 字段用作地址,即使当用户缓冲区被锁定时也是如此。这是由于在调用驱动程序时,在系统中可能看不到调用用户的地址空间。(对于该规则的一个例外是,在最高层驱动程序将 IRP 向下传递到较低层的驱动程序之前,它可能需要使用 UserBuffer 来复制数据。)如果使用"直接"或"两者都不"方法,在创建 MDL 之后,驱动程序可以使用 MmGetSystemAddressForMdl 函数来获取有效的系统地址以访问用户缓冲区。

在驱动层,依传输类型的不同,输入缓冲区的位置亦不同,见下表。
传输类型                                     位置
METHOD_IN_DIRECT                irp->AssociatedIrp.SystemBuffer
METHOD_OUT_DIRECT             irp->AssociatedIrp.SystemBuffer
METHOD_BUFFERED                 irp->AssociatedIrp.SystemBuffer
METHOD_NEITHER                   irpStack->Parameters.DeviceIoControl.Type3InputBuffer

在驱动层,依传输类型的不同,输出缓冲区的位置亦不同,见下表。
传输类型                                    位置
METHOD_IN_DIRECT                irp->MdlAddress
METHOD_OUT_DIRECT             irp->MdlAddress
METHOD_BUFFERED                 irp->AssociatedIrp.SystemBuffer
METHOD_NEITHER                    irp->UserBuffer

所以只要确定了传输方式后,就可以根据各自的位置来读取和写入数据,从而实现应用层和驱动的通信。
下面看驱动层对ioctl控制码的处理代码:

//METHOD_OUT_DIREC方式
NTSTATUS COMM_DirectOutIo(PIRP Irp, PIO_STACK_LOCATION pIoStackIrp, UINT *sizeofWrite)
{
    NTSTATUS status = STATUS_UNSUCCESSFUL;
    PVOID pInputBuffer, pOutputBuffer;
	ULONG  outputLength, inputLength;

    DbgPrint("COMM_DirectOutIo\r\n");

    outputLength = pIoStackIrp->Parameters.DeviceIoControl.OutputBufferLength;
    inputLength  = pIoStackIrp->Parameters.DeviceIoControl.InputBufferLength;
    pInputBuffer = Irp->AssociatedIrp.SystemBuffer;
    pOutputBuffer = NULL;

    if(Irp->MdlAddress)
        pOutputBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);

    if(pInputBuffer && pOutputBuffer)
    {                                                          
        DbgPrint("COMM_DirectOutIo UserModeMessage = '%s'", pInputBuffer);
        RtlCopyMemory(pOutputBuffer, pInputBuffer, outputLength);
		*sizeofWrite = outputLength;
        status = STATUS_SUCCESS;
    }
    return status;
}

// METHOD_IN_DIRECT
NTSTATUS COMM_DirectInIo(PIRP Irp, PIO_STACK_LOCATION pIoStackIrp, UINT *sizeofWrite)
{
    NTSTATUS status = STATUS_UNSUCCESSFUL;
    PVOID pInputBuffer, pOutputBuffer;
	ULONG  outputLength, inputLength;

    DbgPrint("COMM_DirectInIo\r\n");

    outputLength = pIoStackIrp->Parameters.DeviceIoControl.OutputBufferLength;
    inputLength  = pIoStackIrp->Parameters.DeviceIoControl.InputBufferLength;
    pInputBuffer = Irp->AssociatedIrp.SystemBuffer;
    pOutputBuffer = NULL;

    if(Irp->MdlAddress)
        pOutputBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);

    if(pInputBuffer && pOutputBuffer)
    {                                                          
        DbgPrint("COMM_DirectInIo UserModeMessage = '%s'", pInputBuffer);
        RtlCopyMemory(pOutputBuffer, pInputBuffer, outputLength);
        *sizeofWrite = outputLength;
        status = STATUS_SUCCESS;
    }
    return status;
}

// METHOD_BUFFERED
NTSTATUS COMM_BufferedIo(PIRP Irp, PIO_STACK_LOCATION pIoStackIrp, UINT *sizeofWrite)
{
    NTSTATUS status = STATUS_UNSUCCESSFUL;
    PVOID pInputBuffer, pOutputBuffer;
	ULONG  outputLength, inputLength;

    DbgPrint("COMM_BufferedIo\r\n");

	outputLength = pIoStackIrp->Parameters.DeviceIoControl.OutputBufferLength;
    inputLength  = pIoStackIrp->Parameters.DeviceIoControl.InputBufferLength;
    pInputBuffer = Irp->AssociatedIrp.SystemBuffer;
    pOutputBuffer = Irp->AssociatedIrp.SystemBuffer;

    if(pInputBuffer && pOutputBuffer)
    {              
		DbgPrint("COMM_BufferedIo UserModeMessage = '%s'", pInputBuffer);
        RtlCopyMemory(pOutputBuffer, pInputBuffer, outputLength);
		*sizeofWrite = outputLength;
        status = STATUS_SUCCESS;
    }
    return status;
}

// METHOD_NEITHER
NTSTATUS COMM_NeitherIo(PIRP Irp, PIO_STACK_LOCATION pIoStackIrp, UINT *sizeofWrite)
{
    NTSTATUS status = STATUS_UNSUCCESSFUL;
    PVOID pInputBuffer, pOutputBuffer;
	ULONG  outputLength, inputLength;

    DbgPrint("COMM_NeitherIo\r\n");

	outputLength  = pIoStackIrp->Parameters.DeviceIoControl.OutputBufferLength;
    inputLength   = pIoStackIrp->Parameters.DeviceIoControl.InputBufferLength;
    pInputBuffer  = pIoStackIrp->Parameters.DeviceIoControl.Type3InputBuffer;
    pOutputBuffer = Irp->UserBuffer;

    if(pInputBuffer && pOutputBuffer)
    {              
		DbgPrint("COMM_NeitherIo UserModeMessage = '%s'", pInputBuffer);
        RtlCopyMemory(pOutputBuffer, pInputBuffer, outputLength);
		*sizeofWrite = outputLength;
        status = STATUS_SUCCESS;
    }
    return status;
}

代码比较简单,都是取得输入的数据,然后把数据直接拷贝到输出,传输给应用层。

应用层的代码:
procedure TfrmMain.Send_Recv_Data(AInData: String; var AOutData:String;
  IoctlCode: DWORD);
var
  dwReturn: DWORD;
  inData:array[0..1023] of char;
  outData:array[0..1023] of char;
begin
  StrPCopy(inData, AInData);
  if m_hCommDevice <> 0 then
  begin
    DeviceIoControl(m_hCommDevice, IoctlCode, @inData,	Length(inData), @outData, Length(outData), dwReturn, nil);
    AOutData := StrPas(@outData);
  end;
end;


上面是进行发送和接受的过程。
需要通信,只要如下做:
procedure TfrmMain. btnDirect_IN_IOClick (Sender: TObject);
var
  outData:String;
begin
  Send_Recv_Data(Trim(edtDirect_in_in.Text), outData, IOCTL_COMM_DIRECT_IN_IO);
  edtDirect_in_out.Text := outData;
end;


这是 direct_in方式通信,其他通信方式类似,大家可以参考代码了,这里就不列举了,由于代码比较简单,我就不多说了,大家还是看代码吧,很好明白。最后,给个测试图:

应用层:

驱动层:


代码:
驱动和应用层通信.rar
上传的附件:
本主题帖已收到 0 次赞赏,累计¥0.00
最新回复 (66)
7
sislcb 2008-1-4 11:58
2
因为METHOD_IN_DIRECT  和 METHOD_OUT_DIRECT  是类似的,所以可以说是三种。
26
forgot 2008-1-4 12:29
3
感谢楼主分享
26
combojiang 2008-1-4 13:54
4
好,写得有水平。
Kendiv 2008-1-5 23:57
5
不错,呵呵。
linhanshi 2008-1-6 00:13
6
Thanks.
1
悠扬笛曲 2008-3-19 15:45
7
谁能解释一下,为何划分为这几种情况呢?统一处理不好吗?
1
悠扬笛曲 2008-3-19 16:02
8
另外分别应用在哪些场合呀?
hswqs 2008-3-19 20:46
9
谢谢楼主,很好的知识分享
nuke 2008-4-10 09:01
10
感谢分享,学习啦~
4
lly鹅 2008-4-10 13:33
11
好手笔..........
往返来学习..........
interfox 2008-4-14 13:08
12
感谢搂主,正在学习驱动,很有帮助
2
hfyy 2008-4-14 17:27
13
不知有没有汇编版本的驱动入门啊
moshior 2008-4-28 15:33
14
好东西,知识啊!
jjgogo 2008-5-24 18:39
15
恩,学习一下
多谢
jmzz 2008-7-21 16:02
16
不知METHOD_OUT_DIRECT一次最大可以传输多少数据?
halfsoul 2008-7-23 20:02
17
这种总结归纳型文章对很多人都有帮助~~~非常感谢
lixupeng 2008-7-23 20:44
18
学习下
30
玩命 2008-7-24 00:14
19
  ... 学习
zhucheba 2008-7-24 18:14
20
感谢楼主分享
菜中菜 2008-7-27 14:31
21
菜鸟受教了,还在学驱动,看了罗的文章,还在朦胧中,这节看懂了,真的写的好,清楚明白,没想到应用程序和驱动传递点数据都那么复杂麻烦.
chinatme 2008-7-28 16:36
22
r3与r0之间数据的传递终于找到答案了.感谢LZ
shaoshunda 2008-7-29 14:06
23
感谢中!!!!
sding 2008-7-30 20:18
24
不错,正说要学习这个
liyongwu 2008-8-22 10:06
25
驱动层的显示是怎么用什么显示的啊?????
是调试工具显示的吗?
是的话用的是什么调试工具啊?我想找这样的!
boainfall 2008-12-23 12:48
26
http://blog.chinaunix.net/u/25096/showart_456024.html怎么是
========================================================
lpOutBuffer

由用户层指定,用于接收驱动层返回数据的缓冲区。在驱动层,依传输类型的不同,输出缓冲区的位置亦不同,见下表。

传输类型         位置
METHOD_IN_DIRECT         irp->MdlAddress
METHOD_OUT_DIRECT         irp->MdlAddress
//下面两个是反的×××××××××××××××××
METHOD_BUFFERED         irp->UserBuffer   
METHOD_NEITHER         irp->AssociatedIrp.SystemBuffer
brucewata 2008-12-27 10:04
27
非常感谢楼主分享,最近正在搞相关的研究
天abc 2010-2-6 00:46
28
感谢楼主分享
6
loudy 2010-2-6 10:51
29
感谢楼主。。。。
laobai 2010-2-6 19:29
30
恩,我刚刚开始学习啊,还看不太懂啊
tmxfh 2010-2-15 13:13
31
感谢搂主,正在学习驱动,很有帮助
ysyyx 2010-2-17 15:34
32
加紧学习中  因为看不懂
runzihao 2010-3-25 16:05
33
分享了!谢谢!
zfyang 2010-3-28 00:51
34
嗯。楼主看来是做了实验了,写出点心得。不错。
不过,建议你既然Driver部分是C语言写的,应用程序部分也用C语言好了。
你这样应用程序用Pascal(?),感觉那个驱动部分不是你写的。

另外,三种ioctl的方式,楼主你有没有试过它们的效率??我平时只用METHOD_BUFFERED方式,所以你有什么效率的结论,请分享给大家。多谢。
zfyang 2010-3-29 19:10
35
自己做了些实验,获得的经验大概如下

如果是传递一两个控制参数,那么用 (METHOD_NEITHER) 效率最高,也最方便
如果是传递大量的数据,比如网络抓包后将数据包全部传递给Application,那么肯定要用 (METHOD_BUFFERED)方法了。
ddzhouqian 2010-3-29 21:44
36
不错,感谢楼主
ToChengh 2010-4-23 16:37
37
谢谢楼主,讲的很好
yvi 2010-7-19 21:24
38
对我很有帮助,多谢楼主了。如果应用层能用C写更好,我电脑里没有DE
1
filly 2010-9-27 22:35
39
mark 即马克
天勇者 2010-10-27 15:37
40
感谢楼主分享,ring0和ring3层的通信我找了好久才找到
flamer 2010-10-27 16:01
41
学习了。嘎嘎
bksaro 2010-10-30 21:09
42
且不应使用 UserBuffer 中存储的地址。

这个为什么不行?网上就会瞎扯.说userbuffer是可以ring3程序指定的..指定个锤子啊.我找了N多代码.结果IRP里userbuffer都不是我指定的值.LZ能说明么?
swzices 2011-6-12 17:03
43
不错,呵呵
其实大多数情况下用的都是buffer IO的方式,关于三种方式在数据传输量和限制其实在DDK帮助文档里面有些说明了,虽然不是很详细....   也可以参考DDK安装目录下的general\ioctl
一颗CPU 2011-6-23 14:12
44
跪谢楼主分享~!
leeone 2011-8-13 18:56
45
METHOD_BUFFERED模式,输入和输出缓冲区都是irp->AssociatedIrp.SystemBuffer
pInputBuffer = Irp->AssociatedIrp.SystemBuffer;
pOutputBuffer = Irp->AssociatedIrp.SystemBuffer;

if(pInputBuffer && pOutputBuffer)
{              
DbgPrint("COMM_BufferedIo UserModeMessage = '%s'", pInputBuffer);
RtlCopyMemory(pOutputBuffer, pInputBuffer, outputLength);
这种用法,感觉很怪。
shuizhilan 2011-9-8 10:25
46
对啊,为什么呢?

用户传入的缓冲区应该是两个吧,这里怎么是同一个呢?期待高手能解答下!
1
sungy 2012-4-21 10:52
47
WriteFile,ReadFile如何指定传输方式?或者他们默认了那种传输方式?
fsjaky 2012-11-6 18:08
48
谢谢分享学习了
8
Winker 2012-11-6 18:32
49
驱动跟ring3通信,有N多方法,,,,hook大法,内核管道,DeviceIoControl等等。。
1
Tensm 2012-11-16 21:41
50
回来学习学习~~~
返回



©2000-2017 看雪学院 | Based on Xiuno BBS | 域名 加速乐 保护 | SSL证书 又拍云 提供 | 微信公众号:ikanxue
Time: 0.017, SQL: 10 / 京ICP备10040895号-17