驱动中同步与异步发送IRP

1. 同步


同步创建方式的意思是应用层必须要等待IRP被IoCompleteRequest后才会返回。其必须是同步的。即使底层设备对收到的IRP进行挂起处理,那也得无限等待下去。看一下下面的例子:

该例子主要是进行了以下步骤:

  1. 应用层程序发送了Read类型的IRP给中间设备
  2. 中间设备捕获IRP后完成该IRP并调用ZwReadFile重新创建了一个IRP给底层设备
  3. 底层设备挂起该IRP并设置DPC例程来处理该被挂起的IRP
  4. 底层设备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例程成来达到的。来看一下具体的实现步骤

  1. 应用层发来Read类型的IRP请求到中间设备
  2. 中间设备通过ZwReadFile发送Read类型的异步IRP请求到底层设备,并对该新创建的IRP设置APC例程,并传入一个event同步对象
  3. 底层设备设置了等待3秒后开始的DPC例程,挂起IRP后返回STATUS_PENDING
  4. 中间设备收到了STATUS_PENDING后阻塞在KeWaitForSingleObject上
  5. 3秒后底层设备的DPC例程被触发,并在其中通过调用IoCompleteRoutine完成了中间设备传下来的IRP请求,中间设备的APC例程被触发
  6. 中间设备的APC例程中触发了同步事件对象后返回
  7. 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必须要填入倒数第二个参数,否则会失败

(完)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值