線程警惕和APC

在內部,Windows NT內核有時使用線程警惕(thread alert)來喚醒線程。這種方法使用APC(異步過程調用)來喚醒線程去執行某些特殊例程。用於生成警惕和APC的支持例程沒有輸出給WDM驅動程序開 發者使用。但是,由於DDK文檔和頭文件中有大量地方引用了這個概念,所以我想在這裏談一下。
當某人通過調用KeWaitXxx例程阻塞一個線程時,需要指定一個布爾參數,該參數表明等待是否是警惕的(alertable)。一個警惕的 等待可以提前完成,即不用滿足任何等待條件或超時,僅由於線程警惕。線程警惕起源於用戶模式的native API函數NtAlertThread。如果因爲警惕等待提前終止,則內核返回特殊的狀態值STATUS_ALERTED。
APC機制使操作系統能在特定線程上下文中執行一個函數。APC的異步含義是,系統可以有效地中斷目標線程以執行一個外部例程。APC的動作有點類似於硬件中斷使處理器從任何當前代碼突然跳到ISR的情形,它是不可預見的。
APC來自三種地方:用戶模式、內核模式,和特殊內核模式。用戶模式代碼通過調用Win32 API函數QueueUserAPC請求一個用戶模式APC。內核模式代碼通過調用一個未公開的函數請求一個APC,而且該函數在DDK頭文件中沒有原 型。某些逆向工程師可能已經知道該例程的名稱以及如何調用它,但該函數的確是僅用於內部,所以我不在這裏討論它。系統把APC排入一個特殊線程直到和適的 執行條件出現。和適的執行條件要取決於APC的類型,如下:
特殊的內核APC被儘可能快地執行,既只要APC_LEVEL級上有可調度的活動。在很多情況下,特殊的內核APC甚至能喚醒阻塞的線程。
普通的內核APC僅在所有特殊APC都被執行完,並且目標線程仍在運行,同時該線程中也沒有其它內核模式APC正執行時才執行。
用戶模式APC在所有內核模式APC執行完後才執行,並且僅在目標線程有警惕屬性時才執行。
如果系統喚醒線程去提交一個APC,則使該線程阻塞的等待原語函數將返回特殊狀態值STATUS_KERNEL_APC或STATUS_USER_APC。
APC與I/O請求
內核使用APC概念有多種目的。我僅解釋APC與執行I/O操作之間的關係。在某些場合,當用戶模式程序在一個句柄 上執行同步的ReadFile操作時,Win32子系統就調用一個名爲NtReadFile(儘管未公開,但已經被廣泛瞭解)的內核模式例程。該函數創建 並提交一個IRP到適當的設備驅動程序,而驅動程序通常返回STATUS_PENDING以指出操作未完成。NtReadFile然後向ReadFile 也返回這個狀態代碼,於是ReadFile調用NtWaitForSingleObject函數,這將使應用程序在那個用戶模式句柄指向的文件對象上等 待。NtWaitForSingleObject接着調用KeWaitForSingleObject以執行一個非警惕的用戶模式的等待,在文件對象內部 的一個事件對象上等待。
當設備驅動程序最後完成了讀操作時,它調用IoCompleteRequest函數,該函數接下來排隊一個特殊的內核模式APC。該APC例程 然後調用KeSetEvent函數使文件對象進入信號態,因此應用程序被釋放並得以繼續執行。有時,I/O請求被完成後還需要執行一些其它任務,如緩衝區 複製,而這些操作又必須發生在請求線程的地址上下文中,因此會需要其它種類的APC。如果請求線程不處於警惕性的等待狀態,則需要內核模式APC。如果在 提交APC時線程並不適合運行,則需要特殊的APC。實際上,APC例程就是用於喚醒線程的機制。
內核模式例程也能調用NtReadFile函數。但驅動程序應該調用ZwReadFile函數替代,它使用與用戶模式程序一樣的系統服務接口到 達NtReadFile(注意,NtReadFile函數未公開給設備驅動程序使用)。如果你遵守DDK的限定調用ZwReadFile函數,那麼你向 NtReadFile的調用與用戶模式中的調用幾乎沒有什麼不同,僅有兩處不同。第一,ZwReadFile函數更小,並且任何等待都將在內核中完成。另 一個不同之處是,如果你調用了ZwCreateFile函數並指定了同步操作,則I/O管理器將自動等待你的讀操作直到完成。這個等待可以是警惕的也可以 不是,取決於你在ZwCreateFile調用中指定的實際選項。
如何指定Alertable和WaitMode參數
現在你已經有足夠的背景資料瞭解等待原語中的Alertable和WaitMode 參數。作爲一個通用規則,你絕不要寫同步響應用戶模式請求的代碼,僅能爲確定的I/O控制請求這樣做。一般說來,最好掛起長耗時的操作(從派遣例程中返回 STATUS_PENDING代碼)而以異步方式完成。再有,你不要一上來就調用等待原語。線程阻塞僅適合設備驅動程序中的某幾個地方使用。下面幾段介紹 了這幾個地方。
內核線程 有時,當你的設備需要週期性循檢時,你需要創建自己的內核模式線程。
1)處理PnP請求 有幾個PnP請求需要你在驅動程序這邊同步處理。換句話說,你把這些請求傳遞到低級驅動程序並等待它們完成。你將調用 KeWaitForSingleObject函數並在內核模式中等待,這是由於PnP管理器是在內核模式線程的上下文中調用你的驅動程序。另外,如果你需 要執行作爲處理PnP請求一部分的輔助請求時,例如,與USB設備通信,你應在內核模式中等待。
2)處理其它I/O請求 當你正在處理其它種類的I/O請求時,並且你知道正運行在一個非任意線程上下文中時,那麼你在行動前必須仔細考慮,如果你確信那個線程可以被阻塞,你應該 在調用者所處於的處理器模式中等待。在多數情況下,你可以利用IRP中的RequestorMode域。此外,你還可以調用 ExGetPreviousMode來確定前一個處理器模式。如果你在用戶模式中等待,並允許用戶程序調用QueueUserAPC提前終止等待,你應該 執行一個警惕性等待。
3)我最後要提到的情況是,在用戶模式中等待並要允許用戶模式APC打斷,你應使用警惕性等待。
*)底線是:使用非警惕性等待,除非你知道不這樣做的原因。

轉自http://hi.baidu.com/fanzier/blog/item/877b86546d4798183b2935c1.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章