前言 :
StartIO 常式能夠保證各個並行的 IRP 循序執行, 級序列化.
並行執行與序列執行 :
在很多情況下, 對裝置的操作必須是序列執行的, 而不能並存執行. 例如對序列埠的操作, 假如有 N 個執行緒同時操作序列埠裝置時, 必須將這些操作排隊, 然後一一進行處理. 如果不做序列化處理, 當一個操作沒有完畢時, 新的操作又開始, 這必然會導致操作的混亂.
因此驅動程式有必要將並行的請求變成序列的請求, 這需要用到佇列, 如下圖所示. 假如有 8 個讀請求先後來到, 其中每個方塊代表一個請求. 每個方塊的左邊代表發出請求的時間, 寬度代表這個操作所需要的時間 :
可以想像如果不做處理, 派遣函式的執行將會交織再一起, 也就是並行執行. 如果想依次處理每個 IRP, 必須採用佇列將處理序列化. 採用的原則是 "先來先服務". 如下圖所示, 這是排隊以後的請求 :
當一個新的 IRP 請求來臨時, 首先檢查裝置是否處於"忙" 狀態. 裝置在初始化的時候為 "空閒". 當裝置處在 "空閒" 的時候, 可以處理一個 IRP 請求, 並改變當前裝置狀態為 "忙". 如果裝置處於 "忙" 狀態, 則將新來的 IRP 插入佇列, 並立刻返回, IRP 留在以後處理.
當裝置狀態由 "忙" 轉入 "空閒" 狀態時, 則從佇列取出一個 IRP 進行處理, 並重新將狀態變為 "忙". 這樣周而復始地將所有 IRP 的請求序列都處理.
StartIO 常式 :
作業系統為程式設計師提供了一個 IRP 佇列來實現佇列, 這個佇列用 KDEVICE_QUEUE 資料結構表示 :
這個佇列的佇列頭保存在裝置物件的 DeviceObject->DeviceQueue 子欄位中. 插入與刪除佇列中的元素都是作業系統負責的. 在使用這個佇列時後, 需要向系統提供一個叫做 StartIo的常式, 並將這個常式的函式名稱傳送給系統, 宣告如下 :
而你會在 DriverEntry 將這個 StartIo 常式傳遞給 DeviceObject, 程式碼如下 :
這個 StartIO 常式執行在 DISPATCH_LEVEL 層級, 因此這個函式是不會被執行緒所打斷的. StartIO 常式的參數類似於派遣函式, 只不過沒有返回值. 注意 StartIO 是執行在 DISPATCH_LEVEL 層級上, 因此在宣告時要加上 #pragma LOCKEDCODE 修飾符. 派遣函式如果想把 IRP 序列化, 只要加入 IoStartPacket 函式. 就可以將 IRP 插入佇列了. 並且 IoStartPacket 函式還可以讓程式設計師取消常式. IoStartPacket 首先判斷當前裝置是 "忙" 還是 "空閒". 如果裝置 "空閒", 則提升當前 IRQL 到 DISPATCH_LEVEL 層級, 並進入 StartIO 常式 "序列" 處理該 IRP 請求. 如果裝置 "忙", 則將 IRP 插入佇列後返回. 以下是 IoStartPacket 的偽程式碼 :
在 StartIO 常式結束前, 應該呼叫 IoStartNextPacket 函式, 其作用是從佇列中抽取下一個 IRP, 並將這個 IRP 作為參數呼叫 StartIO 常式. 以下是 IoStartNextPacket 的示例程式碼 :
從上述程式碼可以看出, 在呼叫 StartIO 之前, 作業系統會將裝置的 device->CurrentIrp 設置為當前 IRP, 這意味這個 IRP 正準備由 StartIO 常式處理. 當處理 StartIO 常式時, 最複雜的莫過於對取消常式的處理, 正確使用 cancel 自旋鎖是關鍵. 在 StartIO 常式的開始處, 應首先獲得 cancel 自旋鎖. 然後判斷當前的 IRP 是否等於 DeviceObject->CurrentIrp. 如果是以上的情況, 說明這個 IRP 正在或者即將被 StartIO 處理. StartIO 常式應立刻釋放自旋鎖, 什麼也不做立刻退出.
示例 :
在使用 StartIO 常式時, 需要 IRP 的派遣函式返回 "Pending" 狀態, 然後呼叫 IoStartPacket 內核函式. 下面的程式碼演示了如何完成 :
在派遣函式中呼叫 IoStartPacket 內核函式指定取消常式. 下面程式碼演示如何編寫取消常式 :
接著編寫 StartIO 常式, 注意 StartIO 執行在 DISPATCH_LEVEL 層級, 因此不能使用分頁記憶體, 否則會引起頁故障, 從而導致系統崩潰. 下面為演示程式碼 :
最後編寫應用程式的程式碼. 這段程式碼首先打開裝置, 然後新建兩個執行緒, 每個執行緒都是非同步讀取. 為了模擬真實的情況, 這裡在 StartIO 中停頓了 3 秒鐘. 應用程式同步的發起兩個執行緒同時讀取裝置, 從而真實模擬了同步發起 IRP 請求 :
執行結果如下圖所示 :
StartIO 常式能夠保證各個並行的 IRP 循序執行, 級序列化.
並行執行與序列執行 :
在很多情況下, 對裝置的操作必須是序列執行的, 而不能並存執行. 例如對序列埠的操作, 假如有 N 個執行緒同時操作序列埠裝置時, 必須將這些操作排隊, 然後一一進行處理. 如果不做序列化處理, 當一個操作沒有完畢時, 新的操作又開始, 這必然會導致操作的混亂.
因此驅動程式有必要將並行的請求變成序列的請求, 這需要用到佇列, 如下圖所示. 假如有 8 個讀請求先後來到, 其中每個方塊代表一個請求. 每個方塊的左邊代表發出請求的時間, 寬度代表這個操作所需要的時間 :
可以想像如果不做處理, 派遣函式的執行將會交織再一起, 也就是並行執行. 如果想依次處理每個 IRP, 必須採用佇列將處理序列化. 採用的原則是 "先來先服務". 如下圖所示, 這是排隊以後的請求 :
當一個新的 IRP 請求來臨時, 首先檢查裝置是否處於"忙" 狀態. 裝置在初始化的時候為 "空閒". 當裝置處在 "空閒" 的時候, 可以處理一個 IRP 請求, 並改變當前裝置狀態為 "忙". 如果裝置處於 "忙" 狀態, 則將新來的 IRP 插入佇列, 並立刻返回, IRP 留在以後處理.
當裝置狀態由 "忙" 轉入 "空閒" 狀態時, 則從佇列取出一個 IRP 進行處理, 並重新將狀態變為 "忙". 這樣周而復始地將所有 IRP 的請求序列都處理.
StartIO 常式 :
作業系統為程式設計師提供了一個 IRP 佇列來實現佇列, 這個佇列用 KDEVICE_QUEUE 資料結構表示 :
這個佇列的佇列頭保存在裝置物件的 DeviceObject->DeviceQueue 子欄位中. 插入與刪除佇列中的元素都是作業系統負責的. 在使用這個佇列時後, 需要向系統提供一個叫做 StartIo的常式, 並將這個常式的函式名稱傳送給系統, 宣告如下 :
而你會在 DriverEntry 將這個 StartIo 常式傳遞給 DeviceObject, 程式碼如下 :
- extern "C" NTSTATUS DriverEntry (
- IN PDEVICE_OBJECT pDriverObject,
- IN PUNICODE_STRING pRegistryPath
- )
- {
- ...
- // 設置 StartIO 常式
- pDeviceObject->DriverStartIo = HelloDDKStartIO;
- ...
- }
- VOID IoStartPacket(PDEVICE_OBJECT device, PIRP Irp, PULONG key, PDEVICE_CANCEL cancel)
- {
- KIRQL oldirql;
- // 獲得自旋鎖
- IoAcquireCancelSpinLock(&oldirql);
- // 設置取消常式
- IoSetCancelRoutine(Irp, cancel);
- // 設置 IRP
- device->CurrentIrp = Irp;
- // 釋放自旋鎖
- IoReleaseCancelSpinLock(oldirql);
- // 呼叫 StartIo 常式
- device->DriverObject->DriverStartIo(device, Irp);
- }
- VOID IoStartNextPacket(PDEVICE_OBJECT device, BOOLEAN cancel)
- {
- KIRQL oldirql;
- if(cancel)
- // 獲取自旋鎖
- IoAcquireCancelSpinLock(&oldirql);
- // 刪除裝置佇列
- PKDEVICE_QUEUE_ENTRY p = KeRemoveDeviceQueue(&device->DeviceQueue);
- // 獲取 IRP 指標
- PIRP Irp = CONTAINING_RECORD(p, IRP, Tail.Overlay.DeviceQueueEntry);
- // 設置 IRP
- device->CurrentIrp = Irp;
- if(cancel)
- // 釋放自旋鎖
- IoReleaseCancelSpinLock(oldirql);
- // 呼叫 StartIO 函式
- device->DriverObject->DriverStartIo(device, Irp);
- }
示例 :
在使用 StartIO 常式時, 需要 IRP 的派遣函式返回 "Pending" 狀態, 然後呼叫 IoStartPacket 內核函式. 下面的程式碼演示了如何完成 :
在派遣函式中呼叫 IoStartPacket 內核函式指定取消常式. 下面程式碼演示如何編寫取消常式 :
接著編寫 StartIO 常式, 注意 StartIO 執行在 DISPATCH_LEVEL 層級, 因此不能使用分頁記憶體, 否則會引起頁故障, 從而導致系統崩潰. 下面為演示程式碼 :
最後編寫應用程式的程式碼. 這段程式碼首先打開裝置, 然後新建兩個執行緒, 每個執行緒都是非同步讀取. 為了模擬真實的情況, 這裡在 StartIO 中停頓了 3 秒鐘. 應用程式同步的發起兩個執行緒同時讀取裝置, 從而真實模擬了同步發起 IRP 請求 :
執行結果如下圖所示 :
This message was edited 7 times. Last update was at 17/01/2011 14:12:31
沒有留言:
張貼留言