之前写过一篇关于通过DeviceIoControl函数来使应用程序与驱动程序通信的博客,这次再通过这个完整的代码来简要疏通总结一下。

  这种通信方式,就是驱动程序和应用程序自定义一种IO控制码,然后调用DeviceIoControl函数,IO管理器会产生一个MajorFunction 为IRP_MJ_DEVICE_CONTROL(DeviceIoControl函数会产生此IRP),MinorFunction 为自己定义的控制码的IRP,系统就调用相应的处理IRP_MJ_DEVICE_CONTROL的派遣函数,你在派遣函数中判断MinorFunction ,是自定义的控制码你就进行相应的处理。

  一.先谈一下这个定义IO控制码 ,其实可以看作是一种通信协议。

看看CTL_CODE原型:

  #define CTL_CODE( DeviceType, Function, Method, Access ) ( \
  ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \
  )

   可以看到,这个宏四个参数,自然是一个32位分成了4部分,高16位存储设备类型,14~15位访问权限,2~13位操作功能,最后0,1两位就是确定缓冲区是如何与I/O和文件系统数据缓冲区进行数据传递方式,最常见的就是METHOD_BUFFERED。

自定义CTL_CODE:

  #define IOCTL_Device_Function CTL_CODE(DeviceType, Function, Method, Access)

  IOCTL_Device_Function:生成的IRP的MinorFunction

  DeviceType:设备对象的类型。设备类型可参考:http://blog.csdn.net/liyun123gx/article/details/38058965

  Function :自定义的IO控制码。自己定义时取0x800到0xFFF,因为0x0到0x7FF是微软保留的。

  Method :数据的操作模式。

METHOD_BUFFERED:缓冲区模式

METHOD_IN_DIRECT:直接写模式

METHOD_OUT_DIRECT:直接读模式

METHOD_NEITHER :Neither模式

Access:访问权限,可取值有:

FILE_ANY_ACCESS:表明用户拥有所有的权限

FILE_READ_DATA:表明权限为只读

FILE_WRITE_DATA:表明权限为可写

也可以 FILE_WRITE_DATA | FILE_READ_DATA:表明权限为可读可写,但还没达到FILE_ANY_ACCESS的权限。

  

  

继续介绍这个缓冲区数据传递方式Method:

  Method表示Ring3/Ring0的通信中的内存访问方式,有四种方式:
  #define METHOD_BUFFERED                0  
  #define METHOD_IN_DIRECT               1  
  #define METHOD_OUT_DIRECT              2  
  #define METHOD_NEITHER                  3

(1)如果使用METHOD_BUFFERED,表示系统将用户的输入输出都经过pIrp->AssociatedIrp.SystemBuffer来缓冲,因此这种方式的通信比较安全。

  METHOD_BUFFERED方式相当于对Ring3的输入输出都进行了缓冲。

METHOD_BUFFERED方式(借图):

  (2)如果使用METHOD_IN_DIRECT或METHOD_OUT_DIRECT方式,表示系统会将输入缓冲在pIrp->AssociatedIrp.SystemBuffer中,并将输出缓冲区锁定,然后在内核模式下重新映射一段地址,这样也是比较安全的。

  METHOD_IN_DIRECT和METHOD_OUT_DIRECT可称为"直接方式",是指系统依然对Ring3的输入缓冲区进行缓冲,但是对Ring3的输出缓冲区并没有缓冲,而是在内核中进行了锁定。这样Ring3输出缓冲区在驱动程序完成I/O请求之前,都是无法访问的,从一定程度上保障了安全性。如图21.1.14所示。
这两种方式,对于Ring3的输入缓冲区和METHOD_BUFFERED方式是一致的。对于Ring3的输出缓冲区,首先由系统锁定,并使用pIrp->MdlAddress来描述这段内存,驱动程序需要使用MmGetSystemAddressForMdlSafe函数将这段内存映射到内核内存地址(OutputBuffer),然后可以直接写入OutputBuffer地址,最终在驱动派遣例程返回后,由系统解除这段内存的锁定。
 
  METHOD_IN_DIRECT和METHOD_OUT_DIRECT方式的内存访问
  8METHOD_IN_DIRECT和METHOD_OUT_DIRECT方式的区别,仅在于打开设备的权限上,当以只读权限打开设备时,METHOD_IN_DIRECT方式的IoControl将会成功,而METHOD_OUT_DIRECT方式将会失败。如果以读写权限打开设备,两种方式都会成功。

  METHOD_IN_DIRECT和METHOD_OUT_DIRECT方式(借图):

  (3)如果使用METHOD_NEITHER方式,"其他方式",虽然通信的效率提高了,但是不够安全。驱动的派遣函数中输入缓冲区可以通过I/O堆栈(IO_STACK_LOCATION)的stack->Parameters.DeviceIo Control.Type3InputBuffer得到。输出缓冲区可以通过pIrp->UserBuffer得到。由于驱动中的派遣函数不能保证传递进来的用户输入和输出地址,因此最好不要直接去读写这些地址的缓冲区。应该在读写前使用ProbeForRead和ProbeForWrite函数探测地址是否可读和可写。

  METHOD_ NEITHER方式是不进行缓冲的,在驱动中可以直接使用Ring3的输入输出内存地址,

  驱动程序可以通过pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer得到Ring3的输入缓冲区地址(其中pIrpStack是IoGetCurrentIrpStackLocation(pIrp)的返回);通过pIrp-> UserBuffer得到Ring3的输出缓冲区地址。
  由于METHOD_NEITHER方式并不安全,因此最好对Type3InputBuffer读取之前使用ProbeForRead函数进行探测,对UserBuffer写入之前使用ProbeForWrite函数进行探测,当没有发生异常时,再进行读取和写入操作。

  METHOD_NEITHER方式(借图):

  

  二 .定义驱动设备名,符号链接名
       定义好了IO控制码CTL_CODE,第二步驱动程序还要准备驱动设备名和符号链接名。

    关于在Ring0层中要设置驱动设备名的同时还要设置符号链接名的原因,是因为只有符号链接名才可以被用户模式下的应用程序识别。

    windows下的设备是以"\Device\[设备名]”形式命名的。例如磁盘分区的c盘,d盘的设备名称就是"\Device\HarddiskVolume1”,"\Device\HarddiskVolume2”, 当然也可以不指定设备名称。                               如果IoCreateDevice中没有指定设备名称,那么I/O管理器会自动分配一个数字作为设备的名称。例如"\Device\00000001"。\Device\[设备名],不容易记忆,通常符号链接可以理解为设备的别名,更重要的是设备名,只能被内核模式下的其他驱动所识别,而别名可以被用户模式下的应用程序识别,例如c盘,就是名为"c:"的符号链接,其真正的设备对象是"\Device\HarddiskVolume1”,所以在写驱动时候,一般我们创建符号链接,即使驱动中没有用到,这也算是一个好的习惯吧。

    驱动中符号链接名是这样写的
    L"\\??\\HelloDDK" --->\??\HelloDDK

    或者
    L"\\DosDevices\\HelloDDK"--->\DosDevices\HelloDDK

    在应用程序中,符号链接名:
    L"\\\\.\\HelloDDK"-->\\.\HelloDDK

    DosDevices的符号链接名就是??, 所以"\\DosDevices\\XXXX"其实就是\\??\\XXXX

#define DEVICE_OBJECT_NAME  L"\\Device\\BufferedIODeviceObjectName"
//设备与设备之间通信
#define DEVICE_LINK_NAME L"\\DosDevices\\BufferedIODevcieLinkName"
//设备与Ring3之间通信

  三.将符号链接名与设备对象名称关联 ,等待IO控制码

    驱动程序要做的最后一步,先用IoCreateDevice函数创建设备对象,再用IoCreateSymbolicLink将符号链接名与设备对象名称关联 ,大功告成,等待IO控制码。

    

        //创建设备对象名称
RtlInitUnicodeString(&DeviceObjectName,DEVICE_OBJECT_NAME);
//创建设备对象
Status = IoCreateDevice(DriverObject,NULL,
&DeviceObjectName,
FILE_DEVICE_UNKNOWN,
0, FALSE,
&DeviceObject);
if (!NT_SUCCESS(Status))
{
return Status;
} //创建设备连接名称
RtlInitUnicodeString(&DeviceLinkName, DEVICE_LINK_NAME);
//将设备连接名称与设备名称关联
Status = IoCreateSymbolicLink(&DeviceLinkName,&DeviceObjectName); if (!NT_SUCCESS(Status))
{
IoDeleteDevice(DeviceObject);
return Status;
}

  

  四.应用程序获取设备句柄,发送IO控制码。

    驱动程序铺垫打理好之后,应用程序就可以由符号链接名通过CreateFile函数获取到设备句柄DeviceHandle,再用本场的主角,DeviceIoControl通过这个DeviceHandle发送控制码了。

    先看看这两个函数:

   

BOOL WINAPI DeviceIoControl(
_In_ HANDLE hDevice, //CreateFile函数打开的设备句柄
_In_ DWORD dwIoControlCode,//自定义的控制码
_In_opt_ LPVOID lpInBuffer, //输入缓冲区
_In_ DWORD nInBufferSize, //输入缓冲区的大小
_Out_opt_ LPVOID lpOutBuffer, //输出缓冲区
_In_ DWORD nOutBufferSize, //输出缓冲区的大小
_Out_opt_ LPDWORD lpBytesReturned, //实际返回的字节数,对应驱动程序中pIrp->IoStatus.Information。
_Inout_opt_ LPOVERLAPPED lpOverlapped //重叠操作结构指针。同步设为NULL,DeviceIoControl将进行阻塞调用;否则,应在编程时按异步操作设计
); HANDLE CreateFile(
LPCTSTR lpFileName, //打开的文件名
DWORD dwDesiredAccess, //访问权限
DWORD dwShareMode, //共享模式
LPSECURITY_ATTRIBUTES lpSecurityAttributes, //安全属性
DWORD dwCreationDisposition, //文件存在与不存在时的文件创建模式
DWORD dwFlagsAndAttributes, //文件属性设定(隐藏、只读、压缩、指定为系统文件等)
HANDLE hTemplateFile //文件副本句柄
);

  

  最后总结一下DeviceIoControl的通信流程:

    1.驱动程序和应用程序自定义好IO控制码 (CTL_CODE宏 四个参数,32位,4部分,存储设备类型,访问权限,操作功能,缓冲区数据传递方式(四种))

    2.驱动程序定义驱动设备名,符号链接名, 将符号链接名与设备对象名称关联 ,等待IO控制码(IoCreateDevice,IoCreateSymbolicLink)

    3.应用程序由符号链接名通过CreateFile函数获取到设备句柄DeviceHandle,再用本场的主角,DeviceIoControl通过这个设备句柄发送控制码给派遣函数。

  源代码:

  BufferedIO.h

  

#pragma once
#include <ntifs.h> #define CTL_SYS \
CTL_CODE(FILE_DEVICE_UNKNOWN,0x830,METHOD_BUFFERED,FILE_ANY_ACCESS) #define DEVICE_OBJECT_NAME L"\\Device\\BufferedIODeviceObjectName"
//设备与设备之间通信
#define DEVICE_LINK_NAME L"\\DosDevices\\BufferedIODevcieLinkName"
//设备与Ring3之间通信
VOID DriverUnload(PDRIVER_OBJECT DriverObject);
NTSTATUS PassThroughDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp);
NTSTATUS ControlThroughDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp);

  

BufferedIO.c

#include "BufferedIO.h"

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegisterPath)
{
NTSTATUS Status = STATUS_SUCCESS;
PDEVICE_OBJECT DeviceObject = NULL;
UNICODE_STRING DeviceObjectName;
UNICODE_STRING DeviceLinkName;
ULONG i;
// 栈
// 堆
// 全局(global Static Const)
DriverObject->DriverUnload = DriverUnload; //创建设备对象名称
RtlInitUnicodeString(&DeviceObjectName,DEVICE_OBJECT_NAME); //创建设备对象
Status = IoCreateDevice(DriverObject,NULL,
&DeviceObjectName,
FILE_DEVICE_UNKNOWN,
0, FALSE,
&DeviceObject);
if (!NT_SUCCESS(Status))
{
return Status;
}
//创建设备连接名称
RtlInitUnicodeString(&DeviceLinkName, DEVICE_LINK_NAME); //将设备连接名称与设备名称关联
Status = IoCreateSymbolicLink(&DeviceLinkName,&DeviceObjectName); if (!NT_SUCCESS(Status))
{
IoDeleteDevice(DeviceObject);
return Status;
}
//设计符合我们代码的派遣历程
for (i=0;i<IRP_MJ_MAXIMUM_FUNCTION;i++)
{
DriverObject->MajorFunction[i] = PassThroughDispatch; //函数指针
}
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = ControlThroughDispatch;
return Status;
}
//派遣历程
NTSTATUS PassThroughDispatch(PDEVICE_OBJECT DeviceObject,PIRP Irp)
{
Irp->IoStatus.Status = STATUS_SUCCESS; //LastError()
Irp->IoStatus.Information = 0; //ReturnLength
IoCompleteRequest(Irp, IO_NO_INCREMENT); //将Irp返回给Io管理器
return STATUS_SUCCESS;
}
NTSTATUS ControlThroughDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
NTSTATUS Status;
ULONG_PTR Informaiton = 0;
PVOID InputData = NULL;
ULONG InputDataLength = 0;
PVOID OutputData = NULL;
ULONG OutputDataLength = 0;
ULONG IoControlCode = 0;
PIO_STACK_LOCATION IoStackLocation = IoGetCurrentIrpStackLocation(Irp); //Irp堆栈
IoControlCode = IoStackLocation->Parameters.DeviceIoControl.IoControlCode;
InputData = Irp->AssociatedIrp.SystemBuffer;
OutputData = Irp->AssociatedIrp.SystemBuffer;
InputDataLength = IoStackLocation->Parameters.DeviceIoControl.InputBufferLength;
OutputDataLength = IoStackLocation->Parameters.DeviceIoControl.OutputBufferLength;
switch (IoControlCode)
{
case CTL_SYS:
{
if (InputData != NULL&&InputDataLength > 0)
{
DbgPrint("%s\r\n", InputData);
}
if (OutputData != NULL&&OutputDataLength >= strlen("Ring0->Ring3") + 1)
{
memcpy(OutputData, "Ring0->Ring3", strlen("Ring0->Ring3") + 1);
Status = STATUS_SUCCESS;
Informaiton = strlen("Ring0->Ring3") + 1;
}
else
{
Status = STATUS_INSUFFICIENT_RESOURCES; //内存不够
Informaiton = 0;
}
break;
}
default:
break;
}
Irp->IoStatus.Status = Status; //Ring3 GetLastError();
Irp->IoStatus.Information = Informaiton;
IoCompleteRequest(Irp, IO_NO_INCREMENT); //将Irp返回给Io管理器
return Status; //Ring3 DeviceIoControl()返回值
}
VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
UNICODE_STRING DeviceLinkName;
PDEVICE_OBJECT v1 = NULL;
PDEVICE_OBJECT DeleteDeviceObject = NULL; RtlInitUnicodeString(&DeviceLinkName, DEVICE_LINK_NAME);
IoDeleteSymbolicLink(&DeviceLinkName); DeleteDeviceObject = DriverObject->DeviceObject;
while (DeleteDeviceObject != NULL)
{
v1 = DeleteDeviceObject->NextDevice;
IoDeleteDevice(DeleteDeviceObject);
DeleteDeviceObject = v1;
}
}

  

IO.cpp

// 缓冲区IO.cpp : 定义控制台应用程序的入口点。
// #include "stdafx.h"
#include <windows.h>
#define DEVICE_LINK_NAME L"\\\\.\\BufferedIODevcieLinkName" #define CTL_SYS \
CTL_CODE(FILE_DEVICE_UNKNOWN,0x830,METHOD_BUFFERED,FILE_ANY_ACCESS)
int main()
{
HANDLE DeviceHandle = CreateFile(DEVICE_LINK_NAME,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (DeviceHandle==INVALID_HANDLE_VALUE)
{
return 0;
}
char BufferData = NULL;
DWORD ReturnLength = 0;
BOOL IsOk = DeviceIoControl(DeviceHandle, CTL_SYS,
"Ring3->Ring0",
strlen("Ring3->Ring0")+1,
(LPVOID)BufferData,
0,
&ReturnLength,
NULL);
if (IsOk == FALSE)
{
int LastError = GetLastError(); if (LastError == ERROR_NO_SYSTEM_RESOURCES)
{
char BufferData[MAX_PATH] = { 0 };
IsOk = DeviceIoControl(DeviceHandle, CTL_SYS,
"Ring3->Ring0",
strlen("Ring3->Ring0") + 1,
(LPVOID)BufferData,
MAX_PATH,
&ReturnLength,
NULL); if (IsOk == TRUE)
{
printf("%s\r\n", BufferData);
}
}
}
if (DeviceHandle != NULL)
{
CloseHandle(DeviceHandle);
DeviceHandle = NULL;
}
printf("Input AnyKey To Exit\r\n"); getchar();
return 0;
}

  

应用程序与驱动程序通信 DeviceIoControl的更多相关文章

  1. CreateFile DeviceIoControl dwIoControlCode——应用程序与驱动程序通信

    在“进程内存管理器中”的一个Ring0,Ring3层通信问题,之前也见过这样的代码,这次拆分出来详细总结一下. 先通过CreateFile函数得到设备句柄,CreateFile函数原型: HANDLE ...

  2. 客户端程序通过TCP通信传送"小文件"到服务器

    客户端程序通过TCP通信传送"小文件"到服务器 [c#源码分享]客户端程序通过TCP通信传送"小文件"到服务器 源码  (不包含通信框架源码,通信框架源码请另行 ...

  3. 计算机网络|C语言Socket编程,实现两个程序间的通信

    C语言Socket编程,实现两个程序间的通信 server和client通信流程图 在mooc上找到的,使用Socket客户端client和服务端server通信的流程图

  4. 小程序接入云通信IM

    小程序接入云通信IM--插件 小程序微信后台搜索AI情报官组件即可获得小程序云通信IM的即时通信能力

  5. Siverlight网页应用程序中WCF通信注意事项

    最近刚刚接触WCF通信,功能就是客户端点击按钮后,服务器端返回一个随机数字.在VS2010中调试的时候,通信都正常,但发布到IIS7中就没反应了,经过几天的摸索,发现WCF的配置要注意以下两点: 1. ...

  6. [转] 微信小程序页面间通信的5种方式

    微信小程序页面间通的5种方式 PageModel(页面模型)对小程序而言是很重要的一个概念,从app.json中也可以看到,小程序就是由一个个页面组成的. 如上图,这是一个常见结构的小程序:首页是一个 ...

  7. electron调用C#应用程序实现串口通信

    最近转入零售行业开发了一系列产品,包含便利店收银软件.会员系统.供应链系统.为了追赶潮流,收银软件使用了electron平台开发,界面效果.开发效率确实不错:但是涉及到串口通讯时遇到了麻烦,elect ...

  8. 将hello程序作为驱动程序编译进系统内核

    0x00开始 恩,可能是我比较愚钝,一个内核编译搞了一天,各种问题,各种bug,几度无奈,也是因为我突发奇想,并没有按照原来的那种操作,我直接把helloworld程序放到内核模块中编译成了一个驱动程 ...

  9. linux应用程序开发-进程通信(IPC)

    IPC why: 1.数据传输 2.资源共享 目的: 3.通知事件 4.进程控制 发展: 1.UNIX进程间通信 2.基于SYStem V 3.POSIX 方式分类: 1.pipe(管道) FIFO( ...

随机推荐

  1. MapReduce的倒排索引

    MapReduce的倒排索引 索引: 什么是索引:索引(Index)是帮助数据库高效获取数据的数据结构.索引是在基于数据库表创建的,它包含一个表中某些列的值以及记录对应的地址,并且把这些值存储在一个数 ...

  2. open live writer安装教程和账号配置

    第一步:Open Live Writer软件下载.官方地址:http://openlivewriter.org/ 第二步:双击安装文件(OpenLiveWriterSetup.exe),然后点击下一步 ...

  3. 使用RTL-SDR打开车门

    在最近几年,入侵汽车在当代社会的黑客圈中成为热点,很多文章表明汽车产业还有很多东西等待完善,在本篇文章中,我会让你熟悉我一直研究的一些概念,以及如何在网状网络中使用一些便宜的部件渗透远程开门系统. 软 ...

  4. keil的51项目创建

    keil的51项目创建步骤: 工程创建: Project->New uVision Project 项目命名:如...test CPU->Atmel::AT89C51 文件创建: File ...

  5. Django runserver UnicodeDecodeError

    编码问题可以说是我遇到过的python 2.7最大的败笔 今天写django时,很简单的一个项目却报UnicodeDecodeError,而我的代码中一个中文字符都没有出现. 如下: 网上找到的所谓解 ...

  6. element ui表格相同内容自动合并

    一开始觉得合并单元格很困难,什么鬼,后来仔细查看api,发现是可以实现的,特此记录下,直接看代码, 项目需求是第一列和第二列还有第16列需要相同内容进行合并,所以判断条件是不同的: 实现后效果如下: ...

  7. django之model多表操作

    一对多表之间的查询: class userInfo(models.Model): name = models.CharField(max_length=50) password = models.Ch ...

  8. python中类的创建和实例化

    python中同样使用关键字class创建一个类,类名称第一个字母大写,可以带括号也可以不带括号: python中实例化类不需要使用关键字new(也没有这个关键字),类的实例化类似函数调用方式: # ...

  9. 深入理解synchronized方法同步的是方法还是对象?

    一.运用synchronized关键字 首先我们来看看一个多线程中线程不安全的列子 代码如下: 共享数据类: public class NotSynchronizated extends Thread ...

  10. 初识vue小结

    初识vue 大家都那么热爱他一定有原因,我也特想了解. 我来咯, 首先用vue开发版,用一个标签在head中插入,script标签src属性引入vue文件,就像jquey一样在script,但是放在h ...