本博文由CSDN部落客zuishikonghuan所作,版權歸zuishikonghuan所有,轉載請注明出處:http://blog.csdn.net/zuishikonghuan/article/details/51301922
在之前的博文中,我們對于IRP,都是同步完成的,但是 Windows 對異步操作很友好,我們來看看如何異步完成 IRP 。
在應用程式中異步通路裝置
在開始之前,我認為有必要提一句異步通路裝置。在之前的博文中,與驅動通信的代碼都是采用同步通路裝置的,其實所謂同步通路,是 Win32 子系統封裝了“等待”這一過程。 Win32API 會在内部建立事件,并在向裝置發送 I/O 請求後直接等待事件被完成,一旦驅動程式完成 I/O ,那麼就會激活事件,進而使 Win32API 中的等待狀态結束,線程恢複運作。(關于“事件”對象,見上一篇博文“核心中開啟多線程和同步對象”)
其實,這個等待操作我們可以自己來做,就像這樣:
#include "stdafx.h"
#include<Windows.h>
int _tmain(int argc, _TCHAR* argv[])
{
//如果 CreateFile 的第 6 個參數 dwFlagsAndAttributes 被指定為 FILE_FLAG_OVERLAPPED,
HANDLE handle = CreateFile(TEXT("\\\\.\\D:\\1.txt"), GENERIC_READ, , NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL, NULL);
if (handle == INVALID_HANDLE_VALUE){
MessageBoxA(, "打開檔案/裝置失敗", "錯誤", );
return ;
}
unsigned char buffer[] = { };
OVERLAPPED over = { };
HANDLE Event = CreateEvent(NULL, FALSE, FALSE, NULL);
over.hEvent = Event;
ReadFile(handle, buffer, , NULL, &over);
//Do somethings
WaitForSingleObject(Event, INFINITE);
for (int i = ; i < sizeof(buffer); i++)
printf("0x%X ", buffer[i]);
CloseHandle(handle);
getchar();
return ;
}
當然,還有一種方法異步通路裝置,即使用ReadFileEx/WriteFileEx,這是通過APC來實作異步通路的,這裡不展開了。
為什麼要說異步通路裝置呢,這是為了避免大家和下面的異步完成IRP産生混淆,同時也有必要讓大家明白下 Win32 子系統對同步讀寫裝置的實作,同時,了解異步通路裝置有助于下面的對派遣函數和IRP相關内容的了解。
要異步通路裝置,需要得到驅動程式的支援,當應用程式調用 I/O 函數時,驅動程式的 Dispatch Function 會被調用,當 Dispatch Function 傳回時,應用程式的 I/O 函數才能退出。驅動程式調用 IoMarkIrpPending 并傳回 STATUS_PENDING 時,意味着 I/O 請求已經挂起,如果此時應用程式選擇了異步通路裝置,那麼 I/O 函數會退出,當 I/O 真正處理完成時(即調用 IoCompleteRequest ),應用程式建立的 Event 就會被激發!WaitForSingleObject 會傳回。而同步通路,其實是在 I/O 函數内部建立并等待了這個事件。
異步完成 IRP
要異步完成IRP,需要在派遣函數中不調用 IoCompleteRequest ,而是調用 IoMarkIrpPending 函數,同時需要派遣函數傳回 STATUS_PENDING
通過上面的異步通路裝置我們可以發現,應用程式會建立一個 Event 對象,要使應用程式等待完成,需要激活這個事件,這個過程不需要驅動程式自己去做的,完成IRP時如果調用 IoCompleteRequest , IoCompleteRequest 會自動激活此事件。
也就是說,調用 IoMarkIrpPending 後,應用程式建立的事件并沒有被激活,即,如果應用程式等待此事件(如同步讀寫),并不會使其退出等待,他的作用僅僅是使派遣函數傳回,以便于驅動程式在其他地方完成 IRP 。
就像這樣,之後,此 IRP 被挂起,驅動程式可以儲存此 IRP 的指針,在未來某個必要的時刻完成他:
//in a Dispatch Function
IoMarkIrpPending(pIrp);
return STATUS_PENDING;
CancelRoutine 取消 I/O 例程
有些時候,驅動程式需要允許應用程式“取消”某個 I/O 請求。比如,應用程式程式異步讀寫檔案時可以實作一個“終止”按鈕。這需要驅動程式的支援。如果我們希望給自己的裝置實作這種功能,就需要給 IRP 設定取消例程。應用程式通過 Win32 子系統提供的 CancelIO API來取消一個 I/O 請求,這會調用驅動程式設定的取消例程。
設定取消例程的核心函數是 IoSetCancelRoutine,這個函數的原型如下:
PDRIVER_CANCEL IoSetCancelRoutine(
_In_ PIRP Irp,
_In_ PDRIVER_CANCEL CancelRoutine
);
- 參數1是要設定取消例程的 IRP 指針。
- 參數2是取消例程指針。如果此參數為 NULL ,則删除取消例程
傳回值:目前 IRP 存在取消例程時傳回 Irp->CancelRoutine ,否則傳回 NULL 。
取消例程的原型如下:
DRIVER_CANCEL Cancel;
VOID Cancel(
_Inout_ struct _DEVICE_OBJECT *DeviceObject,
_Inout_ struct _IRP *Irp
)
{ ... }
IoCancelIrp 會在内部調用 IoAcquireCancelSpinLock 擷取cancel自旋鎖,是以,在取消例程中,必須調用 IoReleaseCancelSpinLock 函數釋放自旋鎖,否則會導緻藍屏當機。
IRP 異步完成和 CancelRoutine 例程代碼
最後,我們以一個例程來結束本篇博文,此例中,我們在處理”讀”的派遣函數中挂起 IRP 來示範 IRP 異步完成,并設定 CancelRoutine ,并在取消例程中将挂起的 IRP 完成。
應用程式源碼:
#include "stdafx.h"
#include<Windows.h>
int _tmain(int argc, _TCHAR* argv[])
{
//打開裝置
HANDLE handle = CreateFileA("\\\\.\\MyDevice1_link", GENERIC_READ | GENERIC_WRITE, , NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL, NULL);
if (handle == INVALID_HANDLE_VALUE){
MessageBoxA(, "打開裝置失敗", "錯誤", );
return ;
}
unsigned char buffer[] = { };
DWORD len;
OVERLAPPED over = { };
HANDLE Event = CreateEvent(NULL, FALSE, FALSE, NULL);
over.hEvent = Event;
if (!ReadFile(handle, buffer, , &len, &over)){
if (GetLastError() == ERROR_IO_PENDING){
puts("I/O is Pending");
}
}
Sleep();
CancelIo(handle);
CloseHandle(handle);
return ;
}
驅動程式源碼:
#include <ntddk.h>
extern "C" VOID DriverUnload(PDRIVER_OBJECT pDriverObject);
extern "C" NTSTATUS DefDispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp);
extern "C" NTSTATUS ReadDispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp);
//我們定義的裝置擴充
typedef struct _DEVICE_EXTENSION {
UNICODE_STRING SymLinkName;//符号連結名
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
#pragma code_seg("INIT")
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] = ReadDispatchRoutine;
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, , TRUE, &pDevObj);
if (!NT_SUCCESS(status))
return status;
pDevObj->Flags |= DO_BUFFERED_IO;//将裝置設定為緩沖裝置
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("DefDispatchRoutine\r\n");
NTSTATUS status = STATUS_SUCCESS;
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = ;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return status;
}
VOID Read_CancelIRP(PDEVICE_OBJECT DeviceObject, PIRP pIrp)
{
DbgPrint("Read_CancelIRP pIrp: 0x%X\r\n", pIrp);
//完成狀态設定為 STATUS_CANCELLED
pIrp->IoStatus.Status = STATUS_CANCELLED;
//操作位元組數
pIrp->IoStatus.Information = ;
//完成 IRP
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
//釋放 Cancel 自旋鎖
IoReleaseCancelSpinLock(pIrp->CancelIrql);
}
extern "C" NTSTATUS ReadDispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
DbgPrint("ReadDispatchRoutine\r\n");
NTSTATUS status = STATUS_SUCCESS;
//得到裝置擴充
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
//得到I/O堆棧的目前這一層,也就是IO_STACK_LOCATION結構的指針
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
//ULONG ReadLength = stack->Parameters.Read.Length;//得到讀的長度
//ULONG ReadOffset = (ULONG)stack->Parameters.Read.ByteOffset.QuadPart;//得到讀偏移量
//DbgPrint("ReadLength: %d\r\nReadOffset: %d\r\n", ReadLength, ReadOffset);//輸出相關資訊
//PVOID buffer = pIrp->AssociatedIrp.SystemBuffer;//得到緩沖區指針
//if (ReadOffset + ReadLength > BUFFER_LENGTH){
// //如果要操作的超出了緩沖區,則失敗完成IRP,傳回無效
// DbgPrint("E: The size of the data is too long.\r\n");
// status = STATUS_FILE_INVALID;//會設定使用者模式下的GetLastError
// ReadLength = 0;//設定操作了0位元組
//}
//else{
// //沒有超出,則進行緩沖區複制
// DbgPrint("OK, I will copy the buffer.\r\n");
// RtlMoveMemory(buffer, pDevExt->buffer + ReadOffset, ReadLength);
// status = STATUS_SUCCESS;
//}
IoSetCancelRoutine(pIrp, Read_CancelIRP);
IoMarkIrpPending(pIrp);
DbgPrint("IoMarkIrpPending pIrp: 0x%X\r\n", pIrp);
return STATUS_PENDING;
//pIrp->IoStatus.Status = status;//設定IRP完成狀态,會設定使用者模式下的GetLastError
//pIrp->IoStatus.Information = ReadLength;//設定操作位元組數
//IoCompleteRequest(pIrp, IO_NO_INCREMENT);//完成IRP
//return status;
}
效果圖: