VS2013 WDK8.1驱动开发7(派遣函数)
本系列博客为学习《Windows驱动开发技术详解》一书的学习笔记。
前言
驱动程序的主要功能是负责处理I/O请求。用户模式下所有对驱动程序的I/O请求,全部由操作系统转化为一个叫做IRP的数据结构,不同的IRP数据会被“派遣”到不同的派遣函数中。
IRP与派遣函数
在前面介绍的HelloNTDriver和HelloWDM驱动程序中,我们会在DriverEntry里注册IRP的派遣函数。在HelloNTDriver中我们的DriverEntry内容如下:
/// @brief 驱动程序入口函数
/// @param[in] pDriverObject 从I/O管理器中传进来的驱动对象
/// @param[in] pRegPath 驱动程序在注册表中的路径
/// @return 初始化驱动状态
NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegPath)
{
NTSTATUS status = STATUS_SUCCESS;
KdPrint(("Enter DriverEntry\n"));
UNREFERENCED_PARAMETER(pRegPath);
// 注册驱动调用函数入口
// 这些函数不是由驱动程序本身负责调用, 而是由操作系统负责调用
pDriverObject->DriverUnload = HelloNTDriverUnload;
pDriverObject->MajorFunction[IRP_MJ_CREATE] = HelloNTDriverDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = HelloNTDriverDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloNTDriverDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_READ] = HelloNTDriverDispatchRoutine;
// 创建驱动设备对象
status = CreateDevice(pDriverObject);
KdPrint(("Leave DriverEntry\n"));
return status;
}
驱动对象pDriverObject中有个函数指针数组MajorFunction,每个元素都记录着一个函数的地址。通过设置这个数组,可以将IRP类型和派遣函数关联起来。示例中我们只设置了4个IRP的派遣函数,对于其他没有设置的IRP类型,系统默认这些IRP类型与_IopInvalidDeviceRequest函数关联。
IRP类型
示例中我们对4个类型的IRP(IRP_MJ_CREATE,IRP_MJ_CLOSE,IRP_MJ_WRITE,IRP_MJ_READ])设置了派遣函数,它们分别由CreateFile,CloseHandle,WriteFile,ReadFile这4个I/O函数产生。还有很多IRP由系统创建,下表列出了IRP的类型,并对其产生的来源做了说明:
| IRP类型 | 来源
|:------------- |:-------------
| IRP_MJ_CREATE | 创建设备,CreateFile会产生此IRP
| IRP_MJ_CLOSE | 关闭设备,CloseHandle会产生此IRP
| IRP_MJ_CLEANUP | 清除工作,CloseHandle会产生此IRP
| IRP_MJ_DEVICE_CONTROL | DeviceIOControl函数会产生此IRP
| IRP_MJ_PNP | 即插即用消息,WDM驱动才支持此种IRP
| IRP_MJ_POWER | 在操作系统处理电源消息时,产生此IRP
| IRP_MJ_QUERY_INFORMATION | 获取文件长度,GetFileSize会产生此IRP
| IRP_MJ_READ | 读取设备内容,ReadFile会产生此IRP
| IRP_MJ_SET_INFORMATION | 设置文件位置,SetEndOfFile会产生此IRP
| IRP_MJ_SHUTDOWN | 关闭系统前会产生此IRP
| IRP_MJ_SYSTEM_CONTROL | 系统内部产生的控制信息
| IRP_MJ_WRITE | 对设备进行WriteFile时产生此IRP
对IRP的简单处理
在HelloNTDriver中我们对IRP的简单处理函数如下:
/// @brief 对IRP进行处理
/// @param[in] pDriverObject
/// @param[in] pIrp
/// @return
NTSTATUS HelloNTDriverDispatchRoutine(IN PDEVICE_OBJECT pDevObject, IN PIRP pIrp)
{
KdPrint(("Enter HelloNTDriverDispatchRoutine\n"));
NTSTATUS status = STATUS_SUCCESS;
UNREFERENCED_PARAMETER(pDevObject);
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
KdPrint(("Leave HelloNTDriverDispatchRoutine\n"));
return status;
}
我们设置了IRP的完成状态(pIrp->IoStatus.Status)为STATUS_SUCCESS,这样发起I/O请求的Win32API(如ReadFile)将会返回TRUE。如果设置IRP的完成状态为别的值(如STATUS_TIMEOUT ),那么Win32API将会返回False。这时我们可以使用GetLastError得到错误代码,得到的错误代码就是我们设置的IRP的状态值。
除了设置完成状态,我们还需要设置这个IRP操作了多少字节(pIrp->IoStatus.Information = 0)。如果是ReadFile产生的IRP,这个字节数代表从设备读了多少字节。
最后我们通过IoCompleteRequest将IRP请求结束。
I/O堆栈
在前面我们介绍过驱动的层次结构。不同的驱动对象会创建一个个设备对象,并将这些设备对象附加成一个垂直结构,这种结构被称为设备栈。IRP会被操作系统发送到设备栈的顶层,如果某层的设备对象的派遣函数结束了IRP的请求,则这次I/O请求结束。如果没有将IRP的请求结束,那么操作系统将IRP转发到设备栈的下一层处理。如下图所示:
因此一个IRP可能会被转发多次,为了记录IRP在每层设备中做的操作,IRP会有一个IO_STACK_LOCATION数组。数组的元素大于IRP穿越过的设备数。每个IO_STACK_LOCATION元素记录着对应设备中做的操作。对于本层设备对于的IO_STACK_LOCATION,可以通过IoGetCurrentIrpStackLocation函数得到,IO_STACK_LOCATION中会记录IRP的类型,我们修改我们的IRP派遣函数,让它打印当前的IRP类型:
/// @brief 对IRP进行处理
/// @param[in] pDriverObject
/// @param[in] pIrp
/// @return
NTSTATUS HelloNTDriverDispatchRoutine(IN PDEVICE_OBJECT pDevObject, IN PIRP pIrp)
{
static char* s_IRPName[] =
{
"IRP_MJ_CREATE ",
"IRP_MJ_CREATE_NAMED_PIPE ",
"IRP_MJ_CLOSE ",
"IRP_MJ_READ ",
"IRP_MJ_WRITE ",
"IRP_MJ_QUERY_INFORMATION ",
"IRP_MJ_SET_INFORMATION ",
"IRP_MJ_QUERY_EA ",
"IRP_MJ_SET_EA ",
"IRP_MJ_FLUSH_BUFFERS ",
"IRP_MJ_QUERY_VOLUME_INFORMAT",
"IRP_MJ_SET_VOLUME_INFORMATIO",
"IRP_MJ_DIRECTORY_CONTROL ",
"IRP_MJ_FILE_SYSTEM_CONTROL ",
"IRP_MJ_DEVICE_CONTROL ",
"IRP_MJ_INTERNAL_DEVICE_CONTR",
"IRP_MJ_SHUTDOWN ",
"IRP_MJ_LOCK_CONTROL ",
"IRP_MJ_CLEANUP ",
"IRP_MJ_CREATE_MAILSLOT ",
"IRP_MJ_QUERY_SECURITY ",
"IRP_MJ_SET_SECURITY ",
"IRP_MJ_POWER ",
"IRP_MJ_SYSTEM_CONTROL ",
"IRP_MJ_DEVICE_CHANGE ",
"IRP_MJ_QUERY_QUOTA ",
"IRP_MJ_SET_QUOTA ",
"IRP_MJ_PNP "
};
KdPrint(("Enter HelloNTDriverDispatchRoutine\n"));
NTSTATUS status = STATUS_SUCCESS;
UNREFERENCED_PARAMETER(pDevObject);
PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp);
UCHAR irpType = pStack->MajorFunction;
KdPrint((s_IRPName[irpType]));
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
KdPrint(("Leave HelloNTDriverDispatchRoutine\n"));
return status;
}
如果我们完成一个Win32程序,对我们的设备对象进行打开和关闭动作,就会得到如下结果:
其他章节链接
VS2013 WDK8.1驱动开发3(手动加载NT驱动程序)
VS2013 WDK8.1驱动开发7(派遣函数)
还没有人评论...