1. 同步
同步创建方式的意思是应用层必须要等待IRP被IoCompleteRequest后才会返回。其必须是同步的。即使底层设备对收到的IRP进行挂起处理,那也得无限等待下去。看一下下面的例子:
该例子主要是进行了以下步骤:
- 应用层程序发送了Read类型的IRP给中间设备
- 中间设备捕获IRP后完成该IRP并调用ZwReadFile重新创建了一个IRP给底层设备
- 底层设备挂起该IRP并设置DPC例程来处理该被挂起的IRP
- 底层设备DPC例程完成了IRP后返回
接下去看一下底层设备代码:
#include <ntddk.h>
typedef struct _DEVICE_EXTENSION {
PDEVICE_OBJECT pDevice;
UNICODE_STRING ustrDeviceName; //设备名称
UNICODE_STRING ustrSymLinkName; //符号链接名
KDPC pollingDPC; // 存储DPC对象
KTIMER pollingTimer;// 存储计时器对象
PIRP currentPendingIRP;//记录当前挂起的IRP
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
VOID Unload(IN PDRIVER_OBJECT pDriverObject) {
PDEVICE_OBJECT pNextObj;
KdPrint(("DriverA: 进入Unload例程\n"));
pNextObj = pDriverObject->DeviceObject;
while (pNextObj) {
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pNextObj->DeviceExtension;
UNICODE_STRING pLinkName = pDevExt->ustrSymLinkName;
IoDeleteSymbolicLink(&pLinkName);
IoDeleteDevice(pDevExt->pDevice);
pNextObj = pNextObj->NextDevice;
}
KdPrint(("DriverA: 离开Unload例程\n"));
}
#define MICROSECOND(s) (-10 * s)
#define MILLISECOND(s) (MICROSECOND(s) * 1000)
#define SECOND(s) (MILLISECOND(s) * 1000)
void OnTimerDpc(PKDPC Dpc, PVOID Context, PVOID SystemArgument1, PVOID SystemArgument2) {
KdPrint(("DriverA: 进入DPC例程\n"));
PDEVICE_OBJECT pDevObj = (PDEVICE_OBJECT)Context;
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
PIRP currentPendingIrp = pDevExt->currentPendingIRP;
currentPendingIrp->IoStatus.Status = STATUS_SUCCESS;
currentPendingIrp->IoStatus.Information = 0;
KdPrint(("完成了挂起的IRP: 0x%08X\n", currentPendingIrp));
IoCompleteRequest(currentPendingIrp, IO_NO_INCREMENT);
KdPrint(("DriverA: 离开DPC例程\n"));
}
NTSTATUS CreateDevice(IN PDRIVER_OBJECT pDriverObject) {
NTSTATUS status;
PDEVICE_OBJECT pDevObj;
PDEVICE_EXTENSION pDevExt;
//创建设备名称
UNICODE_STRING devName;
RtlInitUnicodeString(&devName, L"\\Device\\DevA");
//创建设备
status = IoCreateDevice(pDriverObject, sizeof(DEVICE_EXTENSION), &devName, FILE_DEVICE_UNKNOWN, 0, TRUE, &pDevObj);
if (!NT_SUCCESS(status)) {
KdPrint(("IoCreateDevice: %08X\n", status));
return status;
}
pDevObj->Flags |= DO_BUFFERED_IO;
pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
pDevExt->pDevice = pDevObj;
pDevExt->ustrDeviceName = devName;
KeInitializeTimer(&pDevExt->pollingTimer);
KeInitializeDpc(&pDevExt->pollingDPC, OnTimerDpc, (PVOID)pDevObj);
UNICODE_STRING symLinkName;
RtlInitUnicodeString(&symLinkName, L"\\??\\DevA");
pDevExt->ustrSymLinkName = symLinkName;
status = IoCreateSymbolicLink(&symLinkName, &devName);
if (!NT_SUCCESS(status)) {
KdPrint(("IoCreateSymbolicLink: %08X\n", status));
IoDeleteDevice(pDevObj);
return status;
}
return STATUS_SUCCESS;
}
NTSTATUS DispatchRoutine(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp) {
NTSTATUS status = STATUS_SUCCESS;
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return status;
}
NTSTATUS ReadRoutine(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp) {
KdPrint(("DriverA: 进入了ReadRoutine\n"));
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
LARGE_INTEGER li = RtlConvertLongToLargeInteger(-30 * 1000 * 1000);
pDevExt->currentPendingIRP = pIrp; // 保存IRP
IoMarkIrpPending(pIrp); // 挂起IRP
KeSetTimer(&pDevExt->pollingTimer, li, &pDevExt->pollingDPC); // 设置了DPC例程3秒后启动
KdPrint(("DriverA: 离开了ReadRoutine\n"));
return(STATUS_PENDING);
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath) {
KdPrint(("DriverA: 进入DriverEntry\n"));
pDriverObject->DriverUnload = Unload;
for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; ++i)
pDriverObject->MajorFunction[i] = DispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_READ] = ReadRoutine;
NTSTATUS status = CreateDevice(pDriverObject);
if (!NT_SUCCESS(status))
return(status);
KdPrint(("DriverA: 离开DriverEntry\n"));
return(STATUS_SUCCESS);
}
来看一下中间设备代码:
#include <ntddk.h>
typedef struct _DEVICE_EXTENSION {
PDEVICE_OBJECT pDevice;
UNICODE_STRING ustrDeviceName; //设备名称
UNICODE_STRING ustrSymLinkName; //符号链接名
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
NTSTATUS DispatchRoutine(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp) {
NTSTATUS status = STATUS_SUCCESS;
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return status;
}
VOID Unload(IN PDRIVER_OBJECT pDriverObject) {
PDEVICE_OBJECT pNextObj;
KdPrint(("DriverB: 进入Unload例程\n"));
pNextObj = pDriverObject->DeviceObject;
while (pNextObj) {
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pNextObj->DeviceExtension;
UNICODE_STRING pLinkName = pDevExt->ustrSymLinkName;
IoDeleteSymbolicLink(&pLinkName);
IoDeleteDevice(pDevExt->pDevice);
pNextObj = pNextObj->NextDevice;
}
KdPrint(("DriverB: 离开Unload例程\n"));
}
NTSTATUS CreateDevice(IN PDRIVER_OBJECT pDriverObject) {
NTSTATUS ntStatus;
PDEVICE_OBJECT pDevObj;
PDEVICE_EXTENSION pDevExt;
UNICODE_STRING symLinkName;
UNICODE_STRING devName;
RtlInitUnicodeString(&devName, L"\\Device\\DevB");
ntStatus = IoCreateDevice(pDriverObject, sizeof(DEVICE_EXTENSION), &devName, FILE_DEVICE_UNKNOWN, 0, TRUE, &pDevObj);
if (!NT_SUCCESS(ntStatus))
return ntStatus;
pDevObj->Flags |= DO_BUFFERED_IO;
pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
pDevExt->pDevice = pDevObj;
pDevExt->ustrDeviceName = devName;
RtlInitUnicodeString(&symLinkName, L"\\??\\DevB");
pDevExt->ustrSymLinkName = symLinkName;
NTSTATUS status = IoCreateSymbolicLink(&symLinkName, &devName);
if (!NT_SUCCESS(status)) {
IoDeleteDevice(pDevObj);
return status;
}
return STATUS_SUCCESS;
}
NTSTATUS ReadRoutine(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp) {
KdPrint(("DriverB: 进入ReadRoutine\n"));
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
OBJECT_ATTRIBUTES obj;
UNICODE_STRING ustrDevName;
HANDLE hDevA;
NTSTATUS status = STATUS_SUCCESS;
IO_STATUS_BLOCK IoStatus;
RtlInitUnicodeString(&ustrDevName, L"\\Device\\DevA"); // 获取DevA的设备名称
InitializeObjectAttributes(&obj, &ustrDevName, OBJ_CASE_INSENSITIVE, NULL, NULL);
status = ZwCreateFile(&hDevA, FILE_ALL_ACCESS | SYNCHRONIZE, &obj, &IoStatus, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
if (!NT_SUCCESS(status)) {
KdPrint(("打开设备A失败\n"));
return(status);
}
// 创建一个新的Read类型的IRP发送给底层驱动
status = ZwReadFile(hDevA, NULL, NULL, NULL, &IoStatus, NULL, 0, NULL, 0);
if (!NT_SUCCESS(status)) {
KdPrint(("读取设备A失败\n"));
return(status);
}
ZwClose(hDevA);
// 应用层发来的IRP被完成
KdPrint(("应用层发来的IRP: %08X\n"));
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
KdPrint(("DriverB: 离开ReadRoutine\n"));
return(status);
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath) {
KdPrint(("DriverB: 进入DriverEntry\n"));
pDriverObject->DriverUnload = Unload;
for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; ++i)
pDriverObject->MajorFunction[i] = DispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_READ] = ReadRoutine;
NTSTATUS status = CreateDevice(pDriverObject);
if (!NT_SUCCESS(status))
return(status);
KdPrint(("DriverB: 离开DriverEntry\n"));
return(STATUS_SUCCESS);
}
来看下应用层代码:
#include <windows.h>
#include <stdio.h>
int main() {
HANDLE hDevice = NULL;
hDevice = CreateFile("\\\\.\\DevB", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE == hDevice) {
printf("打开设备失败! %d\n", GetLastError());
return(-1);
}
DWORD dwRet;
ReadFile(hDevice, NULL, 0, &dwRet, NULL);
CloseHandle(hDevice);
return(0);
}
程序运行结果:
可以看得出来两个IRP是不一样的,因为中间驱动做的并不是直接转发IRP,而是完成应用层IRP后再重新创建一个新的IRP发送到底层设备
值得注意的是ZwCreateFile的参数:
ZwCreateFile(&hDevA, FILE_ALL_ACCESS | SYNCHRONIZE, &obj, &IoStatus, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
第二个参数要加上SYNCHRONIZE表示同步,倒数第三个参数必须指定为: FILE_SYNCHRONOUS_IO_NONALERT或者FILE_SYNCHRONOUS_IO_ALERT
2. 异步创建
第一种异步调用的方法是通过同步对象并设置APC例程成来达到的。来看一下具体的实现步骤
- 应用层发来Read类型的IRP请求到中间设备
- 中间设备通过ZwReadFile发送Read类型的异步IRP请求到底层设备,并对该新创建的IRP设置APC例程,并传入一个event同步对象
- 底层设备设置了等待3秒后开始的DPC例程,挂起IRP后返回STATUS_PENDING
- 中间设备收到了STATUS_PENDING后阻塞在KeWaitForSingleObject上
- 3秒后底层设备的DPC例程被触发,并在其中通过调用IoCompleteRoutine完成了中间设备传下来的IRP请求,中间设备的APC例程被触发
- 中间设备的APC例程中触发了同步事件对象后返回
- KeWaitForSingleObject的阻塞状态被解除并完成了应用层发来的IRP
其中最关键的因素在于底层设备ZwReadFile设置APC例程,APC例程触发同步事件。底层设备返回STATUS_PENDING并且DPC例程完成中间设备发来的IRP请求
由于底层设备和应用层的代码没有任何改变,这里仅仅给出中间设备代码修改过的代码,来看一下实现代码:
VOID ApcRoutine (
_In_ PVOID ApcContext,
_In_ PIO_STATUS_BLOCK IoStatusBlock,
_In_ ULONG Reserved
) {
KdPrint(("DriverB: 进入了APC例程\n"));
// 触发同步事件
KeSetEvent((PKEVENT)ApcContext, IO_NO_INCREMENT, FALSE);
KdPrint(("DriverB: 离开了APC例程\n"));
}
NTSTATUS ReadRoutine(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp) {
KdPrint(("DriverB: 进入ReadRoutine\n"));
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
OBJECT_ATTRIBUTES obj;
UNICODE_STRING ustrDevName;
HANDLE hDevA;
NTSTATUS status = STATUS_SUCCESS;
IO_STATUS_BLOCK IoStatus;
KEVENT event;
RtlInitUnicodeString(&ustrDevName, L"\\Device\\DevA"); // 获取DevA的设备名称
InitializeObjectAttributes(&obj, &ustrDevName, OBJ_CASE_INSENSITIVE, NULL, NULL);
KeInitializeEvent(&event, NotificationEvent, FALSE); // 初始化事件
// 没有FILE_SYNCHRONOUS_IO_NONALERT标志
status = ZwCreateFile(&hDevA, FILE_ALL_ACCESS, &obj, &IoStatus, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF, 0, NULL, 0);
if (!NT_SUCCESS(status)) {
KdPrint(("打开设备A失败%08X\n", status));
return(status);
}
LARGE_INTEGER li = RtlConvertLongToLargeInteger(0);
// 创建一个新的Read类型的IRP发送给底层驱动, 同时设置APC例程
status = ZwReadFile(hDevA, NULL, ApcRoutine, &event, &IoStatus, NULL, 0, &li, 0);
if (!NT_SUCCESS(status)) {
KdPrint(("读取设备A失败%08X\n", status));
return(status);
}
if (status == STATUS_PENDING) {
KdPrint(("底层IRP返回了STATUS_PENDING, 等待底层IRP被完成...\n"));
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
}
ZwClose(hDevA);
// 应用层发来的IRP被完成
KdPrint(("应用层发来的IRP: %08X\n"));
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
KdPrint(("DriverB: 离开ReadRoutine\n"));
return(status);
}
来看一下结果:
我认为最值得注意的是两个函数, ZwCreateFile和ZwReadFile:
status = ZwCreateFile(&hDevA, FILE_ALL_ACCESS, &obj, &IoStatus, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF, 0, NULL, 0);
ZwReadFile(hDevA, NULL, ApcRoutine, &event, &IoStatus, NULL, 0, &li, 0);
与同步方式不同的是
- ZwCreateFile不需要SYNCHRONIZE属性。代表异步。倒数第三个参数不需要FILE_SYNCHRONOUS_IO_NONALERT或FILE_SYNCHRONOUS_IO_ALERT
- ZwReadFile必须要填入倒数第二个参数,否则会失败
(完)