Windows核心-7-(IRP)I/O請求包

IRP(I/O Request Packet)就是一個進行I/O操作的請求包。

IRP是一個結構體,誰分配誰就得釋放,通常由執行體裡的管理器,獲取核心驅動程式來分配。

IRP不是單獨的,只要建立了IRP就會跟著建立IRP的I/O棧,有一個棧是給核心驅動用的:

驅動需要呼叫IoGetCurrentIrpStackLocation函式來獲取內驅驅動對應的I/O棧。

I/O傳遞

I/O系統是以裝置物件為中心,而不是以驅動物件為中心的。IRP可以在裝置物件中傳來傳去:

有點類似於計算機網路的體系結構。但是不可否認的是I/O可以在裝置直接進行各種傳遞,所以通常用來作為讀取文字資料。

具體的傳遞過程有點類似於棧。

IRP和I/O棧

這張圖是對前面第一張圖的細化。

分發例程(dispatch routine)

IRP最主要的一個作用就是拿來做分發例程,其實也就是分發的例項函式,就是對檔案資料的傳遞。在我的理解裡,就是對資訊的分發,讓大家一起用到資料。

所有的分發例程都有一個原型:

typedef NTSTATUS DRIVER_DISPATCH (
_In_ PDEVICE_OBJECT DeviceObject,
_Inout_ PIRP Irp);

分發例程必須儲存在驅動物件的MajorFunction裡面,且需要對應和驅動的連線狀態:

狀態 描述
IRP_MJ_CREATE 獲取檔案控制代碼,對應User下的CreateFile
IRP_MJ_CLOSE 關閉檔案控制代碼,對應User下的CloseHandle
IRP_MJ_READ 對應ReadFile
IRP_MJ_WRITE 對應WriteFile
IRP_MJ_DEVICE_CONTROL 對應DeviceIoControl,即可讀又可寫
IRP_MJ_INTERNAL_DEVICE_CONTROL 和IRP_MJ_DEVICE_CONTROL差不多,但是隻有Kernel可用

完成請求

一旦決定要處理IRP(意味著不會把該IRP傳遞給下一個裝置物件),就必須要完成它。

比如:

NTSTATUS MyDispatchRoutine(PDEVICE_OBJECT, PIRP Irp) {
//...
Irp->IoStatus.Status = STATUS_XXX;
Irp->IoStatus.Information = NumberOfBytesTransfered; // depends on request type
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_XXX;
}
//這裡的IoCompleteRequest就是必須的

安全IRP操作

由於執行緒原因,很有可能一個去讀一個去寫,導致兩個只有一個拿到了想要的資料。

還有頁面記憶體的原因,修改的時候有可能一起修改了。

核心提供了兩種比較安全的處理方式:

1 緩衝I/O

2 直接I/O

緩衝I/O

要使用緩衝I/O只需要在裝置物件的Flag欄位裡面新增一個DO_BUFFERED_IO

DeviceObject->Flags |= DO_BUFFERED_IO

緩衝I/O的原理就是在系統記憶體中開闢一段緩衝區,作為一箇中間人。

比如說要寫,就先把內容寫到系統記憶體緩衝區裡,然後系統記憶體緩衝區再寫入/讀取到緩衝區裡面。

直接I/O

直接緩衝採用實體記憶體對映來處理,將User和Kernel的緩衝區都指向實體記憶體裡面,並且鎖定在記憶體裡,然後讀寫都採用它。這樣的好處是減少了呼叫的開銷。但是佔了部分記憶體。

IRP_MJ_DEVICE_CONTROL 排程

前面的緩衝和直接I/O是讀或寫操作,這裡的IRP_MJ_DEVICE_CONTROL操作是基於控制程式碼來選擇讀或寫兩個都可以的。

User態的IRP_MJ_DEVICE_CONTROL 和Kernel態的呼叫函式原型差不多:

BOOL DeviceIoControl(
HANDLE       hDevice,
DWORD       dwIoControlCode,
LPVOID       lpInBuffer,
DWORD       nInBufferSize,
LPVOID       lpOutBuffer,
DWORD       nOutBufferSize,
LPDWORD     lpBytesReturned,
LPOVERLAPPED lpOverlapped
);

dwIoControlCode:控制程式碼,由CTL_CODE巨集的四個引數來處理。

Zero驅動程式

Client+Kernel的一個練手專案:

對於讀請求,會將提供的緩衝區清零然後輸出

對於寫請求,會把緩衝區消耗掉

原始碼zodiacon/windowskernelprogrammingbook: The Windows Kernel Programming book samples (github.com)