驱动程序通信的函数,除了ReadFile和WriteFile函数还有DeviceIoControl函数,而且DeviceIoControl函数那是相当的彪悍。因为它可以自定义控制码,你只要在IRP_MJ_DEVICE_CONTROL对应的派遣函数中读取控制码,然后针对控制码,你就可以实现自定义的功能了。

函数原型:

BOOL WINAPI DeviceIoControl(

__in         HANDLEhDevice,

__in         DWORDdwIoControlCode,

__in_opt     LPVOID lpInBuffer,

__in         DWORDnInBufferSize,

__out_opt    LPVOID lpOutBuffer,

__in         DWORDnOutBufferSize,

__out_opt    LPDWORD lpBytesReturned,

__inout_opt  LPOVERLAPPED lpOverlapped

);

其中lpBytesReturned的值来自于IRP结构中的pIRP->IoStatus.Information。DeviceIoControl的第二个参数就是控制码,控制码是一个32为无符号整型,需要符合DDK的规定。

控制代码中各数据位字段的含义如下: 
◎ DeviceType--设备类型(31-16bit)指 出了设备的类型,微软保留了0-7FFFh的取值,剩下的8000h-0FFFFh   供开发商定义新的内核模式驱动程序。我们可以在\include\w2k\ntddk.inc文件中找到一组FILE_DEVICE_XXX  符号常量,这些值都是微软保留的 值,我们可以使用其中的FILE_DEVICE_UNKNOWN。当然你也可以定义另外一个FILE_DEVICE_XXX值

◎ Access--存取代码(15-14bit)指明应用程序存取设备的方式,由于这个字段只有2位,所以只有4种可能性:

· FILE_ANY_ACCESS (0)--最大的存取权限,就是什么操作都可以

· FILE_READ_ACCESS (1)--读权限,设备将数据传递到指定的缓冲区

· FILE_WRITE_ACCESS (2)--写权限,可以从内存中向设备传递数据

· FILE_READ_ACCESS or FILE_WRITE_ACCESS (3)--读写权限,设备和内存缓冲区之间可以互相传递数据

◎ Function--功能代码(13-2bit)用来描述要进行的操作,我们可以用800h-0FFFh来定义自己的I/O控制代码,

0-7FFh之间的值是被微软保留的,用来定义公用的I/O控制代码

◎ Method--缓冲模式(0-1bit)表示I/O管理器如何对输入和输出的数据进行缓冲,这个字段的长度是2位,所以有4种可能性:

·METHOD_BUFFERED (0)--对I/O进行缓冲

·METHOD_IN_DIRECT (1)--对输入不进行缓冲

·METHOD_OUT_DIRECT (2)--对输出不进行缓冲

·METHOD_NEITHER (3)--都不缓冲

缓冲区模式虽然会损失点性能,但是其安全性好。

下面将分别讲述这几种模式。

缓冲内存模式(对应代码中的IOCTL_TEST1)

首先要将控制码中的Method设置为METHOD_BUFFERED。

往驱动中Input数据:在Win32 APIDeviceIoControl函数的内部,用户提供的输入缓冲区的内容被复制到IRP的pIRP->AssociatedIrp.SystemBuffer的内存地址,复制的字节是有DeviceControl指定的输入字节数。从驱动中Output数据:派遣函数可以向pIRP->AssociatedIrp.SystemBuffer写入数据,被当做是设备输出的数据。操作系统会将AssociatedIrp.SystemBuffer的数据再次复制到DeviceIoControl提供的输出缓冲区,复制的字节数有pIrp->IoStatus.Information指定,DeviceIoControl也可以通过参数lpBytesReturned得到复制的字节数。

原理就是这样了,理论上就可以实现读和写的双向操作了。

直接内存模式(对应代码中的IOCTL_TEST2)

首先将Method设置为METHOD_IN_DIRECT 或METHOD_OUT_DIRECT ,这两者的不同只是体现在打开设备的权限上,当以只读权限打开设备时,METHOD_IN_DIRECT 就可以顺利操作,而METHOD_OUT_DIRECT 就会失败。如果以读写权限打开时,两者都可以执行成功。

往驱动中Input数据:这部分和上面的缓冲内存模式一样,输入缓冲区的数据复制到pIrp->AssociateIrp.SystemBuffer内存地址,复制的字节数是按照DeviceIoControl指定的。

从驱动中Output数据:操作系统会为DeviceIoControl指定的输出缓冲区锁定,然后在内核模式地址下重新映射到一段地址。在派遣函数中可以先获取DeviceIoControl指定的输出缓冲区(lpOutBufferb被记录在pIrp->AssociateIrp.SystemBuffer),然后再通过MmGetSystemAddressForMdlSafe获取其在核地址中的映射值。

其他内存模式(对应代码中的IOCTL_TEST3)

个人觉得这种方式挺麻烦的而且少被用到,由于它是直接访问用户模式地址,要求调用DeviceIoControl的线程和派遣函数运行在同一个线程设备上下文中,自己有个印象就行了。

首先将指定的Method参数设置为METHOD_NEITHER。

往驱动中Input数据:通过I/O堆栈的Parameters.DeviceIoControl.Type3InputBuffer得到DeviceIoControl提供的输入缓冲区地址,Parameters.DeviceIoControl.InputBufferLength得到其长度。由于不能保证传递过来的地址合法,所以需要先要结果ProbeRead函数进行判断。

从驱动中Output数据:通过pIrp->UserBuffer得到DeviceIoControl函数提供的输出缓冲区地址,再通过Parameters.DeviceIoControl.OutputBufferLength得到输出缓冲区大小。同样的要用ProbeWrite函数先进行判断。

下面给出一个实例代码,来自于张帆的《Windows驱动开发详解》

首先是控制码设置:

#define IOCTL_TEST1 CTL_CODE(\
FILE_DEVICE_UNKNOWN, \

0x800, \

METHOD_BUFFERED, \

FILE_ANY_ACCESS)

#define IOCTL_TEST2 CTL_CODE(\
FILE_DEVICE_UNKNOWN, \

0x801, \

METHOD_IN_DIRECT, \

FILE_ANY_ACCESS)

#define IOCTL_TEST3 CTL_CODE(\
FILE_DEVICE_UNKNOWN, \

0x802, \

METHOD_NEITHER, \

FILE_ANY_ACCESS)

再是IRP_MJ_DEVICE_CONTROL派遣函数:

NTSTATUSIOCTRLDRIVER_DispatchDeviceControl(

IN PDEVICE_OBJECT DeviceObject,

IN PIRP pIrp

)

{

NTSTATUS status = STATUS_SUCCESS;

PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);

//得到输入缓冲区大小

ULONG cbin = stack->Parameters.DeviceIoControl.InputBufferLength;

//得到输出缓冲区大小

ULONG cbout = stack->Parameters.DeviceIoControl.OutputBufferLength;

//得到IOCTL码

ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;

ULONG info = 0;

switch(code)

{

case IOCTL_TEST1:

{

KdPrint(("zhui:IOCTL_TEST1\n"));

UCHAR* InputBuffer =(UCHAR*)pIrp->AssociatedIrp.SystemBuffer;

for (ULONGi=0;i<cbin;i++)

{

KdPrint(("zhui:%X\n",InputBuffer[i]));

}

//操作输出缓冲区

UCHAR* OutputBuffer =(UCHAR*)pIrp->AssociatedIrp.SystemBuffer;

memset(OutputBuffer,0xAA,cbout);

//设置实际操作输出缓冲区长度

info = cbout;

break;

}

case IOCTL_TEST2:

{

KdPrint(("zhui:IOCTL_TEST2\n"));

UCHAR* InputBuffer =(UCHAR*)pIrp->AssociatedIrp.SystemBuffer;

for (ULONGi=0;i<cbin;i++)

{

KdPrint(("zhui:%X\n",InputBuffer[i]));

}

//pIrp->MdlAddress为DeviceIoControl输出缓冲区地址相同

KdPrint(("zhui:UserAddress:0X%08X\n",MmGetMdlVirtualAddress(pIrp->MdlAddress)));

UCHAR* OutputBuffer =(UCHAR*)MmGetSystemAddressForMdlSafe(pIrp->MdlAddress,NormalPagePriority);

//InputBuffer被映射到内核模式下的内存地址,必定在0X80000000-0XFFFFFFFF之间

memset(OutputBuffer,0xAA,cbout);

//设置实际操作输出缓冲区长度

info = cbout;

break;

}

case IOCTL_TEST3:

{

KdPrint(("zhui:IOCTL_TEST3\n"));

UCHAR* UserInputBuffer= (UCHAR*)stack->Parameters.DeviceIoControl.Type3InputBuffer;

KdPrint(("zhui:UserInputBuffer:0X%0X\n",UserInputBuffer));

//得到用户模式地址

PVOID UserOutputBuffer= pIrp->UserBuffer;

KdPrint(("zhui:UserOutputBuffer:0X%0X\n",UserOutputBuffer));

__try

{

KdPrint(("zhui:Enter __try block\n"));

//判断指针是否可读

ProbeForRead(UserInputBuffer,cbin,4);

//显示输入缓冲区内容

for (ULONGi=0;i<cbin;i++)

{

KdPrint(("zhui:%X\n",UserInputBuffer[i]));

}

//判断指针是否可写

ProbeForWrite(UserOutputBuffer,cbout,4);

//操作输出缓冲区

memset(UserOutputBuffer,0xAA,cbout);

//由于在上面引发异常,所以以后语句不会被执行!

info = cbout;

KdPrint(("zhui:Leave __try block\n"));

}

__except(EXCEPTION_EXECUTE_HANDLER)

{

KdPrint(("zhui:Catch the exception\n"));

KdPrint(("zhui:The program will keep going\n"));

status =STATUS_UNSUCCESSFUL;

}

info = cbout;

break;

}

default:

status =STATUS_INVALID_DEVICE_REQUEST;

break;

}

pIrp->IoStatus.Status = status;

pIrp->IoStatus.Information = info;

IoCompleteRequest(pIrp, IO_NO_INCREMENT);

return status;

}

测试的main函数:

int main()

{

HANDLE hDevice =

CreateFile("\\\\.\\HelloDDK",

GENERIC_READ | GENERIC_WRITE,

0, // share mode none

NULL, // no security

OPEN_EXISTING,

FILE_ATTRIBUTE_NORMAL,

NULL); // no template

if (hDevice == INVALID_HANDLE_VALUE)

{

printf("Failed to obtainfile handle to device: "

"%s with Win32error code: %d\n",

"MyWDMDevice", GetLastError() );

return 1;

}

UCHAR InputBuffer[10];

UCHAR OutputBuffer[10];

//将输入缓冲区全部置成0XBB

memset(InputBuffer,0xBB,10);

DWORD dwOutput;

//输入缓冲区作为输入,输出缓冲区作为输出

BOOL bRet;

bRet = DeviceIoControl(hDevice, IOCTL_TEST1, InputBuffer, 10,&OutputBuffer, 10, &dwOutput, NULL);

if (bRet)

{

printf("Output buffer:%dbytes\n",dwOutput);

for (inti=0;i<(int)dwOutput;i++)

{

printf("%02X",OutputBuffer[i]);

}

printf("\n");

}

bRet = DeviceIoControl(hDevice, IOCTL_TEST2, InputBuffer, 10,&OutputBuffer, 10, &dwOutput, NULL);

if (bRet)

{

printf("Output buffer:%dbytes\n",dwOutput);

for (inti=0;i<(int)dwOutput;i++)

{

printf("%02X",OutputBuffer[i]);

}

printf("\n");

}

bRet = DeviceIoControl(hDevice, IOCTL_TEST3, InputBuffer, 10,&OutputBuffer, 10, &dwOutput, NULL);

if (bRet)

{

printf("Output buffer:%dbytes\n",dwOutput);

for (int i=0;i<(int)dwOutput;i++)

{

printf("%02X",OutputBuffer[i]);

}

printf("\n");

}

CloseHandle(hDevice);

return 0;

}

DeviceIoControl 驱动交互的更多相关文章

  1. Linux系统调用怎么和内核或底层驱动交互的

    学习Linux系统下驱动程序开发已有大半年时间,心中一直有个疑惑:那就是诸如open.write.read等系统调用是怎么和内核或底层驱动建立起联系的呢?今天将自己的一些粗略的理解总结如下.      ...

  2. DeviceIoControl 应用层如何和驱动层通信?

    调用的方法之一的DeviceIoControl 驱动层提供设备名 例如filedisk 在驱动层 首先先是注册列表 用winObj查看 filedisk的驱动对象 但是 这八个对象时怎么生成的呢? 我 ...

  3. 用户空间与内核驱动的交互过程 — ioctl

    在Linux内核模块的开发过程中,经常涉及到运行在用户空间上的应用程序与内核模块进行交互,ioctl系统调用是常用的一种方式.本文并不涉及vlan的具体原理,仅通过vconfig与vlan内核模块进行 ...

  4. (转)Windows驱动编程基础教程

    版权声明     本书是免费电子书. 作者保留一切权利.但在保证本书完整性(包括版权声明.前言.正文内容.后记.以及作者的信息),并不增删.改变其中任何文字内容的前提下,欢迎任何读者 以任何形式(包括 ...

  5. [windows驱动]内核态驱动架构

    1.windows驱动简介: 1.1 windows组件简介: 1.2 windows驱动类型: windows驱动分为两种基本类型: 用户态驱动在用户态下执行.它们一般提供一套win32应用程序和内 ...

  6. linux RTC 驱动模型分析【转】

    转自:http://blog.csdn.net/yaozhenguo2006/article/details/6824970 RTC(real time clock)实时时钟,主要作用是给Linux系 ...

  7. linux内核空间与用户空间信息交互方法

    linux内核空间与用户空间信息交互方法     本文作者: 康华:计算机硕士,主要从事Linux操作系统内核.Linux技术标准.计算机安全.软件测试等领域的研究与开发工作,现就职于信息产业部软件与 ...

  8. Linux 块设备驱动 (一)

    1.块设备的I/O操作特点 字符设备与块设备的区别: 块设备只能以块为单位接受输入和返回输出,而字符设备则以字符为单位. 块设备对于I/O请求有对应的缓冲区,因此它们可以选择以什么顺序进行响应,字符设 ...

  9. Android : 跟我学Binder --- (4) 驱动情景分析

    目录: Android : 跟我学Binder --- (1) 什么是Binder IPC?为何要使用Binder机制? Android : 跟我学Binder --- (2) AIDL分析及手动实现 ...

随机推荐

  1. http 请求头部解析

    作者:知乎用户链接:https://www.zhihu.com/question/42696895/answer/109035792来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请 ...

  2. for&while循环

    流程控制: 1. if 2. while 3. for if判断 什么是if判断 判断一个条件成立则做...不成了则做... 为何要有if判断 让计算机像人一样具有判断的能力 什么是循环 循环指的是一 ...

  3. vue优势

    Vue.js是一个轻巧.高性能.可组件化的MVVM库,同时拥有非常容易上手的API: 我们都知道单页面应用:页面切换快 ,首屏时间稍慢,SEO差        js 渲染 (多页面应用:  首屏时间快 ...

  4. Day12作业及默写

    1.整理今天的博客,写课上代码,整理流程图. 2.用列表推导式做下列小题 li=['alex','wusir','abds','meet','ab'] a. 过滤掉长度小于3的字符串列表,并将剩下的转 ...

  5. excel单元格内容连接

    1.连接符号: & 举例子:C1= A1&B1 2.生成sql: CONCATENATE("(seq_table.nextval,sysdate, 'test',sysdat ...

  6. PAT乙级 1016. 部分A+B (15)

    题目传送:https://www.patest.cn/contests/pat-b-practise/1016 正整数A的“DA(为1位整数)部分”定义为由A中所有DA组成的新整数PA.例如:给定A ...

  7. paddle实践

    Docker image阅读:https://github.com/PaddlePaddle/book/blob/develop/README.cn.md docker run -d -p 8888: ...

  8. input标签(按钮)

    按钮: <input type="button" name="..." value="..." /> <input typ ...

  9. SQLI DUMB SERIES-5

    less5 (1)输入单引号,回显错误,说明存在注入点.输入的Id被一对单引号所包围,可以闭合单引号 (2)输入正常时:?id=1 说明没有显示位,因此不能使用联合查询了:可以使用报错注入,有两种方式 ...

  10. js 自定义滚动条

    http://visugar.com/2017/08/18/20170818CustomScroll/    chrome浏览器 https://www.cnblogs.com/yclblog/p/6 ...