前言 :
在設計 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 概念, 在下面會描述一個執行緒的執行過程. 這個執行緒在執行中被一個插斷中斷, 並且在插斷服務執行期間, 被更高階別插斷打斷. 執行過程如下圖所示, 執行緒的執行分為以下幾個階段 :
執行緒執行在 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 的動作, 底下是範例代碼 :
補充說明 :
* Windows Kernel的IRQL運作機制
在設計 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 概念, 在下面會描述一個執行緒的執行過程. 這個執行緒在執行中被一個插斷中斷, 並且在插斷服務執行期間, 被更高階別插斷打斷. 執行過程如下圖所示, 執行緒的執行分為以下幾個階段 :
執行緒執行在 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 的動作, 底下是範例代碼 :
補充說明 :
* Windows Kernel的IRQL運作機制
This message was edited 9 times. Last update was at 01/01/2011 16:41:29
沒有留言:
張貼留言