程式扎記: [ Windows DDP ] IRP 的同步 : 自訂的 StartIO 常式

標籤

2011年2月23日 星期三

[ Windows DDP ] IRP 的同步 : 自訂的 StartIO 常式


前言 :
系統定義的 StartIO 常式只能使用一個佇列, 這個佇列會將所有的 IRP 進行處理與分配. 這樣讀, 寫操作就會混在一起進行連續處理. 然而在某些情況下, 需要將讀, 寫分別進行序列化處理. 這時候就需要自訂 StartIO 常式.

多個序列化佇列 :
你可能會發現 StartIO 雖然可以很方便地將 IRP 序列化, 但是存在一個問題, 這就是讀寫操作都被一起序列化. 有時候需要將讀, 寫分開序列化, 便需要兩個佇列. 一個佇列序列 IRP_MJ_READ 類型 IRP, 另外一個佇列負責序列化 IRP_MJ_WRITE 類型的 IRP.
但是很遺憾是 DDK 提供的 StartIO 常式內部只有一個佇列, 無法實現上述的需求. 但是 DDK 提供了一種更靈活的方式 - 自訂 StartIO. 自訂 StartIO 類似於前面介紹的 StartIO 常式, 不同的是程式設計師要自己維護 IRP 佇列. 設計師可以靈活維護多個佇列, 分別應用於不同的類型的 IRP. DDK 提供 KDEVICE_QUEUE 資料結構儲存佇列 :
- Syntax :
  1. typedef struct _KDEVICE_QUEUE {    
  2.     CSHORT Type;    
  3.     CSHORT Size;    
  4.     LIST_ENTRY DeviceListHead;    
  5.     KSPIN_LOCK Lock;    
  6.     BOOLEAN Busy;    
  7. } KDEVICE_QUEUE, *PKDEVICE_QUEUE, *RESTRICTED_POINTER, PRKDEVICE_QUEUE;  

佇列中每個元素用 KDEVICE_QUEUE_ENTRY 資料結構表示 :
- Syntax :
  1. typedef struct _KDEVICE_QUEUE_ENTRY {  
  2.     LIST_ENTRY DeviceListEntry;  
  3.     ULONG SortKey;  
  4.     BOOLEAN Inserted;  
  5. } KDEVICE_QUEUE_ENTRY, *PKDEVICE_QUEUE_ENTRY, *RESTRICTED_POINTER, PRKDEVICE_QUEUE_ENTRY;  

在 StartIO 的內部也用到這種佇列. 但當使用 StartIO 時, 程式員不用關心佇列的 "入隊" 和 "出隊" 操作, 這些都由作業系統負責. 而當使用自訂 StartIO 時, 程式員需要自己負責 "入隊" 和 "出隊" 操作. 在使用佇列前, 應該初始化佇列, 用 KeInitializeDeviceQueue 函式初始化佇列. 佇列應該儲存在裝制擴充中, 在初始化裝置的時候一起初始化該佇列. 插入佇列的內核函式是 KeInsertDeviceQueue, 其宣告如下 :
- Syntax :
  1. BOOLEAN KeInsertDeviceQueue(  
  2.   __inout  PKDEVICE_QUEUE DeviceQueue,  
  3.   __inout  PKDEVICE_QUEUE_ENTRY DeviceQueueEntry  
  4. );  

參數說明 :
* 參數 DeviceQueue : 這個參數是需要被插入的佇列
* 參數 DeviceQueueEntry : 這個參數是要被插入的元素

從佇列中刪除元素使用 KeRemoveDeviceQueue 函式, 其宣告如下 :
- Syntax :
  1. PKDEVICE_QUEUE_ENTRY KeRemoveDeviceQueue(  
  2.   __inout  PKDEVICE_QUEUE DeviceQueue  
  3. );  

參數說明 :
* 參數DeviceQueue : 指定從哪個佇列中取出元素


示例 :
這裡將演式如何編寫自訂 StartIO 常式.
首先應該在裝置擴充中加入 KDEVICE_QUEUE 資料結構儲存佇列, 並且在 DriverEntry 中初始化該佇列. 如果程式設計師需要對讀和寫操作分別序列化, 可以在裝置擴充新建兩個佇列, 分別對應讀和寫. 這裡只示範一個佇列的做法, 讀者可以試著為自己的驅動加入更多的佇列.
接著是編寫派遣常式, 在派遣函式中首先用 IoMarkIrpPending 函式將該 IRP 設為 "Pending", 然後準備將 IRP 存入佇列. 在進入佇列前要先將當前 IRQL 提升至 DISPATCH_LEVEL 層級. 插入佇列使用 KeInsertDeviceQueue 函式, 該函式的返回值指示是否需要立即執行. 當該函式返回 FALSE 的時候, 表明 IRP 沒有插入到佇列, 而是需要被立刻執行. 這時候需要呼叫自訂的 StartIO 常式 :
- Driver.cpp (函式 HelloDDKRead) :
  1. NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,  
  2.                                  IN PIRP pIrp)   
  3. {  
  4.     KdPrint(("Enter HelloDDKRead\n"));  
  5.   
  6.     PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)  
  7.             pDevObj->DeviceExtension;  
  8.   
  9.     //將IRP設置為掛起  
  10.     IoMarkIrpPending(pIrp);  
  11.   
  12.     IoSetCancelRoutine(pIrp,OnCancelIRP);  
  13.       
  14.     KIRQL oldirql;  
  15.     //提升IRP至DISPATCH_LEVEL  
  16.     KeRaiseIrql(DISPATCH_LEVEL, &oldirql);  
  17.   
  18.     KdPrint(("HelloDDKRead irp :%x\n",pIrp));  
  19.   
  20.     KdPrint(("DeviceQueueEntry:%x\n",&pIrp->Tail.Overlay.DeviceQueueEntry));  
  21.     if (!KeInsertDeviceQueue(&pDevExt->device_queue, &pIrp->Tail.Overlay.DeviceQueueEntry))  
  22.         MyStartIo(pDevObj,pIrp);  
  23.   
  24.     //將IRP降至原來IRQL  
  25.     KeLowerIrql(oldirql);  
  26.   
  27.     KdPrint(("Leave HelloDDKRead\n"));  
  28.   
  29.     //返回pending狀態  
  30.     return STATUS_PENDING;  
  31. }  

接下來編寫自訂 StartIO 常式. 自訂 StartIO 的任務是首先處理傳進來的 IRP, 然後在枚舉佇列中的 IRP, 接著依次出佇列並對其進行處理 :
- Driver.cpp (函式 MyStartIo) :
  1. #pragma LOCKEDCODE  
  2. VOID  MyStartIo(IN PDEVICE_OBJECT  DeviceObject, IN PIRP pFistIrp)  
  3. {  
  4.     KdPrint(("Enter MyStartIo\n"));  
  5.   
  6.     PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)  
  7.         DeviceObject->DeviceExtension;  
  8.   
  9.     PKDEVICE_QUEUE_ENTRY device_entry;  
  10.   
  11.     PIRP Irp = pFistIrp;  
  12.     do  
  13.     {  
  14.         KEVENT event;  
  15.         KeInitializeEvent(&event,NotificationEvent,FALSE);  
  16.   
  17.         //等3秒  
  18.         LARGE_INTEGER timeout;  
  19.         timeout.QuadPart = -3*1000*1000*10;  
  20.   
  21.         //定義一個3秒的延時,主要是為了模擬該IRP操作需要大概3秒左右時間  
  22.         KeWaitForSingleObject(&event,Executive,KernelMode,FALSE,&timeout);  
  23.   
  24.         KdPrint(("Complete a irp:%x\n",Irp));  
  25.         Irp->IoStatus.Status = STATUS_SUCCESS;  
  26.         Irp->IoStatus.Information = 0;   // no bytes xfered  
  27.         IoCompleteRequest(Irp,IO_NO_INCREMENT);  
  28.   
  29.         device_entry=KeRemoveDeviceQueue(&pDevExt->device_queue);  
  30.         KdPrint(("device_entry:%x\n",device_entry));  
  31.         if (device_entry==NULL)  
  32.         {  
  33.             break;  
  34.         }  
  35.   
  36.         Irp = CONTAINING_RECORD(device_entry, IRP, Tail.Overlay.DeviceQueueEntry);  
  37.     }while(1);  
  38.   
  39.     KdPrint(("Leave MyStartIo\n"));  
  40. }  

底下為執行結果 :
This message was edited 5 times. Last update was at 17/01/2011 15:59:58

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!