轉(zhuǎn)
C++ 及其對(duì)象特性似乎與 Microsoft Windows Driver Model (WDM) 和 Windows Driver Foundation (WDF) 驅(qū)動(dòng)程序的語義非常吻合。但是,對(duì)于內(nèi)核模式驅(qū)動(dòng)程序,C++ 語言的一些特性可能導(dǎo)致難以發(fā)現(xiàn)和解決的問題。為了幫助您進(jìn)行合理選擇,本文將與您分享來自 Microsoft 關(guān)于使用 C++ 為 Windows 家族操作系統(tǒng)編寫內(nèi)核模式驅(qū)動(dòng)程序的調(diào)查的見解和建議。
此信息適用于以下操作系統(tǒng):
Microsoft Windows 2000
Microsoft Windows XP
Microsoft Windows Server 2003
Microsoft Windows Vista
Microsoft Windows Server 2008
簡(jiǎn)介
借助其對(duì)象特性,C++ 似乎與 Microsoft Windows Driver Model (WDM) 和 Windows Driver Foundation (WDF) 驅(qū)動(dòng)程序的語義非常吻合,而且它為開發(fā)人員帶來的便利性和極富表現(xiàn)性的功能確實(shí)很有吸引力。但是,使用目前可用的 Microsoft 編譯器在 C++ 中編寫內(nèi)核模式代碼涉及到一些技術(shù)問題,這些問題可能引起驅(qū)動(dòng)程序代碼中的其他問題。
許多開發(fā)人員將 C++ 編譯器當(dāng)作“超級(jí) C”來使用,而沒有完全使用 C++ 的功能,因?yàn)?C++ 編譯器執(zhí)行的某些規(guī)則比標(biāo)準(zhǔn) C 編譯器更加嚴(yán)格,而且提供一些能夠在驅(qū)動(dòng)程序上下文中安全使用的附加特性。通常認(rèn)為 C++ 編譯器的這種使用方式適合于內(nèi)核模式代碼。正是一些“高級(jí)的”C++ 特性引起了內(nèi)核模式代碼中的問題,例如非 POD("plain old data",如 C++ 標(biāo)準(zhǔn)所定義)類和繼承、模板和異常。這些問題主要是由 C++ 實(shí)現(xiàn)和內(nèi)核環(huán)境引起,而不是 C++ 語言的內(nèi)在屬性。
Microsoft 正在調(diào)查與使用 C++ 為 Microsoft Windows 家族操作系統(tǒng)編寫內(nèi)核模式驅(qū)動(dòng)程序相關(guān)的問題。本文將與您分享 Micorsoft 開發(fā)人員關(guān)于如何權(quán)衡使用 C++ 編寫驅(qū)動(dòng)程程序的利弊的最新見解。
本文內(nèi)容適用于創(chuàng)建內(nèi)核模式驅(qū)動(dòng)程序的標(biāo)準(zhǔn) Windows Driver Development Kit (DDK) 構(gòu)建環(huán)境(從 Windows Server 2003 Service Pack 1 (SP1) DDK 開始)。如果您使用的構(gòu)建環(huán)境或編譯器不是由 DDK 或 Windows Driver Kit (WDK) 提供的,那么您應(yīng)該確定本文討論的各個(gè)問題是否適用于您的開發(fā)環(huán)境,以及是否存在其他問題。確定該問題的信息可以通過文檔的形式從編譯器提供者獲得,但是正如下面所描述的,您可能更有必要檢查生成的代碼和鏈接圖。
本文不打算討論如何使用 C++ 編寫內(nèi)核模式驅(qū)動(dòng)程序,而是假設(shè)您了解編寫內(nèi)核模式驅(qū)動(dòng)程序的基本原理。有關(guān)編寫內(nèi)核模式驅(qū)動(dòng)程序的一般信息,請(qǐng)參閱內(nèi)核模式體系結(jié)構(gòu)指南和 Windows DDK 文檔中的設(shè)備特定信息。
內(nèi)核模式代碼注意事項(xiàng)
內(nèi)核模式代碼必須考慮以下因素,以避免損壞數(shù)據(jù)、系統(tǒng)不穩(wěn)定和操作系統(tǒng)沖突。
內(nèi)核管理其自己的內(nèi)存頁:
您必須處理好兩個(gè)相互矛盾的要求,即操作正確和最小內(nèi)存占用。
• |
在不允許分頁時(shí),如果要執(zhí)行代碼,那么代碼和數(shù)據(jù)必須位于內(nèi)存中。也就是說,當(dāng)系統(tǒng)以 IRQL DISPATCH_LEVEL 或更高級(jí)別運(yùn)行時(shí),包含當(dāng)前執(zhí)行例程及其調(diào)用的任何例程或訪問的數(shù)據(jù)(以及在此函數(shù)調(diào)用鏈上的所有信息)等的頁面都必須鎖定到內(nèi)存中,直到 IRQL 級(jí)別降低到 DISPATCH_LEVEL 以下。否則,就會(huì)發(fā)生頁面錯(cuò)誤和系統(tǒng)沖突。
|
• |
要增加用戶應(yīng)用程序可用的內(nèi)存量,驅(qū)動(dòng)程序應(yīng)該使其代碼和數(shù)據(jù)片段能夠在合適的情形下分頁。這可以提升系統(tǒng)性能。
|
并不是隨時(shí)都可以使用所有的處理器資源。
• |
在 x86 系統(tǒng)上,浮點(diǎn)和多媒體單元就無法在內(nèi)核模式中使用,除非特意請(qǐng)求。嘗試不恰當(dāng)?shù)厥褂盟鼈儾灰欢〞?huì)導(dǎo)致提升的 IRQL 上的浮點(diǎn)錯(cuò)誤(這將造成系統(tǒng)沖突),但是可能導(dǎo)致隨機(jī)進(jìn)程中的數(shù)據(jù)不知不覺地?fù)p壞。不恰當(dāng)?shù)厥褂靡部赡茉斐善渌M(jìn)程中的數(shù)據(jù)損壞;這類問題通常難以調(diào)試。
|
• |
在 Intel Itanium 系統(tǒng)上,不是所有的浮點(diǎn)寄存器都可用。
|
資源(尤其是堆棧)具有嚴(yán)格的限制。用戶空間中“廉價(jià)”的資源在內(nèi)核模式中可能非常昂貴,或者要求采取不同的方法來獲取。具體來講,內(nèi)核堆棧的大小是 3 頁。
內(nèi)核模式中沒有提供所有的標(biāo)準(zhǔn)庫(C 或 C++)。
• |
構(gòu)建環(huán)境為內(nèi)核模式提供的標(biāo)準(zhǔn)庫不必與用戶模式相同,因?yàn)閮?nèi)核模式的標(biāo)準(zhǔn)庫不依賴于 Win32 API,而且這些庫的編寫必須符合內(nèi)核模式要求。標(biāo)準(zhǔn)庫的內(nèi)核模式實(shí)現(xiàn)可能僅有有限的功能,或者受到其他內(nèi)核模式屬性的制約。
|
• |
庫例程的用戶模式實(shí)現(xiàn)可能不能在內(nèi)核模式下工作。有些例程不能鏈接,有些不能運(yùn)行,還有些例程看似可以運(yùn)行,但具有負(fù)面影響。
|
將 C++ 編譯器用于內(nèi)核模式代碼
請(qǐng)務(wù)必牢記,編譯器生成的正確的目標(biāo)代碼未必是您期望的代碼,其組織方式也未必是您所期望的。事實(shí)總是如此,但是 C++ 比 C 更可能發(fā)生這種問題。您必須檢查目標(biāo)代碼,以確保與您的期望一致,或者至少能在內(nèi)核環(huán)境中正確工作。
目前可用的 C++ 編譯器的輸出不能保證在所有平臺(tái)和版本的內(nèi)核模式都能工作。代碼使用的 C++“高級(jí)”特性越多,就越可能出現(xiàn)互操作性問題。
內(nèi)核模式代碼的關(guān)鍵區(qū)域
需要特別注意內(nèi)核模式驅(qū)動(dòng)程序中的以下區(qū)域。對(duì)于那些適合兩種語言(C 和 C++)的區(qū)域,C++ 代碼可能更容易出問題,因?yàn)?C++ 編譯器做了更多的自動(dòng)化工作,而且您可能不會(huì)意識(shí)到它導(dǎo)致了一個(gè)問題。
• |
必須使用 KeSaveFloatingPointState 和 KeRestoreFloatingPointState 或 Windows DDK 文檔描述的其他機(jī)制恰當(dāng)?shù)乇Wo(hù)浮點(diǎn)指令。
|
• |
InterlockedXxx 函數(shù)應(yīng)當(dāng)在生成的代碼中插入內(nèi)存屏障指令。檢查輸出以確保您需要的屏障已經(jīng)存在。
|
• |
必須仔細(xì)理解 volatile 關(guān)鍵字的語義,確保其指向一個(gè)“易變”級(jí)別的對(duì)象。可變項(xiàng)有時(shí)是指針,有時(shí)是對(duì)象本身,有時(shí)指針和對(duì)象都是可變的。將 volatile 應(yīng)用到錯(cuò)誤的對(duì)象上是常見的錯(cuò)誤,因此應(yīng)該仔細(xì)檢查該關(guān)鍵字的使用。例如,如果打算將一個(gè)穩(wěn)定的指針指向可變的位置,那么應(yīng)該(通過仔細(xì)閱讀代碼)確保代碼實(shí)現(xiàn)的不是一個(gè)指向穩(wěn)定位置的可變指針。
|
• |
堆棧幀嚴(yán)格受限。例如,在 x86 系統(tǒng)上,每個(gè)線程可用的堆棧總量是 12K。
|
• |
函數(shù)源代碼中隱含的跳轉(zhuǎn)或內(nèi)存使用會(huì)帶來發(fā)生意外的頁面錯(cuò)誤的風(fēng)險(xiǎn)。特別地,編譯器生成的一些函數(shù)和數(shù)據(jù)對(duì)象可能不會(huì)馬上顯露出來。關(guān)于可能發(fā)生意外的對(duì)象的詳細(xì)信息,請(qǐng)參閱本文稍后的“內(nèi)存中的代碼”。
|
• |
對(duì)于內(nèi)聯(lián)函數(shù)(和 __forceinline)的使用,如果要確保代碼駐留在內(nèi)存中,則應(yīng)該與編譯器的優(yōu)化規(guī)則交互。
• |
您期望其內(nèi)聯(lián)的函數(shù)可能并不是內(nèi)聯(lián)的。結(jié)果,使用這樣的函數(shù)可能造成頁面錯(cuò)誤。
|
• |
編譯器可能在您不期望的情況下生成函數(shù)的內(nèi)聯(lián)代碼。
|
|
安全和不安全的 C++ 構(gòu)造
盡管目前還沒有嚴(yán)格的和可測(cè)試的“完全安全的” C++ 子集可用于內(nèi)核模式代碼,但是一些有用的指南可用于區(qū)分安全與不安全的構(gòu)造。
一個(gè)出色的經(jīng)驗(yàn)法則是,如果有一種明顯的方式可以將 C++ 構(gòu)造重新整理為合法的 C 代碼,那么它可能是安全的。一個(gè)示例就是聲明的松散排序,包括在 for 語句中聲明變量。
C++ 中更嚴(yán)格的類型檢查可能不允許技術(shù)上合法但是語義上錯(cuò)誤的構(gòu)造。這種更嚴(yán)格的類型檢查是一種提高驅(qū)動(dòng)程序可靠性的有用方式。
涉及類層次結(jié)構(gòu)或模板、異常,或各種形式的動(dòng)態(tài)類型的任何內(nèi)容都可能不安全。使用這些構(gòu)造需要對(duì)生成的目標(biāo)代碼進(jìn)行非常仔細(xì)的分析。將類的使用限制到 POD 類能夠顯著降低風(fēng)險(xiǎn)。
檢查生成的代碼
C 語言的一個(gè)最初的設(shè)計(jì)目標(biāo)是能夠輕松確定生成的目標(biāo)代碼的用途,因此 C 語言非常適合處理內(nèi)核模式。而 C++ 是一種復(fù)雜得多的語言,這使得將其用于內(nèi)核環(huán)境要困難得多。
要使用 C++ 編寫驅(qū)動(dòng)程序,必須理解編譯器生成的代碼,確保目標(biāo)代碼滿足內(nèi)核模式要求,并確保其不會(huì)出現(xiàn)本文討論的問題。開發(fā)人員應(yīng)該做好閱讀目標(biāo)代碼、瀏覽鏈接圖的準(zhǔn)備,以確保數(shù)據(jù)和代碼都位于合適的位置并且僅使用了內(nèi)核安全的庫。檢查代碼的可分頁性、內(nèi)聯(lián)函數(shù)和正確的程序順序。
我們強(qiáng)烈建議您立即閱讀和測(cè)試這方面的代碼,而不是等到編寫完源代碼再進(jìn)行閱讀和測(cè)試。檢查早期的原型并測(cè)試潛在的疑難用法,這樣如果遇到了難以克服的 C++ 問題,您還有機(jī)會(huì)找到和實(shí)現(xiàn)替代解決方案。
內(nèi)核模式驅(qū)動(dòng)程序的 C++ 問題
Microsoft 開發(fā)人員已經(jīng)發(fā)現(xiàn) C++ 中容易出現(xiàn)特定的內(nèi)核模式驅(qū)動(dòng)程序問題的一些區(qū)域。
內(nèi)存中的代碼
使用 C++ 編寫內(nèi)核模式驅(qū)動(dòng)程序面臨的最嚴(yán)重的問題是內(nèi)存頁面的管理,尤其是內(nèi)存中的代碼,而不是數(shù)據(jù)。大型驅(qū)動(dòng)程序的可分頁性非常重要,而且分頁代碼并不總是在內(nèi)存中。在系統(tǒng)進(jìn)入無法進(jìn)行分頁的狀態(tài)之前,所有將要用到的代碼都必須駐留在內(nèi)存中。
C++ 編譯器為非 POD 類和模板生成代碼的方式使得很難確定執(zhí)行一個(gè)函數(shù)所需的所有代碼的去向,因此很難將代碼安全地分頁。編譯器能夠?yàn)橹辽傧铝袑?duì)象自動(dòng)生成代碼。如果這些對(duì)象不一致,開發(fā)人員無法直接控制插入這些代碼的節(jié),這意味著當(dāng)需要這些代碼時(shí),它們卻可能已經(jīng)被分頁出去。
• |
編譯器生成的代碼,比方構(gòu)造函數(shù)、析構(gòu)函數(shù)、類型轉(zhuǎn)換和賦值運(yùn)算符。(雖然可以明確地提供這些代碼,但是需要仔細(xì)確認(rèn)是否需要提供它們。)
|
• |
Ajdustor thunk,用于在層次結(jié)構(gòu)中的類之間進(jìn)行轉(zhuǎn)換。
|
• |
虛函數(shù) thunk,用于實(shí)現(xiàn)虛函數(shù)調(diào)用。
|
• |
虛函數(shù)表 thunk,用于管理基類和多態(tài)。
|
• |
模板代碼正文,在首次使用時(shí)插入,除非對(duì)其進(jìn)行了顯式實(shí)例化。
|
• |
虛函數(shù)表本身。
|
C++ 編譯器沒有提供機(jī)制來直接控制這些實(shí)體在內(nèi)存中的位置。C++ 的設(shè)計(jì)并沒有考慮控制內(nèi)存位置的必要性。#pragma alloc_text 不能用于控制成員函數(shù)的位置,因?yàn)闊o法命名該成員函數(shù)(有多種原因)。編譯器生成的函數(shù)、擴(kuò)展模板正文和編譯器生成 thunk 的 #pragma code_seg 的作用域比較模糊。沒有控制虛函數(shù)表的位置的機(jī)制,因?yàn)閺木幾g器的角度看,這種表既不是代碼也不是數(shù)據(jù)(虛函數(shù)表獨(dú)占了一節(jié))。
如果頭文件中的函數(shù)聲明為內(nèi)聯(lián),但是編譯器沒有生成該函數(shù)的內(nèi)聯(lián)代碼,那么根據(jù)使用該函數(shù)的位置,它可能被插入多個(gè)代碼段中。實(shí)例化一個(gè)類模板時(shí),它會(huì)在首次使用它的節(jié)中生成,并且通常不會(huì)立即發(fā)現(xiàn)是哪一節(jié)生成了它。這兩個(gè)問題會(huì)造成不應(yīng)該分頁的代碼變得可以分頁,或者應(yīng)該分頁的代碼卻無法分頁。
如果使用了一種類層次結(jié)構(gòu),那么是否需要在訪問派生類時(shí)將基類代碼放入內(nèi)存中完全取決于從派生類調(diào)用的基類函數(shù)(和編譯器是否能夠內(nèi)聯(lián)這些函數(shù)),以及在哪些節(jié)插入這些函數(shù)。例如,如果派生類提供了一種不需要使用基類方法的方法,那么基類代碼就無需駐留在內(nèi)存中。但是,難以確定何時(shí)屬于這種情形。另外,該層次結(jié)構(gòu)及其類使用的任何 thunk 也可能需要駐留在內(nèi)存中。
堆棧
編譯器始終能夠在堆棧上自由生成額外數(shù)據(jù),比如創(chuàng)建臨時(shí)對(duì)象、延遲調(diào)用清除和其他以隱蔽方式使用堆棧的操作。有關(guān)單個(gè)函數(shù)使用堆棧的方式,C 和 C++ 幾乎沒有區(qū)別,但是由于額外的機(jī)制通常會(huì)導(dǎo)致更多的函數(shù)調(diào)用,所以 C++ 使用的堆棧總數(shù)常常會(huì)更多。應(yīng)當(dāng)牢記堆棧大小,在任何編程語言中,當(dāng)堆棧空間受限時(shí)都應(yīng)如此。
異常也會(huì)影響到堆棧。請(qǐng)參閱本文稍后的“異常與 RTTI”。
動(dòng)態(tài)內(nèi)存
驅(qū)動(dòng)程序開發(fā)工具(比如 Driver Verifier)依靠帶有標(biāo)記的內(nèi)存來驗(yàn)證驅(qū)動(dòng)程序中內(nèi)存使用。使用 operator new 和 operator delete 分配和釋放內(nèi)存會(huì)削弱這些工具檢測(cè)驅(qū)動(dòng)程序代碼中的內(nèi)存泄漏和其他問題的能力。
在用戶空間中,operator new 和 operator delete 非常方便,但是如果驅(qū)動(dòng)程序使用了多個(gè)內(nèi)存池或帶標(biāo)記的內(nèi)存,那么這兩個(gè)運(yùn)算符會(huì)變得很麻煩。因?yàn)?"placement new" 帶有額外的操作數(shù),所以將選擇內(nèi)存池或生成標(biāo)記所需的信息傳入到重載的 operator new 中,但是這并不比直接使用內(nèi)存函數(shù)容易多少。因?yàn)闆]有帶有額外的參數(shù)的 "placement delete" 可以傳入標(biāo)記或池類型,所以使用 operatordelete 時(shí)無法傳入標(biāo)記(或內(nèi)存控制,如果需要),也就不可能檢查位于釋放位置的標(biāo)記是否是預(yù)期的標(biāo)記,這極大地影響了使用標(biāo)記內(nèi)存的好處。不用提供標(biāo)記就可以對(duì)內(nèi)存進(jìn)行 delete 操作,但是您需要確定不在驅(qū)動(dòng)程序代碼中使用標(biāo)記的風(fēng)險(xiǎn)和缺點(diǎn)是否大于其便利性。
內(nèi)存跟蹤工具通常記錄進(jìn)行分配的函數(shù)的返回地址。一些 C++ 編譯器將 operator new 實(shí)現(xiàn)為函數(shù),這使得所有內(nèi)存分配似乎都來自同一個(gè)位置,從而影響了內(nèi)存跟蹤工具在這方面的功能。雖然這個(gè)問題可以解決,但是您必須確定這樣做的好處是否大于直接使用內(nèi)存分配的好處。
庫
創(chuàng)建和使用庫時(shí)需要考慮許多明顯因素:
• |
導(dǎo)出的 C++ 函數(shù)的名稱可能因版本不同而異。
|
• |
不是用戶模式中所有可用的函數(shù)都能夠在內(nèi)核模式庫中使用。
|
• |
標(biāo)準(zhǔn)模板庫設(shè)計(jì)用于處理來自單個(gè) DLL 的數(shù)據(jù)對(duì)象。
|
C++ 函數(shù)的導(dǎo)出基于它們的完整簽名,而不是(像 C 函數(shù)那樣)只基于其名稱。C++ 函數(shù)的名稱被改編為包含類型信息,該信息是其簽名的一部分。盡管名稱改編的規(guī)則相當(dāng)穩(wěn)定,但是無法保證改編的名稱不隨編譯器版本的變化而改變。因此,無法將 C++ 函數(shù)可靠地導(dǎo)出到不同版本的庫中,但是可以表示為 extern "C" 的 C++ 函數(shù)能夠做到。另外,使用 .def 文件能夠幫助減輕這個(gè)問題的風(fēng)險(xiǎn)。注意:extern "C" 函數(shù)的獨(dú)特性僅基于函數(shù)名稱,而不是像 C++ 中那樣基于整個(gè)簽名。
不是所有的庫函數(shù)都可以在內(nèi)核模式下使用,尤其是與“高級(jí)” C++ 語言特性相關(guān)的函數(shù)。標(biāo)準(zhǔn)模板庫是實(shí)現(xiàn)許多 C++ 概念(例如大小可變的數(shù)組)的“常用”方法。但是,簡(jiǎn)單地假定標(biāo)準(zhǔn)模板庫存在且可用是不安全的。盡管標(biāo)準(zhǔn)模板庫的大部分內(nèi)容都實(shí)現(xiàn)為頭文件中的源代碼,但是這個(gè)庫也會(huì)偶爾使用內(nèi)核環(huán)境中不可用或沒有用處的庫函數(shù)或其他特性。
標(biāo)準(zhǔn)模板庫還假設(shè)其使用的每個(gè)數(shù)據(jù)對(duì)象都存在于單個(gè) DLL 中。盡管在大多數(shù)情況下,可以跨越 DLL 邊界傳遞 POD 對(duì)象的引用,但是傳遞比較復(fù)雜的結(jié)構(gòu)(比如列表)的引用可能導(dǎo)致運(yùn)行時(shí)錯(cuò)誤并且難以診斷。已知問題包括:如果沒有為一個(gè) DDL 分配內(nèi)存,那么釋放它的內(nèi)存就會(huì)導(dǎo)致失敗(至少在進(jìn)行調(diào)試模式編譯時(shí)是這樣);各個(gè) DDL 的 "end of list" 標(biāo)記各不相同,這會(huì)導(dǎo)致意外的超越列表搜索。您必須清楚這些問題并采取步驟來預(yù)防它們。
我們不建議在內(nèi)核模式驅(qū)動(dòng)程序中使用標(biāo)準(zhǔn)模板庫函數(shù),因?yàn)闊o法假定標(biāo)準(zhǔn)模板庫已經(jīng)存在并且能正常工作。對(duì)于內(nèi)核模式代碼,準(zhǔn)確理解特定數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn)方法有助于確保該數(shù)據(jù)結(jié)構(gòu)不會(huì)違背內(nèi)核空間的要求。專門的實(shí)現(xiàn)也可能比更常用的標(biāo)準(zhǔn)模板庫函數(shù)更小,但是庫通常能夠更好地滿足內(nèi)核空間的要求。
異常與 RTTI
C++ 異常很有吸引力,但是它們很難在內(nèi)核模式中實(shí)現(xiàn)。C++ 異常需要一個(gè)內(nèi)核模式安全的庫,目前還不存在這種庫。異常還會(huì)帶來無法回避的運(yùn)行時(shí)問題,因?yàn)閷?duì)于大小受限的堆棧來說,拋出異常時(shí)生成的異常記錄是很大的對(duì)象。在 x86 系統(tǒng)上,異常記錄不是特別大(但是比許多一般的堆棧幀更大),但是 Intel Itanium 系統(tǒng)上的異常記錄非常大:3K 到 4K,或者可用的 24K 堆棧空間的 1/8 到 1/6。為了保持驅(qū)動(dòng)程序到 64 位平臺(tái)的可移植性,應(yīng)該盡量避免使用異常,即使在 x86 體系結(jié)構(gòu)上也是如此。rethrow 運(yùn)算符會(huì)在堆棧上生成多個(gè)異常記錄。注意:結(jié)構(gòu)化異常處理 (__try /__except/__finally) 可以在內(nèi)核模式下使用,但是空間問題仍然存在。C++ 異常在語義上有許多微妙之處,可以防止將其簡(jiǎn)單地映射到結(jié)構(gòu)化異常處理上。
運(yùn)行時(shí)類型信息 (RTTI) 還需要一個(gè)庫,內(nèi)核模式的C++ 中目前還沒有這個(gè)庫。迄今為止,內(nèi)核模式代碼中就這個(gè)庫的請(qǐng)求(如果有)非常少。現(xiàn)在還無法確定這種需求的缺乏是因?yàn)槠渌麊栴}的掩蓋,還是因?yàn)樗鼘?duì)內(nèi)核模式無用。
編譯器版本
盡管 C++ 語言標(biāo)準(zhǔn)很穩(wěn)定,但其實(shí)現(xiàn)技術(shù)一直在發(fā)展。因此,編譯器版本可能會(huì)更改生成代碼的操作方式。這類修改不太可能影響到用戶模式代碼,但是會(huì)影響到內(nèi)核模式代碼。在內(nèi)核模式代碼中,更多的底層實(shí)現(xiàn)被曝露給(有時(shí)來自)驅(qū)動(dòng)程序開發(fā)人員;無法保證內(nèi)核模式代碼在版本之間的互操作性。
您應(yīng)該謹(jǐn)慎控制兩個(gè)驅(qū)動(dòng)程序之間或一個(gè)驅(qū)動(dòng)程序和操作系統(tǒng)之間的任何接口,通常使用 C 而不用 C++ 編寫這些結(jié)構(gòu)。否則,C++ 實(shí)現(xiàn)的版本間不兼容性可能導(dǎo)致互操作失敗。
靜態(tài)變量與全局范圍變量和初始化
C++ 靜態(tài)變量(在全局或局部范圍內(nèi)聲明)為驅(qū)動(dòng)程序帶來了許多問題。
C++ 標(biāo)準(zhǔn)允許在局部范圍內(nèi)聲明 static 變量,以在首次使用時(shí)(首次進(jìn)入該范圍時(shí))對(duì)其初始化。這種實(shí)現(xiàn)方式不但會(huì)造成初始化期間的競(jìng)爭(zhēng)條件,還會(huì)帶來與意外的線程間數(shù)據(jù)共享相關(guān)的高風(fēng)險(xiǎn),因?yàn)槁暶鳛?static 的變量是全局靜態(tài),而不是基于每個(gè)線程。最好在全局范圍內(nèi)顯式地處理(在線程間共享的)全局靜態(tài)數(shù)據(jù),以確保訪問保護(hù)適合于所應(yīng)用的條件。
如果 C++ 全局對(duì)象要求聲明初始化(全局構(gòu)造函數(shù)),則無法調(diào)用這個(gè)構(gòu)造函數(shù)。不應(yīng)該使用需要構(gòu)造函數(shù)的全局對(duì)象,或者必須開發(fā)一種機(jī)制來確保可以調(diào)用該構(gòu)造函數(shù)。網(wǎng)絡(luò)上有一些消息來源聲稱已經(jīng)解決這個(gè)問題,其中可能有適合您的解決方案。
C++ 標(biāo)準(zhǔn)沒有指定全局對(duì)象的初始化順序,所以即使存在一種調(diào)用全局對(duì)象構(gòu)造函數(shù)的機(jī)制,初始化順序也必須由驅(qū)動(dòng)程序明確地控制,或者該順序無關(guān)緊要。
結(jié)束語
Microsoft 既不認(rèn)可也不反對(duì)使用 C++ 編寫內(nèi)核模式驅(qū)動(dòng)程序。這種保守態(tài)度一部分源于本文所述問題,也有一部分源于支持所有平臺(tái)的需要。在嘗試使用 C++ 進(jìn)行任何內(nèi)核模式開發(fā)之前,您必須清楚本文講述的已知問題和風(fēng)險(xiǎn),也應(yīng)該警惕其他的未知問題。
Microsoft 一直在調(diào)查研究在內(nèi)核中更有效地使用 C++ 的方法。目前還不知道適用于用戶模式代碼的所有 C++ 特性是否都可用于內(nèi)核模式代碼。
• |
將 C++ 編譯器用作“超級(jí) C”通常是可行的,但是編譯器的這種用法會(huì)給開發(fā)人員帶來一定的風(fēng)險(xiǎn)。
|
• |
自動(dòng)識(shí)別存在問題的 C++ 構(gòu)造目前還不實(shí)際,因此開發(fā)人員必須仔細(xì)分析編譯器輸出,以確保生成的代碼適合于內(nèi)核模式。
|
• |
在使用 C++ 之前,應(yīng)該認(rèn)真評(píng)估 C++ 是否適合您。具體來講,您應(yīng)該在開發(fā)過程的早期測(cè)試 C++ 構(gòu)造,以確保這些構(gòu)造不會(huì)導(dǎo)致本文所述的問題,或者違背內(nèi)核模式驅(qū)動(dòng)程序的編寫原則。
|
• |
本文討論的一些問題可能直到開發(fā)結(jié)束時(shí)才會(huì)變得明朗,而且解決這些問題可能需要完全重寫代碼。
|
• |
一些非常嚴(yán)重的問題很難在測(cè)試驅(qū)動(dòng)程序時(shí)根據(jù)需要重現(xiàn),所以,具有內(nèi)在的不可靠性的驅(qū)動(dòng)程序也許能夠在預(yù)期的時(shí)間段運(yùn)行良好,但是會(huì)隨機(jī)地出現(xiàn)失敗。這進(jìn)一步增加了仔細(xì)分析的要求。
|
細(xì)心編碼和仔細(xì)檢查生成的代碼可以避免許多問題。也有一些問題非常難以克服。所有這些問題都需要開發(fā)人員格外小心和仔細(xì)分析。