首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> C++ 入門

Windows 驅動開發:IRP 分發函數與三種緩衝區模式深度解析

     編輯:C++ 入門

IRP 與分發函數核心機制

IRP (I/O Request Packet) 是用戶態與內核態通信的載體。驅動在 DriverEntry 中向 DRIVER_OBJECT->MajorFunction[IRP_MJ_*] 註冊分發函數,I/O 管理器根據 MajorFunction 將 IRP 派遣到對應處理例程。

常見 IRP 類型對照表

IRP 類型觸發源 (Win32 API)說明
IRP_MJ_CREATECreateFile打開設備/文件
IRP_MJ_CLOSECloseHandle關閉句柄
IRP_MJ_READReadFile讀取數據
IRP_MJ_WRITEWriteFile寫入數據
IRP_MJ_DEVICE_CONTROLDeviceIoControl控制碼操作
IRP_MJ_CLEANUPCloseHandle清理上下文(最後一個句柄關閉前)
IRP_MJ_PNP系統即插即用(WDM 支持)
IRP_MJ_POWER系統電源管理
IRP_MJ_SHUTDOWN系統系統關機通知

最簡分發函數:直接完成 IRP


NTSTATUS DispatchRoutine(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
    Irp->IoStatus.Status = STATUS_SUCCESS;
    Irp->IoStatus.Information = 0;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}

通用分發函數:識別 IRP 類型並日志


NTSTATUS DispatchRoutine(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
    PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
    static const char* irpNames[] = {
        "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(irpNames))
        DbgPrint("IRP: %s\n", irpNames[type]);
    else
        DbgPrint("IRP: Unknown 0x%X\n", type);
    
    Irp->IoStatus.Status = STATUS_SUCCESS;
    Irp->IoStatus.Information = 0;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}

用戶態打開設備:符號鏈接

內核設備名(如 \Device\MyDDKDevice)不可被用戶態直接訪問,需創建符號鏈接(如 \DosDevices\HelloDDK),用戶態通過 CreateFile("\\\\.\\HelloDDK", ...) 打開。

三種緩衝區數據傳輸模式

設備對象 Flags 決定模式:

  • DO_BUFFERED_IO:緩衝區模式
  • DO_DIRECT_IO:直接模式
  • 0:既不緩衝也不直接(少用)

模式一:緩衝區模式 (Buffered I/O)

原理:系統將用戶緩衝區數據拷貝到內核分配的系統緩衝區 (Irp->AssociatedIrp.SystemBuffer),驅動操作系統緩衝區,完成後再拷回用戶空間。

優點:內核地址穩定,無需鎖定頁面,編程簡單

缺點:雙次拷貝,大數據傳輸效率低

讀操作示例


NTSTATUS ReadBuffered(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
    PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
    ULONG readLen = stack->Parameters.Read.Length;
    
    // 填充系統緩衝區
    memset(Irp->AssociatedIrp.SystemBuffer, 0xAA, readLen);
    
    Irp->IoStatus.Status = STATUS_SUCCESS;
    Irp->IoStatus.Information = readLen; // 實際傳輸字節數
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}

模式二:直接模式 (Direct I/O)

原理:系統鎖定用戶緩衝區物理頁面,建立 MDL (Memory Descriptor List) 映射到內核地址空間。用戶緩衝區與內核映射地址指向同一物理內存,零拷貝。

關鍵結構:Irp->MdlAddress 指向 MDL

MDL 關鍵宏:


MmGetMdlByteCount(Mdl)      // 緩衝區總字節數
MmGetMdlByteOffset(Mdl)     // 起始偏移
MmGetMdlVirtualAddress(Mdl) // 內核映射起始虛擬地址
MmGetSystemAddressForMdlSafe(Mdl, Priority) // 獲取安全內核地址

讀操作示例


NTSTATUS ReadDirect(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
    PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
    ULONG readLen = stack->Parameters.Read.Length;
    
    ULONG mdlLen = MmGetMdlByteCount(Irp->MdlAddress);
    if (mdlLen != readLen) {
        Irp->IoStatus.Information = 0;
        Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
    } else {
        PVOID kernelAddr = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
        memset(kernelAddr, 0xAA, readLen);
        Irp->IoStatus.Information = readLen;
        Irp->IoStatus.Status = STATUS_SUCCESS;
    }
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}

模式三:既不緩衝也不直接 (Neither I/O)

驅動直接使用用戶態虛擬地址 (Irp->UserBuffer),需自行處理地址有效性、頁面鎖定、異常捕獲。極不推薦,除非極特殊場景。

寫操作狀態機 (Direct I/O 核心邏輯)

Write 狀態機


// 發送邏輯核心
NTSTATUS SendImpl(const Buffer& data) {
    if (hasPendingData() || !checkPreSend()) { // 有未發數據 或 發送緩衝區已滿
        appendToPendingList(data);             // 掛入應用層待發隊列
        return STATUS_PENDING;
    }
    
    // 直接發送
    ULONG sent = WriteToSocket(data);
    if (sent < data.size() || GetLastError() == ERROR_IO_PENDING) {
        appendToPendingList(data.substr(sent)); // 剩餘數據入隊
        return STATUS_PENDING;
    }
    handleWriteCompleted(sent);
    return STATUS_SUCCESS;
}

// EPoll 通知可寫時
void OnWriteReady() {
    while (hasPendingData()) {
        auto& front = pendingQueue.front();
        ULONG sent = WriteToSocket(front);
        if (sent < front.size()) {
            front.erase(0, sent); // 部分發送,保留剩餘
            break; // 緩衝區滿,等待下次通知
        }
        pendingQueue.pop(); // 完全發送
    }
}

監聽 Socket 處理要點 (ET 模式)

  • 忽略 Write 事件
  • Read 事件:循環 accept() 直到返回 EWOULDBLOCK
  • Error 事件:過濾可接受錯誤 (EMFILE, ENFILE, ECONNABORTED 等)

總結

IRP 分發函數是驅動處理 I/O 的入口,掌握 MajorFunction 映射與 IO_STACK_LOCATION 使用。
緩衝區模式選擇:小數據/控制路徑用 Buffered I/O;大數據/數據路徑用 Direct I/O (MDL 零拷貝)。
Direct I/O 寫路徑:應用層待發隊列 + 發送緩衝區狀態機,EPoll 邊緣觸發模式最清晰。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved