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

http://blog.csdn.net/zuishikonghuan/article/details/50380577

在上面的两篇博文中,介绍了IRP与派遣函数,以及我们通过了一个例子“磁盘设备的绝对读写”来演示了在应用程序中是如何向一个设备发出I/O请求的。这篇博文将演示在驱动程序中处理一个非常简单的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)

来看看这个宏的使用方法:

首先是这个宏的定义:

扫描二维码关注公众号,回复: 6745788 查看本文章
 
  1. #define CTL_CODE(DeviceType, Function, Method, Access) (

  2. ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))

DeviceType:设备类型,IoCreateDevice使用的设备类型,具体参加我之前的博文”NT驱动的基本结构“

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

Method:定义操作模式

 
  1. METHOD_BUFFERED:缓冲区方法,本例使用这种方法

  2. METHOD_IN_DIRECT:直接输入

  3. METHOD_OUT_DIRECT:直接输出

  4. 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\r\n");

if (DeviceIoControl(handle, IOCTL1, buffer, strlen((char*)buffer), buffer2, 49, &len, NULL)){

printf("len: %d\n", 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\r\n");


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\r\n");

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\r\n");

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\r\n");

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\r\n");

//显示输入缓冲区数据

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))进入内核后的详细实现过程。

猜你喜欢

转载自blog.csdn.net/wxh0000mm/article/details/94721005