天天看點

驅動開發(13)IRP 的異步完成和 CancelRoutine

本博文由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. 參數1是要設定取消例程的 IRP 指針。
  2. 參數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;
}
           

效果圖:

驅動開發(13)IRP 的異步完成和 CancelRoutine

繼續閱讀