• 驱动开发(8)处理设备I/O控制函数DeviceIoControl


    本博文由CSDN博主zuishikonghuan所作,版权归zuishikonghuan全部,转载请注明出处:http://blog.csdn.net/zuishikonghuan/article/details/50380577

    在上面的两篇博文中,介绍了IRP与派遣函数,以及我们通过了一个样例“磁盘设备的绝对读写”来演示了在应用程序中是怎样向一个设备发出I/O请求的。这篇博文将演示在驱动程序中处理一个很easy的I/O请求——由DeviceIoControl这个Win32API经过一系列的调用,在内核中由I/O管理器构造生成的IRP_MJ_DEVICE_CONTROL这个IRP。

    我们先来看看DeviceIoControl这个函数的原型,此函数向某个打开的设备所在驱动程序的派遣函数中发送IRP:IRP_MJ_DEVICE_CONTROL。函数原型:

    BOOL WINAPI DeviceIoControl(
      _In_        HANDLE       hDevice,
      _In_        DWORD        dwIoControlCode,
      _In_opt_    LPVOID       lpInBuffer,
      _In_        DWORD        nInBufferSize,
      _Out_opt_   LPVOID       lpOutBuffer,
      _In_        DWORD        nOutBufferSize,
      _Out_opt_   LPDWORD      lpBytesReturned,
      _Inout_opt_ LPOVERLAPPED lpOverlapped
    );

    hDevice:操作是要运行的设备句柄。

    使用 CreateFile 函数打开。

    dwIoControlCode:操作的控制代码。

    须要注意的是,这个控制码不是随便定的,为了方便定义控制码,微软提供了一个CTL_CODE宏。控制码的结构例如以下:

    本例中博主定义了这种一个ioctl控制码:

    #define IOCTL1 CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)

    来看看这个宏的用法:

    首先是这个宏的定义:

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

    DeviceType:设备类型。IoCreateDevice使用的设备类型,详细參加我之前的博文”NT驱动的基本结构“

    Function:定义设备类别中的一个操作。0-2047和4096以后被微软保留,2048-4095(0x800-0xFFF)留给我们使用。

    Method:定义操作模式

    METHOD_BUFFERED:缓冲区方法。本例使用这样的方法
    METHOD_IN_DIRECT:直接输入
    METHOD_OUT_DIRECT:直接输出
    METHOD_NEITHER:两者都不,即其它方法

    对于 Windows 嵌入式设备,此字段将被忽略。始终使用 METHOD_BUFFERED。

    Access:一般用FILE_ANY_ACCESS,全部权限。

    lpInBuffer:(可选)指向输入缓冲区的指针。

    nInBufferSize:输入缓冲区以字节为单位的大小。

    lpOutBuffer:(可选)指向输出缓冲区的指针,

    nOutBufferSize:输出缓冲区以字节为单位的大小。

    lpBytesReturned:(可选)指向接收“输出缓冲区中接收的数据的大小”的变量的指针。假设输出缓冲区太小,无法接收不论什么数据,则GetLastError返回ERROR_INSUFFICIENT_BUFFER。此时lpBytesReturned是零。假设输出缓冲区太小,不能容纳全部数据,但能够容纳一些条目,一些驱动可能将尽可能多的返回数据。在这样的情况下,GetLastError返回ERROR_MORE_DATA,然后lpBytesReturned指示接收的数据量。应用程序能够指定一个新的起点再次调用DeviceIoControl。假设lpOverlapped是NULL,那么lpBytesReturned不能为 NULL。

    lpOverlapped:(可选)OVERLAPPED结构的指针。假设打开hDevice时没有指定FILE_FLAG_OVERLAPPED标志,lpOverlapped将被忽略。假设打开 hDevice 时指定了FILE_FLAG_OVERLAPPED 标志,则作为异步操作运行。

    在这样的情况下,lpOverlapped必须指向有效的重叠结构,而且必须包括事件对象的句柄。

    否则。该函数会失败。
    注:异步操作,为 DeviceIoControl 马上返回,而且当在操作完毕时终止的事件对象的操作。

    返回值:假设该操作成功完毕,则返回值不为零。

    假设操作失败。或处于挂起状态,则返回值为零。若要获取扩展的错误信息,请调用GetLastError

    注意:IRP_MJ_DEVICE_CONTROL这个IRP在Win32子系统中调用DeviceIoControl生成。在NT Native层或内核模式下应该使用ZwDeviceIoControlFile。

    事实上这个IRP能够用于应用程序与驱动程序通信。

    先上应用程序的源代码,还是打开我们设备的符号连接。并使用DeviceIoControl函数向驱动程序发送一个控制码,我想我不用再解释什么了,假设你不能理解,请回过头看看我的前两篇和更早的博文。

    源代码:

    #include "stdafx.h"
    #include<Windows.h>
    
    #define IOCTL1 CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	HANDLE handle = CreateFileA("\\.\MyDevice1_link", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    	if (handle == INVALID_HANDLE_VALUE){
    		MessageBoxA(0, "打开设备失败", "错误", 0);
    		return 0;
    	}
    	unsigned char buffer[50] = { 0 };
    	unsigned char buffer2[50] = { 0 };
    	DWORD len;
    	sprintf((char*)buffer, "hello, driver
    ");
    	if (DeviceIoControl(handle, IOCTL1, buffer, strlen((char*)buffer), buffer2, 49, &len, NULL)){
    		printf("len: %d
    ", len);
    		for (int i = 0; i < len; i++){
    			printf("0x%02X ",buffer2[i]);
    		}
    	}
    	getchar();
    	CloseHandle(handle);
    	return 0;
    }

    我们再来看看驱动程序的源代码,在派遣函数中处理这个IRP,我们获取了应用程序发送来的控制码后输出输入缓冲区的数据,并将输出缓冲区填充为0xF1:

    #include <ntddk.h>
    extern "C" VOID DriverUnload(PDRIVER_OBJECT pDriverObject);
    extern "C" NTSTATUS DefDispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp);
    extern "C" NTSTATUS IoctlDispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp);
    
    #define IOCTL1 CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
    
    typedef struct _DEVICE_EXTENSION {
    	UNICODE_STRING SymLinkName;	//我们定义的设备扩展里仅仅有一个符号链接名成员
    } DEVICE_EXTENSION, *PDEVICE_EXTENSION;
    
    extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
    {
    	DbgPrint("DriverEntry
    ");
    
    	pDriverObject->DriverUnload = DriverUnload;//注冊驱动卸载函数
    
    	//注冊派遣函数
    	pDriverObject->MajorFunction[IRP_MJ_CREATE] = DefDispatchRoutine;
    	pDriverObject->MajorFunction[IRP_MJ_CLOSE] = DefDispatchRoutine;
    	pDriverObject->MajorFunction[IRP_MJ_WRITE] = DefDispatchRoutine;
    	pDriverObject->MajorFunction[IRP_MJ_READ] = DefDispatchRoutine;
    	pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IoctlDispatchRoutine;
    
    	NTSTATUS status;
    	PDEVICE_OBJECT pDevObj;
    	PDEVICE_EXTENSION pDevExt;
    
    	//创建设备名称的字符串
    	UNICODE_STRING devName;
    	RtlInitUnicodeString(&devName, L"\Device\MyDevice1");
    
    	//创建设备
    	status = IoCreateDevice(pDriverObject, sizeof(DEVICE_EXTENSION), &devName, FILE_DEVICE_UNKNOWN, 0, TRUE, &pDevObj);
    	if (!NT_SUCCESS(status))
    		return status;
    
    	pDevObj->Flags |= DO_BUFFERED_IO;//将设备设置为缓冲I/O设备,关于缓冲I/O设备将会在下一篇博文中讲。
    	pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;//得到设备扩展
    
    	//创建符号链接
    	UNICODE_STRING symLinkName;
    	RtlInitUnicodeString(&symLinkName, L"\??

    \MyDevice1_link"); pDevExt->SymLinkName = symLinkName; status = IoCreateSymbolicLink(&symLinkName, &devName); if (!NT_SUCCESS(status)) { IoDeleteDevice(pDevObj); return status; } return STATUS_SUCCESS; } extern "C" VOID DriverUnload(PDRIVER_OBJECT pDriverObject) { DbgPrint("DriverUnload "); PDEVICE_OBJECT pDevObj; pDevObj = pDriverObject->DeviceObject; PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;//得到设备扩展 //删除符号链接 UNICODE_STRING pLinkName = pDevExt->SymLinkName; IoDeleteSymbolicLink(&pLinkName); //删除设备 IoDeleteDevice(pDevObj); } extern "C" NTSTATUS DefDispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp) { DbgPrint("Enter DefDispatchRoutine "); NTSTATUS status = STATUS_SUCCESS; pIrp->IoStatus.Status = status; pIrp->IoStatus.Information = 0; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return status; } extern "C" NTSTATUS IoctlDispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp) { DbgPrint("Enter IoctlDispatchRoutine "); NTSTATUS status = STATUS_SUCCESS; //得到I/O堆栈的当前这一层,也就是IO_STACK_LOCATION结构的指针 PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp); ULONG in_size = stack->Parameters.DeviceIoControl.InputBufferLength;//得到输入缓冲区的大小 ULONG out_size = stack->Parameters.DeviceIoControl.OutputBufferLength;//得到输出缓冲区的大小 ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;//得到控制码 PVOID buffer = pIrp->AssociatedIrp.SystemBuffer;//得到缓冲区指针 switch (code) { // process request case IOCTL1: DbgPrint("====Get ioctl code 1 "); //显示输入缓冲区数据 DbgPrint((PCSTR)buffer); //将输出缓冲区填充字符 RtlFillMemory(buffer, out_size, 0xF1); break; default: status = STATUS_INVALID_VARIANT; //假设是没有处理的IRP。则返回STATUS_INVALID_VARIANT。这意味着用户模式的I/O函数失败,但并不会设置GetLastError } // 完毕IRP pIrp->IoStatus.Status = status;//设置IRP完毕状态,会设置用户模式下的GetLastError pIrp->IoStatus.Information = out_size;//设置操作的字节 IoCompleteRequest(pIrp, IO_NO_INCREMENT);//完毕IRP,不添加优先级 return status; }

    效果图:

    这一篇中,本来打算详解一下处理IRP的过程。可是在这个IRP上。实在是不好讲。所下面一篇(处理缓冲I/O设备的读写请求)中介绍IRP处理的具体过程。含义。读和写都是最主要的I/O请求之中的一个。并且也是最好理解的,所下面一篇中,具体介绍怎样处理IRP以及操作系统对于缓冲I/O设备(同步模式下)读写请求的API(ReadFile(Ex)WriteFile(Ex))进入内核后的具体实现过程。


  • 相关阅读:
    线性代数07.Ax=0:主变量,特解
    线性代数06.列空间和零空间
    线性代数05.转置、置换、向量空间
    线性代数04.A的LU分解
    线性代数03.矩阵的乘法和逆
    .线性代数02.矩阵消元
    KEIL中三种编译模式以及对变量空间的影响
    Python之常用模块(三)random模块和序列化
    Python之常用模块(二)time模块与datetime模块
    Python之常用模块(一)自定义模块
  • 原文地址:https://www.cnblogs.com/slgkaifa/p/6956546.html
Copyright © 2020-2023  润新知