27、Windows内核编程,IRP的同步(1)
对设备的任何操作都会转化为IRP请求,而IRP一般都是由操作系统异步发送的。但是有时需要同步来避免逻辑错误。同步方法有:StartIO例程,使用中断服务例程等。
1、应用程序对设备的同步异步操作
1)同步操作原理
大部分IRP是由应用程序的Win32 API发起。这些函数本身就支持同步异步操作。如ReadFile,WriteFile,DeviceIoControl等。
图示 IRP同步操作示意图P250
2)同步操作设备
如CreateFile,其参数dwFlagsAndAttributes 是同步异步的关键。没有设置FILE_FLAG_OVERLAPPED为同步,否则为异步。
再如ReadFile,其参数lpOverlapped 如果设为NULL,则为同步。
#include <windows.h>
#include <stdio.h>
#define BUFFER_SIZE 512
int main()
{
HANDLE hDevice =
CreateFile("test.dat",
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,//此处没有设置FILE_FLAG_OVERLAPPED
NULL );
if (hDevice == INVALID_HANDLE_VALUE)
{
printf("Read Error\n");
return 1;
}
UCHAR buffer[BUFFER_SIZE];
DWORD dwRead;
ReadFile(hDevice,buffer,BUFFER_SIZE,&dwRead,NULL);//这里没有设置OVERLAP参数
CloseHandle(hDevice);
return 0;
}
3)异步操作设备
法一:通过OVERLAPPED 结构体。
typedef struct _OVERLAPPED {
ULONG_PTR Internal;
ULONG_PTR InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;
} OVERLAPPED;
第三个参数:操作设备会指定一个偏移量,从设备的偏移量进行读取。该偏移量用一个64位整形表示。Offset为该偏移量的低32位整形,OffsetHigh为高32位整形。
hEvent用于操作完成后通知应用程序。我们可以初始化其为未激发状态,当操作设备结束后,即在驱动中调用IoCompleteRequest后,设备该事件激发态。
OVERLAPPED 结构使用前要清0,并为其创建事件。
#include <windows.h> #include <stdio.h> #define BUFFER_SIZE 512 //假设该文件大于或等于BUFFER_SIZE #define DEVICE_NAME "test.dat" int main() { HANDLE hDevice = CreateFile("test.dat", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,//此处设置FILE_FLAG_OVERLAPPED NULL ); if (hDevice == INVALID_HANDLE_VALUE) { printf("Read Error\n"); return 1; } UCHAR buffer[BUFFER_SIZE]; DWORD dwRead; //初始化overlap使其内部全部为零 OVERLAPPED overlap={0}; //创建overlap事件 overlap.hEvent = CreateEvent(NULL,FALSE,FALSE,NULL); //这里没有设置OVERLAP参数,因此是异步操作 ReadFile(hDevice,buffer,BUFFER_SIZE,&dwRead,&overlap); //做一些其他操作,这些操作会与读设备并行执行 //等待读设备结束 WaitForSingleObject(overlap.hEvent,INFINITE); CloseHandle(hDevice); return 0; }
法二:ReadFileEx与WriteFileEx 专门被用来异步操作。
BOOL WriteFileEx(
HANDLE hFile, // handle to output file
LPCVOID lpBuffer, // data buffer
DWORD nNumberOfBytesToWrite, // number of bytes to write
LPOVERLAPPED lpOverlapped, // overlapped buffer
LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine // completion routine
);
lpOverlapped不需要提供事件句柄。
ReadFileEx 将读请求传递到驱动程序后立即返回。驱动程序在结束读操作后,会通过调用ReadFileEx提供的回调函数(Call Back Function)lpCompletionRoutine 。类似于一个软中断。Windows将这种机制称为异步过程调用APC(asynchronous procedure call )。只有线程于Alert状态时,回调函数才能被调用。多个函数使系统进行Alert状态:SleepEx,WaitForMultipleObjectsEx,WaitForSingleObjectEx,etc。
OS一旦结束读取操作,就会把相应的completion routine插入到APC队列中。当OS进入Alert状态后,会枚举当前线程的APC队列。
#include <windows.h>
#include <stdio.h>
#define DEVICE_NAME "test.dat"
#define BUFFER_SIZE 512
//假设该文件大于或等于BUFFER_SIZE
VOID CALLBACK MyFileIOCompletionRoutine(
DWORD dwErrorCode, // 对于此次操作返回的状态
DWORD dwNumberOfBytesTransfered, // 告诉已经操作了多少字节,也就是在IRP里的Infomation
LPOVERLAPPED lpOverlapped // 这个数据结构
)
{
printf("IO operation end!\n");
}
int main()
{
HANDLE hDevice =
CreateFile("test.dat",
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,//此处设置FILE_FLAG_OVERLAPPED
NULL );
if (hDevice == INVALID_HANDLE_VALUE)
{
printf("Read Error\n");
return 1;
}
UCHAR buffer[BUFFER_SIZE];
//初始化overlap使其内部全部为零
//不用初始化事件!!
OVERLAPPED overlap={0};
//这里没有设置OVERLAP参数,因此是异步操作
ReadFileEx(hDevice, buffer, BUFFER_SIZE,&overlap,MyFileIOCompletionRoutine);
//做一些其他操作,这些操作会与读设备并行执行
//进入alterable
SleepEx(0,TRUE);
CloseHandle(hDevice);
return 0;
}
2、IRP的同步完成和异步完成
两种方法,一种是在派遣函数中直接结束IRP,称为同步方法。另外一种是在派遣函数中不结束IRP,而让派遣函数直接返回,IRP在以后某个时候再进行处理,也称为异步方法。
比如在ReadFile中同步时,派遣函数在调用IoCompleteRequest时,IoCompleteRequest内部会设置IRP的UserEvent事件。
如果在ReadFile异步时,ReadFile不创建事件,但是接收overlap参数,IoCompleteRequest内部会设置overlap提供的事件。
在ReadFileEx异步中,IoCompleteRequest将ReadFileEx提供的回调函数插入到APC队列中。
异步时,通过GetLastError()得到ERROR_IO_INCOMPLETE。如果派遣函数不调用IoCompleteRequest,此时IRP处于挂起状态。处理方法见示例代码中。
同时,应用程序关闭设置时产生IRP_MJ_CLEANUP类型的IRP。可以在IRP_MJ_CLEANUP的派遣函数中调用IoCompleteRequest来结束那些挂起的IRP请求。
//.h
#pragma once
#ifdef __cplusplus
extern "C"
{
#endif
#include <NTDDK.h>
#ifdef __cplusplus
}
#endif
#define PAGEDCODE code_seg("PAGE")
#define LOCKEDCODE code_seg()
#define INITCODE code_seg("INIT")
#define PAGEDDATA data_seg("PAGE")
#define LOCKEDDATA data_seg()
#define INITDATA data_seg("INIT")
#define arraysize(p) (sizeof(p)/sizeof((p)[0]))
typedef struct _DEVICE_EXTENSION {
PDEVICE_OBJECT pDevice;
UNICODE_STRING ustrDeviceName; //设备名称
UNICODE_STRING ustrSymLinkName; //符号链接名
PLIST_ENTRY pIRPLinkListHead;
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
typedef struct _MY_IRP_ENTRY
{
PIRP pIRP;
LIST_ENTRY ListEntry;
} MY_IRP_ENTRY, *PMY_IRP_ENTRY;
// 函数声明
NTSTATUS CreateDevice (IN PDRIVER_OBJECT pDriverObject);
VOID HelloDDKUnload (IN PDRIVER_OBJECT pDriverObject);
NTSTATUS HelloDDKDispatchRoutin(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp);
NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp);
NTSTATUS HelloDDKCleanUp(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp);
//.cpp
#include "Driver.h"
/************************************************************************
* 函数名称:DriverEntry
* 功能描述:初始化驱动程序,定位和申请硬件资源,创建内核对象
* 参数列表:
pDriverObject:从I/O管理器中传进来的驱动对象
pRegistryPath:驱动程序在注册表的中的路径
* 返回 值:返回初始化驱动状态
*************************************************************************/
#pragma INITCODE
extern "C" NTSTATUS DriverEntry (
IN PDRIVER_OBJECT pDriverObject,
IN PUNICODE_STRING pRegistryPath )
{
NTSTATUS status;
KdPrint(("Enter DriverEntry\n"));
//设置卸载函数
pDriverObject->DriverUnload = HelloDDKUnload;
//设置派遣函数
pDriverObject->MajorFunction[IRP_MJ_CREATE] = HelloDDKDispatchRoutin;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = HelloDDKDispatchRoutin;
pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloDDKDispatchRoutin;
pDriverObject->MajorFunction[IRP_MJ_READ] = HelloDDKRead;
pDriverObject->MajorFunction[IRP_MJ_CLEANUP] = HelloDDKCleanUp;
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = HelloDDKDispatchRoutin;
pDriverObject->MajorFunction[IRP_MJ_SET_INFORMATION] = HelloDDKDispatchRoutin;
pDriverObject->MajorFunction[IRP_MJ_SHUTDOWN] = HelloDDKDispatchRoutin;
pDriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = HelloDDKDispatchRoutin;
//创建驱动设备对象
status = CreateDevice(pDriverObject);
KdPrint(("Leave DriverEntry\n"));
return status;
}
/************************************************************************
* 函数名称:CreateDevice
* 功能描述:初始化设备对象
* 参数列表:
pDriverObject:从I/O管理器中传进来的驱动对象
* 返回 值:返回初始化状态
*************************************************************************/
#pragma INITCODE
NTSTATUS CreateDevice (
IN PDRIVER_OBJECT pDriverObject)
{
NTSTATUS status;
PDEVICE_OBJECT pDevObj;
PDEVICE_EXTENSION pDevExt;
//创建设备名称
UNICODE_STRING devName;
RtlInitUnicodeString(&devName,L"\\Device\\MyDDKDevice");
//创建设备
status = IoCreateDevice( pDriverObject,
sizeof(DEVICE_EXTENSION),
&(UNICODE_STRING)devName,
FILE_DEVICE_UNKNOWN,
, TRUE,
&pDevObj );
if (!NT_SUCCESS(status))
return status;
pDevObj->Flags |= DO_BUFFERED_IO;
pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
pDevExt->pDevice = pDevObj;
pDevExt->ustrDeviceName = devName;
pDevExt->pIRPLinkListHead = (PLIST_ENTRY)ExAllocatePool(PagedPool,sizeof(LIST_ENTRY));
InitializeListHead(pDevExt->pIRPLinkListHead);
//创建符号链接
UNICODE_STRING symLinkName;
RtlInitUnicodeString(&symLinkName,L"\\??\\HelloDDK");
pDevExt->ustrSymLinkName = symLinkName;
status = IoCreateSymbolicLink( &symLinkName,&devName );
if (!NT_SUCCESS(status))
{
IoDeleteDevice( pDevObj );
return status;
}
return STATUS_SUCCESS;
}
/************************************************************************
* 函数名称:HelloDDKUnload
* 功能描述:负责驱动程序的卸载操作
* 参数列表:
pDriverObject:驱动对象
* 返回 值:返回状态
*************************************************************************/
#pragma PAGEDCODE
VOID HelloDDKUnload (IN PDRIVER_OBJECT pDriverObject)
{
PDEVICE_OBJECT pNextObj;
KdPrint(("Enter DriverUnload\n"));
pNextObj = pDriverObject->DeviceObject;
while (pNextObj != NULL)
{
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
pNextObj->DeviceExtension;
//删除符号链接
UNICODE_STRING pLinkName = pDevExt->ustrSymLinkName;
IoDeleteSymbolicLink(&pLinkName);
ExFreePool(pDevExt->pIRPLinkListHead);
pNextObj = pNextObj->NextDevice;
IoDeleteDevice( pDevExt->pDevice );
}
}
/************************************************************************
* 函数名称:HelloDDKDispatchRoutin
* 功能描述:对读IRP进行处理
* 参数列表:
pDevObj:功能设备对象
pIrp:从IO请求包
* 返回 值:返回状态
*************************************************************************/
#pragma PAGEDCODE
NTSTATUS HelloDDKDispatchRoutin(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp)
{
KdPrint(("Enter HelloDDKDispatchRoutin\n"));
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
//建立一个字符串数组与IRP类型对应起来
static char* 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_INFORMATION",
"IRP_MJ_SET_VOLUME_INFORMATION",
"IRP_MJ_DIRECTORY_CONTROL",
"IRP_MJ_FILE_SYSTEM_CONTROL",
"IRP_MJ_DEVICE_CONTROL",
"IRP_MJ_INTERNAL_DEVICE_CONTROL",
"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",
};
UCHAR type = stack->MajorFunction;
if (type >= arraysize(irpname))
KdPrint((" - Unknown IRP, major type %X\n", type));
else
KdPrint(("\t%s\n", irpname[type]));
//对一般IRP的简单操作,后面会介绍对IRP更复杂的操作
NTSTATUS status = STATUS_SUCCESS;
// 完成IRP
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = 0; // bytes xfered
IoCompleteRequest( pIrp, IO_NO_INCREMENT );
KdPrint(("Leave HelloDDKDispatchRoutin\n"));
return status;
}
NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp)
{
KdPrint(("Enter HelloDDKRead\n"));
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
pDevObj->DeviceExtension;
PMY_IRP_ENTRY pIrp_entry = (PMY_IRP_ENTRY)ExAllocatePool(PagedPool,sizeof(MY_IRP_ENTRY));
pIrp_entry->pIRP = pIrp;
//插入队列
InsertHeadList(pDevExt->pIRPLinkListHead,&pIrp_entry->ListEntry);
//将IRP设置为挂起
IoMarkIrpPending(pIrp);
KdPrint(("Leave HelloDDKRead\n"));
//返回pending状态
return STATUS_PENDING;
}
NTSTATUS HelloDDKCleanUp(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp)
{
KdPrint(("Enter HelloDDKCleanUp\n"));
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
pDevObj->DeviceExtension;
//(1)将存在队列中的IRP逐个出队列,并处理
PMY_IRP_ENTRY my_irp_entry;
while(!IsListEmpty(pDevExt->pIRPLinkListHead))
{
PLIST_ENTRY pEntry = RemoveHeadList(pDevExt->pIRPLinkListHead);
my_irp_entry = CONTAINING_RECORD(pEntry,
MY_IRP_ENTRY,
ListEntry);
my_irp_entry->pIRP->IoStatus.Status = STATUS_SUCCESS;
my_irp_entry->pIRP->IoStatus.Information = 0; // bytes xfered
IoCompleteRequest( my_irp_entry->pIRP, IO_NO_INCREMENT );
ExFreePool(my_irp_entry);
}
//(2)处理IRP_MJ_CLEANUP的IRP
NTSTATUS status = STATUS_SUCCESS;
// 完成IRP
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = 0; // bytes xfered
IoCompleteRequest( pIrp, IO_NO_INCREMENT );
KdPrint(("Leave HelloDDKCleanUp\n"));
return STATUS_SUCCESS;
}
//应用程序
#include <windows.h>
#include <stdio.h>
int main()
{
HANDLE hDevice =
CreateFile("\\\\.\\HelloDDK",
GENERIC_READ | GENERIC_WRITE,
,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,//此处设置FILE_FLAG_OVERLAPPED
NULL );
if (hDevice == INVALID_HANDLE_VALUE)
{
printf("Open Device failed!");
return 1;
}
OVERLAPPED overlap1={0};
OVERLAPPED overlap2={0};
UCHAR buffer[10];
ULONG ulRead;
BOOL bRead = ReadFile(hDevice,buffer,10,&ulRead,&overlap1);
if (!bRead && GetLastError()==ERROR_IO_PENDING)
{
printf("The operation is pending\n");
}
bRead = ReadFile(hDevice,buffer,10,&ulRead,&overlap2);
if (!bRead && GetLastError()==ERROR_IO_PENDING)
{
printf("The operation is pending\n");
}
//迫使程序中止2秒
Sleep(2000);
//创建IRP_MJ_CLEANUP IRP
CloseHandle(hDevice);
return 0;
}
取消IRP
PDRIVER_CANCEL
IoSetCancelRoutine(
IN PIRP Irp,
IN PDRIVER_CANCEL CancelRoutine
);
来设置取消IRP请求的回调函数。也可以用来删除取消例程,当CancelRoutine为空指针时,则删除原来设置的取消例程。
用IoCancelIrp函数指定取消IRP请求。在IoCancelIrp内部,通过cancel lock来进行同步。
IoReleaseCancelSpinLock
IoAcquireCancelSpinLock
可以用CancelIo API 来取消IRP请求。在CancelIo内部会枚举所有没有被完成的IRP,依次调用IoCancelIrp。如果应用程序没有调用CancelIo,在应用程序关闭时也会自动调用CancelIo。
注意:cancel lock是全局自旋锁,占用时间不宜太长。
VOID CancelReadIRP( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { KdPrint(("Enter CancelReadIRP\n")); PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension; //设置完成状态为STATUS_CANCELLED Irp->IoStatus.Status = STATUS_CANCELLED; Irp->IoStatus.Information = 0; // bytes xfered IoCompleteRequest( Irp, IO_NO_INCREMENT ); //释放Cancel自旋锁 IoReleaseCancelSpinLock(Irp->CancelIrql); KdPrint(("Leave CancelReadIRP\n")); } NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp) { KdPrint(("Enter HelloDDKRead\n")); PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION) pDevObj->DeviceExtension; IoSetCancelRoutine(pIrp,CancelReadIRP); //将IRP设置为挂起 IoMarkIrpPending(pIrp); KdPrint(("Leave HelloDDKRead\n")); //返回pending状态 return STATUS_PENDING; }
#include <windows.h> #include <stdio.h> int main() { HANDLE hDevice = CreateFile("\\\\.\\HelloDDK", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,//此处设置FILE_FLAG_OVERLAPPED NULL ); if (hDevice == INVALID_HANDLE_VALUE) { printf("Open Device failed!"); return 1; } OVERLAPPED overlap1={0}; OVERLAPPED overlap2={0}; UCHAR buffer[10]; ULONG ulRead; BOOL bRead = ReadFile(hDevice,buffer,10,&ulRead,&overlap1); if (!bRead && GetLastError()==ERROR_IO_PENDING) { printf("The operation is pending\n"); } bRead = ReadFile(hDevice,buffer,10,&ulRead,&overlap2); if (!bRead && GetLastError()==ERROR_IO_PENDING) { printf("The operation is pending\n"); } //迫使程序中止2秒 Sleep(2000); //显式的调用CancelIo,其实在关闭设备时会自动运行CancelIo CancelIo(hDevice); //创建IRP_MJ_CLEANUP IRP CloseHandle(hDevice); return 0; }