2011年1月18日 星期二

[ Windows DDP ] 驅動程式的同步處理 : 插斷要求層級


前言 :
在設計 Winodws 時後, 設計者將插斷要求劃分為軟體插斷和硬體插斷, 並要求這些插斷都映射成不同層級的插斷要求層級 (IRQL). 同步處理機制很大程度依賴於插斷的要求層級, 本節對插斷要求做簡單介紹.

插斷要求 (IRQL) 與可程式設計插斷控制器 (PIC) :
插斷的要求 (IRQ) 一般有兩種, 一種是外部插斷, 也就是硬體產生的插斷, 另一種是由軟體指令 int n 產生的插斷. 這裡只介紹硬體產生的插斷.
在傳統 PC 中, 一般可以接收 16 個插斷信號, 每個插斷信號對應一個插斷號. 外部插斷分為 "不可遮罩" (NMI) 和可遮罩插斷, 分別由 CPU 的兩根引腳 NMI 和 INTR 來接收. 可遮罩插斷是透過可程式設計插斷控制器 (PIC) 8259A 晶片向 CPU 發出請求的. 透過該晶片可以設置插斷的優先順序, 是否被遮罩等. CPU 的 INTR 連接主 8259A 晶片, 同時主8259A 晶片聯接一片從 8259A 晶片. 主從兩個晶片一共可以接收 16 個插斷信號. 下表列出 16 個 IRQ 編號對應的用途 :


高階可程式設計控制器 (APIC) :
傳統 PC 一般使用兩片 Intel 8259A 插斷控制器. 然而現在 x86 電腦基本都是用高階可程式設計控制器, 即 Advanced Programmable Interrupt Controller (APIC).
APIC 相容 PIC, 並且 APIC 把 IRQ 的數量增加到 24 個. 讀者可以用裝置管理員查看這 24 個插斷, 選擇 "檢視" | "資源(依連線)", 可以看到系統中 0~23 號 IRQ. 如下圖 :


插斷要求層級 (IRQL) :
在 APIC 中, IRQ 的數量被增加到了 24 個, 每個 IRQ 有各自的優先順序別, 正在執行的執行緒隨時可以被插斷打斷, 進入到插斷處理常式. 當優先順序高的插斷來臨時, 處於優先順序低的插斷處理常式也會被打斷, 而進入更高階的插斷處理函式.
Windows 將插斷的概念進行了擴充, 提出了一個差斷要求層級 (IRQL) 的概念. 其中規定了 32 個插斷要求層層級, 分別是 0~2 層級為軟體插斷, 3~31 級為硬體插斷 (這裡包含 APIC 中的 24 個插斷) 如下圖所示, 其中數字從 0 到 31, 優先順序逐次遞增 :

使用者模式的程式碼是執行在最低優先順序的 PASSIVE_LEVEL 層級. 驅動程式的 DriverEntry 函式, 派遣函式, AddDevice 等函式一般都是執行在 PASSIVE_LEVEL 層級, 它們在必要時可以申請進入 DISPATCH_LEVEL 層級. Windows 負責執行續調度的元件是執行在 DISPATCH_LEVEL 層級, 當前的執行緒執行完時間片段後, 系統自動從 PASSIVE_LEVEL 層級提升到 DISPATCH_LEVEL 層級. 當執行緒切換完畢後, 作業系統又從 DISPATCH_LEVEL 層級降到 PASSIVE_LEVEL 層級.
驅動程式的 StartIO 函式和 DPC 函式也執行在 DISPATCH_LEVEL 層級, 這些函式會在後面陸續介紹. 在核心模式下, 可以透過呼叫 KeGetCurrentIrql 內核函式來得到當前的 IRQL 層級.

執行緒調度與執行緒優先順序 :
在應用程式設計中, 經常會聽到執行序優先順序的概念. 執行緒優先順序和 IRQL 是兩個容易混淆的概念. 所有應用程式都執行在 PASSIVE_LEVE 層級上, 它的優先順序最低, 可以被其他 IRQL 層級的程式打斷. 執行緒優先順序只針對應用程式而言, 只有程式執行在 PASSIVE_LEVEL 層及才有意義.
執行緒優先順序是指某執行緒是否有更多機會執行在 CPU 上, 執行緒優先順序高的執行緒有更多機會被內核調度. 負責調度執行緒的內核元件執行在 DISPATCH_LEVEL 層級的 IRQL 上, 這時後所有應用程式的程式都會被停止, 並等待調度.
ReadFile 內部新建 IRP_MJ_READ, 然後這個 IRP 被傳遞到驅動程式的派遣函式中. 這時後派遣函式執行於 ReadFile 所在的執行緒中, 或者說 ReadFile 和派遣函式位於同一個執行緒 context 中.

IRQL 的變化 :
為了更好地理解 IRQL 概念, 在下面會描述一個執行緒的執行過程. 這個執行緒在執行中被一個插斷中斷, 並且在插斷服務執行期間, 被更高階別插斷打斷. 執行過程如下圖所示, 執行緒的執行分為以下幾個階段 :
* 階段1 : 一個普通執行緒 A 正在執行.
* 階段2 : 這個時刻插斷發生, 它的 IRQL 為 0xD. CPU 插斷當前執行緒 A, 將 IRQL 提升至 0xD 層級.
* 階段3 : 這時後又有一個更高優先順序插斷發生, 它的 IRQL 是 0x1A. 接著 CPU 將 IRQL 提升至 0x1A 層級.
* 階段4 : 這時後又有一個插斷發生, 它的 IRQL 是 0x18, 低於目前插斷層級, CPU 不會理睬這個插斷.
* 階段5 : 這時後 IRQL 為 0x1A 插斷結束, 作業系統進入 IRQL 為 0x18 的插斷服務
* 階段6 : 這時後 IRQL 為 0x18 插斷結束, 於是進入 IRQL 為 0xD 的插斷服務
* 階段7 : 最後 IRQL 為 0xD 的插斷結束, 作業系統恢復執行緒 A


執行緒執行在 PASSIVE_LEVEL 層級, 這個時候作業系統隨時可能將當前執行緒切換到別的執行緒. 但是如果提升 IRQL 到 DISPATCH_LEVEL 層級, 這時後不會出現執行緒的切換. 這是一種很常用的同步處理機制, 但這種方法只能用於單 CPU 的系統. 對於多 CPU 的系統, 需要採用別的同步處理機制.

IRQL 與記憶體分頁 :
這裡還需要介紹一下 IRQL 與記憶體分頁之間存在的問題. 在使用分頁記憶體時, 可能會導致頁故障. 因為分頁記憶體隨時可能從實體記憶體交換到磁碟檔. 讀取不在實體記憶體的分頁記憶體時, 會引發一個頁故障, 從而執行這個異常的處理函式. 異常處理函式會重新將磁碟檔的內容交換到實體記憶體中.
頁故障允許出現在 PASSIVE_LEVEL 層級的程式中, 但是如果在 DISPATCH_LEVEL 或者更高階別 IRQL 的程式中會帶來系統崩潰. 對於等於或者高於 DISPATCH_LEVEL 層級的程式不能使用分頁記憶體, 必須使用非分頁記憶體. 驅動程式的 StartIO 常式, DPC 常式, 插斷服務常式都執行在 DISPATCH_LEVEL 或者更高的 IRQL. 因此這些常式中不能使用分頁記憶體, 否則可能發生系統崩潰.

控制 IRQL 提升與降低 :
有些時候, 驅動程式中需要提升 IRQL 層級. 在執行一段時間後, 再降回原來的 IRQL 層級. 這樣做的目的是基於同步處理的需要.
首先驅動程式需要知道當前狀態是什麼 IRQL 層級, 可以透過 KeGetCurrentIrql 內核函式獲取當前 IRQL 層級. 然後驅動程式使用內核函式 KeRaiseIrql 將 IRQL 提高. KeRaiseIrql 需要兩個參數, 第一個參數是提升後的 IRQL 層級, 第二個參數保存提升前的 IRQL 層級. 最後驅動程式在某個時刻將 IRQL 恢復到以前的 IRQL 層級, 驅動程式可以呼叫 KeLowerIrql 函式完成恢復原來 IRQL 的動作, 底下是範例代碼 :
- 函式 RaiseIRQL_Test() :
  1. VOID RaiseIRQL_Test()  
  2. {  
  3.     KIRQL oldirql;  
  4.     // 確保當前 IRQL 等於或小於 DISPATCH_LEVEL  
  5.     ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);  
  6.     // 提升 IRQL 至 DISPATCH_LEVEL, 並將先前的 IRQL 保存  
  7.     KeRaiseIrql(DISPATCH_LEVEL, &oldirql);  
  8.     //....  
  9.     // 恢復到之前的 IRQL  
  10.     KeLowerIrql(oldirql);  
  11. }  

補充說明 :
Windows Kernel的IRQL運作機制
IRQL = Interrupt Request Level IRQL.
簡單的說就是『Interrupt執行的優先等級』。 若某個Interrupt產生了,且IRQL等於或低於目前Processor的IRQL setting。那麼他將不會影響目前程式執行。反之,若Interrupt的IRQL高於目前Processor的IRQL setting,那麼將會造成目前的執行中斷,而去執行Interrupt的工作。總而言之,較高優先權的Interrupt會中斷較低優先權Interrupt。當這個狀況發生時,所有其他等於或是低於這個IRQL的中斷都將成為等待狀態。 透過KeGetCurrentIRQL() 這個System routine可以得到目前Processor的IRQL...
This message was edited 9 times. Last update was at 01/01/2011 16:41:29

沒有留言:

張貼留言

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...