驅動程序的主要功能是用來處理IO請求,而大部分的IO請求是在派遣函數中完成的,用戶模式下所有的IO請求都會被IO管理器封裝為一個IRP結構,類似于Windows窗口程序中的消息,不同的IRP被發送到不同的派遣函數中處理
IRP(I/O Request Package)輸入輸出請求包,IRP的兩個最基本的結構是MajorFunction和MinorFunction,分別記錄IRP的主要類型和子類型,它們是一組函數指針數組,不同的項紀錄的是處理當前請求的回調函數,可以在這些派遣函數中繼續通過MinorFunction來判斷
每個驅動都有一個唯一的DRIVER_OBJECT結構,這個結構中有一個MajorFunction數組,通過這個數組可以將IRP與處理它的派遣函數關聯起來,當應用層有一個針對于某個設備對象的I/O請求時,會根據這個設備對象所在驅動找到對應的MajorFunction結構,再根據請求類型來找到它對應的處理函數。
與應用層中有不同的消息類型,系統會根據消息類型調用具體消息處理函數類似,IRP也有不同的類型,在應用層調用不同的函數時會產生不同的IRP類型,例如調用應用層函數CreateFile或者內核函數ZwCreateFile會產生IRP_MJ_CREATE類型的IRP。下面是不同操作所對應產生的IRP請求列表
| IRP類型 | 來源 |
|---|---|
| IRP_MJ_CREATE | 創建設備,CreateFile會產生此IRP |
| IRP_MJ_CLOSE | 關閉設備,CloseHandle會產生此IRP |
| IRP_MJ_CLEANUP | 清除工作,CloseHandle會產生此IRP |
| IRP_MJ_DEVICE_CONTROL | DeviceIoControl函數會產生此IRP |
| IRP_MJ_PNP | 即插即用消息,NT驅動不支持此中IRP,只有WDM驅動才支持此中驅動 |
| IRP_MJ_POWER | 在操作系統處理電源消息時會產生此IRP |
| IRP_MJ_QUERY_INFORMATION | 獲取文件長度,GetFileSize會產生此IRP |
| IRP_MJ_READ | 讀取設備內容,ReadFile會產生此IRP |
| IRP_MJ_SET_INFORMATION | 設置文件長度,SetFileSize |
| IRP_MJ_SHUTDOWN | 關閉系統前會產生此IRP |
| IRP_MJ_SYSTEM_CONTROL | 系統內部產生控制信息,蕾西與調用DeviceIoControl函數 |
| IRP_MJ_WRITE | 對設備進行WriteFile時會產生此IRP |
大部分的I/O請求都來自于應用層調用相應的API對設備進行I/O操作類似于CreateFile、ReadFile等函數產生,最簡單的做法是將IRP設置為成功,然后結束IRP請求,并讓派遣函數返回成功,結束這個IRP調用函數IoCompleteRequest。
VOID IoCompleteRequest( IN PIRP Irp, //代表要結束的IRP IN CCHAR PRiorityBoost//代筆線程恢復時的優先級別 );其實當應用層調用相關函數進行I/O操作時,會陷入睡眠或者阻塞狀態,等待派遣函數成功返回,當派遣函數返回時會喚醒之前的等待線程,而第二個參數就是制定這個被喚醒的線程以何種優先級別運行。一般設置為IO_NO_INCREMENT表示不增加優先級,對于鍵盤,或者鼠標一類的需要更快的相應,這個時候可以設置為IO_MOUSE_INCREMENT 或者IO_KEYBOARD_INCREMENT下面是完成優先級的一個表 
在應用層一般通過設備名稱打開驅動中的設備對象,設備名稱一般只能在內核層使用,應用層能看到的是設備的符號鏈接名,符號鏈接名一般以”/??/”開頭,在應用層的寫法有些不同,應用層設備的符號鏈接名稱以“/./開頭”,因此在內核層符號鏈接為:”/??/HelloDevice”到了應用層則應該寫為”/./HelloDevice”。
驅動對象會創建多個設備對象,并將這些設備對象疊成一個垂直的結構,這種垂直結構被稱作設備棧,IRP請求首先被發往設備棧上的頂層設備上,如果這個設備不處理可以將它下發到下層的設備對象中,直到某個設備結束這個IRP請求。為了記錄IRP在每層設備中做的操作,IRP中有一個IO_STACK_LOCATION數組,這個數組對應于設備棧中各個設備對IRP所做的操作。在本層的設備中可以使用函數IoGetCurrentIrpStackLocation得到本層設備對應的IO_STACK_LOCATION結構,下面是它對應的結構圖 
在調用IoCreateDeivce函數完成設備對象的創建之后,需要設置該設備對象的緩沖區讀寫方式,這個值是由DEVICE_OBJECT中的Flag來設置,主要有三種DO_DIRECT_IO、DO_BUFFERED_IO 、0。 應用層在對設備進行讀寫操作時,會提供一個緩沖區用于保存需要傳入到設備對象或者保存由設備對象傳入的數據,Flag值規定就規定了設備對象是如何使用這個緩沖區的。 1. DO_DIRECT_IO:內核直接通過地址映射的方式將那塊緩沖區映射為內核地址,然后在驅動中使用。當使用這種方式時內核可以在IO_STACK_LOCATION結構中的MdlAddress拿到這塊內存,通過函數MmGetSystemAddressFromMdlSafe傳入MdlAddress值可以得到應用層傳下來的緩沖區地址 2. DO_BUFFERED_IO:內核會在內核的地址空間空另外開辟一段內存,將緩沖區的數據簡單拷貝到這個新開辟的空間中。通過這種方式的讀寫可以在IRP結構的AssociatedIrp.SystemBuffer中獲取。 3. 0:內核直接使用應用層的地址,對那塊內存進行操作,這種方式是十分危險的,如果進行線程切換,這個地址就不一定指向之前的內存,這樣就可能造成系統崩潰藍屏。這種方式可以通過IRP中的UserBuffer拿到緩沖區地址 另外緩沖區的長度可以通過IO_STACK_LOCATION中的Parameters.Read.Length和Parameters.WriteLength分別獲取讀寫緩沖區的長度
這是一個應用層的API函數,用于向驅動發送控制碼,在驅動中,根據控制嗎的不同而采用不同的處理方式進行處理,應用層可以通過后面幾個參數實現與驅動的數據共享。 控制碼采用宏CTL_CODE來定義
#define CTL_CODE(DeviceType, Function, Method, access)這個宏有四個參數,第一個是設備對象的類型,就是在調用IoCreateDevice創建設備對象時傳入的DeviceType的值,第二個參數是設備對象的控制碼,為了與系統定義的區分開,一般用戶自定義的取值在0x800之上。第三個參數是操作模式,主要有這樣幾個值:METHOD_BUFFERED、METHOD_IN_DIRECT、METHOD_OUT_DIRECT、METHOD_NEITHER,這些值主要針對的是設備對象的三種緩沖區的讀寫方式。第四個參數是訪問權限,一般給FILL_ANY_ACCESS; 這個函數在使用的時候需要注意下面幾點: 1. 這個函數是在應用層調用,所以必須在調用這個函數前使用CreateFile打開這個設備對象。在調用CreateFile時會向I/O管理器發送一個Create請求,這個請求被I/O管理器包裝成IRP,這個IRP的類型為IRP_MJ_CREATE,I/O管理器需要根據驅動的返回值來判斷怎么處理這個請求,只有當驅動向I/O管理器返回一個成功的時候才會為其分配句柄,所以驅動中需要自己實現Create的分發派遣函數。 2. 驅動中需要自定義一個分發函數用于處理這個IOControl發下來的信息,函數中可以從IO_STACK_LOCATION結構中的Parameters.DeviceIoControl.IoControlCode獲得用戶層傳下來的控制碼 3. 默認情況下我們會在結束IOControl這個IRP的時候會給定一個返回長度為0,這個時候I/O管理器會將這個值回填到DeviceIoControl函數中的倒數第二個參數中,因此DeviceIoControl的這個參數不能為NULL,否則會造成程序崩潰
新聞熱點
疑難解答