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

  在“进程内存管理器中”的一个Ring0,Ring3层通信问题,之前也见过这样的代码,这次拆分出来详细总结一下。

  先通过CreateFile函数得到设备句柄,CreateFile函数原型:

  

HANDLE CreateFile(
    LPCTSTR lpFileName,                         // 文件名/设备路径 设备的名称
    DWORD dwDesiredAccess,                      // 访问方式
    DWORD dwShareMode,                          // 共享方式
    LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 安全描述符指针
    DWORD dwCreationDisposition,                // 创建方式
    DWORD dwFlagsAndAttributes,                 // 文件属性及标志
    HANDLE hTemplateFile                        // 模板文件的句柄
);

打开:createFile

关闭:closehandle

与普通文件名有所不同,设备驱动的“文件名”(常称为“设备路径”)形式固定为“\\.\DeviceName”(注意写法为“\\\\.\\DeviceName”),DeviceName必须与设备驱动程序内定义的设备名称一致。

在“进程内存管理器”中:

Ring0层的kProcessMemory.h

#define DEVICE_NAME L"\\Device\\KProcessMemoryDeviceName"
#define LINK_NAME L"\\DosDevices\\KProcessMemoryLinkName"

Ring3层的ProcessMemoryManager.cpp

OpenDeviceObject(L"\\\\.\\KProcessMemoryLinkName");

BOOL OpenDeviceObject(LPCTSTR DeviceFullPathData)
{
m_DeviceHandle = CreateFile(DeviceFullPathData,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);

if (m_DeviceHandle == INVALID_HANDLE_VALUE)
{
return FALSE;
}

return TRUE;

}

可以看到在kProcessMemory.h中define了一个驱动设备名和一个符号链接名。

驱动设备名是调用IoCreateDevice时使用的,IoCreateDevice函数原型:

NTSTATUS IoCreateDevice(
  _In_      PDRIVER_OBJECT DriverObject,
  _In_      ULONG DeviceExtensionSize,
  _In_opt_  PUNICODE_STRING DeviceName,
  _In_      DEVICE_TYPE DeviceType,
  _In_      ULONG DeviceCharacteristics,
  _In_      BOOLEAN Exclusive,
  _Out_     PDEVICE_OBJECT *DeviceObject
);

驱动程序中调用IoCreateDevice函数:

RtlInitUnicodeString(&DeviceName, DEVICE_NAME);
Status = IoCreateDevice(DriverObject, 0, &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &DeviceObject);

关于在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

winobj和DeviceTree可以用来查看这些信息。

关于驱动设备名和符号链接名,可以参考这篇博客:

http://www.cnblogs.com/findumars/p/5636505.html

接着回到CreateFile函数上来,它的第二个参数,dwDesireAceess访问方式,一般设置为0或GENERIC_READ|GENERIC_WRITE,共享方式参数设置为FILE_SHARE_READ|FILE_SHARE_WRITE,创建方式参数设置为OPEN_EXISTING,其它参数一般设置为0或NULL。

Ring3层的CreateFile函数获取了设备句柄后,将使用DeviceIoControl函数向指定的设备驱动发送一个IO控制码,驱动程序通过这个控制码来完成特定的工作。该函数原型如下:

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将进行阻塞调用;否则,应在编程时按异步操作设计
);

  先介绍IO控制码,驱动程序可以通过CTL_CODE宏来组合定义一个控制码,并在IRP_MJ_DEVICE_CONTROL的实现中进行控制码的操作。在驱动层,IoStackLocation->Parameters.DeviceIoControl.IoControlCode表示了这个控制码。发送不同的控制码,可以调用设备驱动程序的不同类型的功能。在头文件winioctl.h中,预定义的标准设备控制码,都以IOCTL或FSCTL开头。例如,IOCTL_DISK_GET_DRIVE_GEOMETRY是对物理驱动器取结构参数(介质类型、柱面数、每柱面磁道数、每磁道扇区数等)的控制码,FSCTL_LOCK_VOLUME是对逻辑驱动器的卷加锁的控制码。

lpInBuffer

由用户层发送的缓冲区数据。在“进程内存管理器“程序中,我们是通过进程ID来查询进程内存,故传入的是进程ID.在驱动层,依传输类型的不同,输入缓冲区的位置亦不同,见下表。

传输类型 位置
METHOD_IN_DIRECT irp->AssociatedIrp.SystemBuffer
METHOD_OUT_DIRECT irp->AssociatedIrp.SystemBuffer
METHOD_BUFFERED irp->AssociatedIrp.SystemBuffer
METHOD_NEITHER irpStack->Parameters.DeviceIoControl.Type3InputBuffer

nInBufferSize

由用户层发送的缓冲区大小。在驱动层,这个值是IoStackLocation->Parameters.DeviceIoControl.InputBufferLength。

lpOutBuffer

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

传输类型 位置
METHOD_IN_DIRECT irp->MdlAddress
METHOD_OUT_DIRECT irp->MdlAddress
METHOD_BUFFERED irp->AssociatedIrp.SystemBuffer
METHOD_NEITHER irp->UserBuffer

nOutBufferSize

由用户层指定,用于接收驱动层返回数据的缓冲区大小。在驱动层,这个值是IoStackLocation->Parameters.DeviceIoControl.OutputBufferLength。

lpBytesReturned

由用户层指定,用于接收驱动层实际返回数据大小。在驱动层,这个值是irp->IoStatus->Information。

lpOverlapped

用于异步操作。

程序中的派遣函数:


#define CTL_QUERY_PROCESS_MEMORY \
CTL_CODE(FILE_DEVICE_UNKNOWN,0x830,METHOD_NEITHER,FILE_ANY_ACCESS)


#define CTL_READ_PROCESS_MEMORY \
CTL_CODE(FILE_DEVICE_UNKNOWN,0x831,METHOD_NEITHER,FILE_ANY_ACCESS)


#define CTL_WRITE_PROCESS_MEMORY \
CTL_CODE(FILE_DEVICE_UNKNOWN,0x832,METHOD_NEITHER,FILE_ANY_ACCESS)

......

RtlInitUnicodeString(&DeviceName, DEVICE_NAME);
Status = IoCreateDevice(DriverObject, 0, &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &DeviceObject);

......

RtlInitUnicodeString(&LinkName, LINK_NAME);
Status = IoCreateSymbolicLink(&LinkName, &DeviceName);

......

DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DeviceControlDispatch;

NTSTATUS DeviceControlDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{

    NTSTATUS Status = STATUS_SUCCESS;
    ULONG    IoControlCode = 0;
    ULONG    InputLength = 0;
    SIZE_T   OutputLength = 0;
    PVOID    InputData = NULL;
    PVOID    OutputData = NULL;

    PIO_STACK_LOCATION IoStackLocation = IoGetCurrentIrpStackLocation(Irp);
    IoControlCode = IoStackLocation->Parameters.DeviceIoControl.IoControlCode;
    //BufferIO
    //InputData = OutputData = Irp->AssociatedIrp.SystemBuffer;
    /*
    直接方式DO_DIRECT_IO / 非直接方式(缓冲方式)DO_BUFFERD_IO
        1) 在buffered(AssociatedIrp.SystemBuffer)方式中,I/O管理器先创建一个与用户模式数据缓冲区大小相等的系统缓冲区。而你的驱动程序将使用这个系统缓冲区工作。
           I/O管理器负责在系统缓冲区和用户模式缓冲区之间复制数据。
        2) 在direct(MdlAddress)方式中,I/O管理器锁定了包含用户模式缓冲区的物理内存页,并创建一个称为MDL(内存描述符表)的辅助数据结构来描述锁定页。
           因此你的驱动程序将使用MDL工作。
        3) 在neither(UserBuffer)方式中,I/O管理器仅简单地把用户模式的虚拟地址传递给你。
           而使用用户模式地址的驱动程序应十分小心。
    */
    //Neither方式提高了通信效率,但是不够安全,在读写之前应使用ProbeForRead和ProbeForWrite函数探测地址是可读和可写
    //详见eDiary笔记中“Driver——DeviceIoControl函数与IoControlCode”
    InputData = IoStackLocation->Parameters.DeviceIoControl.Type3InputBuffer;//得到Ring3的输入缓冲区地址
    OutputData = Irp->UserBuffer;                                            //得到Ring3的输出缓冲区地址
    InputLength = IoStackLocation->Parameters.DeviceIoControl.InputBufferLength;
    OutputLength = IoStackLocation->Parameters.DeviceIoControl.OutputBufferLength;

    switch (IoControlCode)   //IO控制码
    {
    case CTL_QUERY_PROCESS_MEMORY:
    {
        if (!MmIsAddressValid(OutputData))
        {
            Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
            Irp->IoStatus.Information = 0;
            break;
        }
        if (InputLength != sizeof(ULONG) || InputData == NULL)
        {
            Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
            Irp->IoStatus.Information = 0;
            break;
        }
        __try
        {

            ProbeForWrite(OutputData, OutputLength, 1);  //检测内存是否可写,这个函数需要在用户模式下使用,详见MSDN:
            //If Irp->RequestorMode = KernelMode, the Irp->AssociatedIrp.SystemBuffer and Irp->UserBuffer fields do not contain user-mode addresses,
            //and a call to ProbeForWrite to probe a buffer pointed to by either field will raise an exception.

            Status = RtlQueryVirtualMemory(*(PULONG)InputData, OutputData, OutputLength);
            Irp->IoStatus.Information = 0;
            Irp->IoStatus.Status = Status;

        }
        __except (EXCEPTION_EXECUTE_HANDLER)
        {
            Irp->IoStatus.Information = 0;
            Status = Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
        }
        break;
    }
    case CTL_READ_PROCESS_MEMORY:
    {

        if (!MmIsAddressValid(OutputData) || OutputLength>MAX_LENGTH)
        {
            Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
            Irp->IoStatus.Information = 0;
            break;
        }

        if (InputLength != sizeof(READ_OPERATION) || InputData == NULL)
        {
            Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
            Irp->IoStatus.Information = 0;

            break;
        }
        __try
        {

            ProbeForWrite(OutputData, OutputLength, 1);
            Status = RtlReadVirtualMemory(InputData, OutputData, OutputLength);
            Irp->IoStatus.Information = 0;
            Irp->IoStatus.Status = Status;

        }
        __except (EXCEPTION_EXECUTE_HANDLER)
        {
            Irp->IoStatus.Information = 0;
            Status = Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
        }

        break;
    }
    case CTL_WRITE_PROCESS_MEMORY:
    {

        if (InputLength < sizeof(WRITE_OPERATION) || InputData == NULL)
        {
            Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
            Irp->IoStatus.Information = 0;

            break;
        }
        __try
        {
            Status = RtlWriteVirtualMemory(InputData);
            Irp->IoStatus.Information = 0;
            Irp->IoStatus.Status = Status;

        }
        __except (EXCEPTION_EXECUTE_HANDLER)
        {
            Irp->IoStatus.Information = 0;
            Status = Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
        }
        break;
    }
    default:
    {

        Irp->IoStatus.Information = 0;
        Status = Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
        break;
    }
    }

    IoCompleteRequest(Irp, IO_NO_INCREMENT);       //将Irp返回给IO管理器
    return Status;
}
时间: 2024-10-25 21:07:06

CreateFile DeviceIoControl dwIoControlCode——应用程序与驱动程序通信的相关文章

驱动开发之 用DeviceIoControl实现应用程序与驱动程序通信

Ring3测试程序:http://blog.csdn.net/zj510/article/details/8216321 1.readfile和writefile可以实现应用程序与驱动程序通信,另外一个Win32 API 是DeviceIoControl. 应用程序自定义一中IO控制码,然后调用DeviceIoControl函数,IO管理器会产生一个MajorFunction 为IRP_MJ_DEVICE_CONTROL,MinorFunction 为自己定义的控制码的IRP,系统就调用相应的处

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

调用的方法之一的DeviceIoControl 驱动层提供设备名 例如filedisk 在驱动层 首先先是注册列表 用winObj查看 filedisk的驱动对象 但是 这八个对象时怎么生成的呢? 我们在加载filedisk.sys驱动时进行中断 查看过程 具体的双击调试 看我的另一篇文章 http://www.cnblogs.com/UnMovedMover/p/3690369.html 在下载的源码filedisk中sys下面的filedisk-17\filedisk-17\sys\src

iOS 程序间的通信

1.什么是程序间的通信:个人理解就是,比如有二个程序,程序A和程序B,通过点击程序A中的方法,唤醒程序B,进入程序B,并将程序A的值传入传入给程序B; 2.为什么会有程序间的通信? 在iOS里,由于程序本身采用沙盒结构,相互之间是隔离的,比较封闭,唯一的程序间通信方式是采用苹果提供的接口利用URL Scheme进行,除此之外目前并没有更好的方式. 3.下面我们就来实现以下程序间的通信:A--->B 3.1首先我们新建2个工程项目A和项目B: 3.2建好之后,我们现在是要实现A调转到B,所以我们在

windows下QT前台和linux下后台程序通过socket通信

通常情况下,linux下的后台程序不需要GUI进行展示,而前台程序往往有个界面,方便和用户的交互.本文所演示的例 子,是QT 程序和后台linux进程(C语言)交互,通过socket传输的内容是结构体.因为QT本身是跨平台的框架,因此以后前端程序移植到其它平台依然能很好 的运行. 结构体的定义如下: struct Test              {                      int a;                      char b;              };

可控硅过零导通程序--可控硅驱动程序

if(F_moto_en) { if(T_moto_delay==0) {//过零延时导通时间 既功率控制--过零重新赋值T_moto_delay=M_Power-1; if(T_250us_hot<=2)//可控硅导通时间 { T_250us_hot++; WORK_H_ON; } else { WORK_H_OFF; } } else {//延时导通时间 T_moto_delay--; } } else { WORK_H_OFF; } 可控硅过零导通程序--可控硅驱动程序

linux下java程序与C语言程序通过SOCKET通信的简单例子

linux下java程序与C语言程序通过SOCKET通信的简单例子 今天上午实验了java程序与c语言程序通过socket进行通信.由于没学过java,因此只是编写了C语言端的代码,java端的代码是从网上别的文章中找的,经过少量修改后与C语言端程序通信成功. 本例中C语言端作为服务器,java端作为客户端 代码如下: /****************** server program *****************/ #include <stdio.h> #include <sy

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

C语言Socket编程,实现两个程序间的通信 server和client通信流程图 在mooc上找到的,使用Socket客户端client和服务端server通信的流程图?? 服务端server 服务端需要 "两个"套接字 : 1.服务端套接字serverSocket 2.客户端connect连接请求时,发来的套接字clientSocket 按流程图来看, server服务端主要就是实现下面几个步骤: 0.WSAStartup初始化 //这个东西也不知道是什么鬼,反正就是要初始化一下,

I2C协议-&gt;裸机程序-&gt;adapter驱动程序分析

开发板:mini2440 内核  :linux2.6.32.2 参考  :韦东山毕业班I2C视频教程 1.i2c协议简要分析 i2c中线是一种由 PHILIPS 公司开发的串行总线,用于连接微控制器及其外围设备,它具有以下特点. 1.只有两条总线线路:一条串行数据线SDA,一条串行时钟线SCL. 2.每个连接到总线的器件都可以使用软件根据它的唯一的地址来确定. 3.传输数据的设备之间是简单的主从关系. 4.主机可以用作主机发送器或者主机接收器. 5.它是一个真正的多主机总线,两个或多个主机同时发

5.2 应用程序和驱动程序中buffer的传输流程

摄像头采集的数据,是通过buffer的形式传到驱动程序中,然后驱动程序使能CSI设备来采集数据.之后将采集到的数据再次通过buffer的形式传递给应用程序中.这个过程中使用了VIDIOC_REQBUFS,VIDIOC_QUERYBUF,VIDIOC_QBUF,VIDIOC_STREAMON和VIDIOC_DQBUF这些ioctl调用.下面就来具体分析这个流程. 1. 内核中的数据结构 内核中有关V4L2的头文件是/include/uapi/linux/videodev2.h文件. 1.1 v4l