IRP (I/O Request Packet) 是用戶態與內核態通信的載體。驅動在 DriverEntry 中向 DRIVER_OBJECT->MajorFunction[IRP_MJ_*] 註冊分發函數,I/O 管理器根據 MajorFunction 將 IRP 派遣到對應處理例程。
| IRP 類型 | 觸發源 (Win32 API) | 說明 |
|---|---|---|
| IRP_MJ_CREATE | CreateFile | 打開設備/文件 |
| IRP_MJ_CLOSE | CloseHandle | 關閉句柄 |
| IRP_MJ_READ | ReadFile | 讀取數據 |
| IRP_MJ_WRITE | WriteFile | 寫入數據 |
| IRP_MJ_DEVICE_CONTROL | DeviceIoControl | 控制碼操作 |
| IRP_MJ_CLEANUP | CloseHandle | 清理上下文(最後一個句柄關閉前) |
| IRP_MJ_PNP | 系統 | 即插即用(WDM 支持) |
| IRP_MJ_POWER | 系統 | 電源管理 |
| IRP_MJ_SHUTDOWN | 系統 | 系統關機通知 |
NTSTATUS DispatchRoutine(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
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:既不緩衝也不直接(少用)原理:系統將用戶緩衝區數據拷貝到內核分配的系統緩衝區 (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;
}
原理:系統鎖定用戶緩衝區物理頁面,建立 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;
}
驅動直接使用用戶態虛擬地址 (Irp->UserBuffer),需自行處理地址有效性、頁面鎖定、異常捕獲。極不推薦,除非極特殊場景。

// 發送邏輯核心
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(); // 完全發送
}
}
accept() 直到返回 EWOULDBLOCKEMFILE, ENFILE, ECONNABORTED 等)IRP 分發函數是驅動處理 I/O 的入口,掌握 MajorFunction 映射與 IO_STACK_LOCATION 使用。
緩衝區模式選擇:小數據/控制路徑用 Buffered I/O;大數據/數據路徑用 Direct I/O (MDL 零拷貝)。
Direct I/O 寫路徑:應用層待發隊列 + 發送緩衝區狀態機,EPoll 邊緣觸發模式最清晰。