探究Windows核心你知多少)

來源:互聯網
上載者:User

Windows核心

      如上所述,現代作業系統的一個明顯特徵就是使用者空間和系統空間的劃分,從UNIX時代以來,人們一直把存在於系統空間的代碼和資料的集合稱為“核心(Kernel)”,因此核心是有明確邊界的。空間的不同,或者說CPU運行模式(系統態和使用者態)的不同,是不會被混淆的本質區別。可是,在Windows的術語中卻不同,微軟並不把系統空間的所有代碼和資料的集合稱為核心,而是把這裡面的一部分,即比較低層、與硬體靠得最近因而最為核心的一部分稱為“核心”,即Kernel。實際上,這也反映了當初微軟在決策上的舉棋不定,因為微軟稱為Kernel的那一部分大致上相當於一個微核心。有些資料甚至據此而認定Windows核心為“微核心”,殊不知現今的Windows核心恐怕是最宏的“宏核心”,因為連圖形介面和視窗機制的實現也在核心裡面了。微軟的文獻把同在系統空間但在所謂“Kernel”以上的部分稱為Executive。國內有些資料把Executive譯為“執行體”,其實是不妥當的。試想,有“執行體”,莫非還有“不執行體”嗎?實際上,在英語中,特別是在企業管理的語境中,Executive是“管理層”、“企業高管”的意思,所以微軟其實是把核心分成了兩大層,其中的低層或者說核心部分稱為“核心”,而高層則稱為“管理層”。管理層中有些什麼呢?讀者在後面將看到,裡面有“對象管理”、“記憶體管理”、“進程/線程管理”、“I/O管理”、“安全管理”、“進程(線程)間通訊”等模組。但是,這麼一來,所謂“核心”的邊界就變得不很清晰了。
      相比之下,還是像UNIX/Linux那樣,以系統空間與使用者空間的劃分為界,把存在於系統空間的所有成分的集合統稱為核心比較清晰,也更為科學(因為有明確的判定方法)。所以,在本書中,只要沒有特別加以說明,“核心”就是“系統空間”的同義字,而不是特指微軟所稱的那個核心;而微軟所稱的核心,則在本書中稱為核心中的“核心層”。核心中從高到低在邏輯上分成若干層次,這一點任何作業系統的核心都是如此。事實上,研究作業系統的幾種觀點(視野)之一就是分層類比、分層提供服務的觀點。即便是微核心的內部,也還是分出層次,其中最底層的就是“硬體抽象層(Hardware Abstraction Layer)”HAL。
    不過作業系統的概念有狹義和廣義之分。狹義的作業系統就是指核心,而廣義的作業系統則並不只是一個核心,也包括一些使用者空間的軟體。例如,一些工具性的軟體、實現人機介面的軟體(例如Linux上的Shell,Windows上的“資源管理員”),就一般都認為屬於作業系統的範疇。Windows作業系統原先的設計目標是支援三種“子系統”。其中用來實現視窗子系統的服務程式,即服務進程Csrss所執行的軟體,當然也應該屬於作業系統。此外,應用軟體在運行時需要用到一些函數庫,特別是“動態串連庫”即DLL,其中靠近核心的“系統DLL”,也應屬於作業系統。這樣,包括應用軟體在內的整個Windows系統的結構。
    圖中畫出了系統的結構層次,愈往上愈接近應用軟體,愈往下愈接近硬體。而包括核心在內的所有中介層次的作用,則是協助應用軟體更好、更安全、更方便、更有效地利用包括CPU在內的硬體資源。
    位於最高層的是應用軟體,就是一般副檔名為.exe的可執行程式。除.exe可執行程式之外,應用軟體的開發人員和提供者可以(但並非必須)選擇把其中的一部分基礎性的功能放在若干“動態串連庫”DLL裡面,這就是副檔名為.dll的可執行程式。DLL也是分層次的,相對高層的DLL依賴於相對低層的DLL,調用由低層DLL所提供的服務。
    在Windows系統中,應用軟體是看不見作業系統核心的,應用軟體通過一個“應用程式(設計)介面”Win32 API獲得Windows作業系統的支援。微軟通過一系列的DLL來實現其API,其中低層、比較靠近核心的幾個DLL稱為“系統DLL”,意思是這些DLL已經屬於作業系統的範疇。其中最靠近核心、最基本的DLL是ntdll.dll,此外還有kernel32.dll、user32.dll。這樣,所謂Windows作業系統,至少應該是核心加上系統DLL還有子系統服務進程的總和。當然,其中最重要、最複雜的是核心。
    圖中的水平粗線表示使用者空間和系統空間的分界。在Windows作業系統中,整個4GB的虛存地址空間被對半分成兩塊,從地址0x80000000開始向上是“系統空間”,就是核心所在的地方,下面則是“使用者空間”,是應用程式所在的地方。後面將講到,系統空間是全域的,而使用者空間只屬於具體的進程,每個(使用者)進程都有自己的使用者空間。
    除表示使用者空間和系統空間的分界外,圖中的水平粗線還有另一層意思,就是表示Windows的“應用程式二進位介面”,即ABI。ABI一方面是系統調用的介面,定義了所有系統調用函數介面的集合,並規定了系統調用如何進行;另一方面也規定了可執行程式檔案的結構和格式。Windows的系統調用介面是不公開的,現在人們所知道的有關資訊基本上來自有關的研究和實驗,特別是來自逆向工程的研究。
    CPU必須進入“系統態”才能執行存放在系統空間的程式,訪問存放在系統空間的資料。而對使用者空間的資料則不論處於“系統態”或“使用者態”都能訪問。但是CPU怎樣才能進入“系統態”,即進入核心呢?只有三種途徑。第一種是“系統調用”,第二種是“中斷”,第三種是“異常”。所以圖中核心的最上層是“系統服務”,即系統調用的介面。圖中還畫上了中斷和異常的入口,這是因為通過中斷或異常進入核心時的一些系統開銷性質的操作與系統調用相似。但是,從邏輯的角度講,中斷和異常的入口應該是在核心的底部,因為中斷和異常都來自(包括CPU在內的)硬體。
    到了核心中,系統調用介面的下面就是Executive,即核心的管理層,管理層的下面又具體分為對象管理、記憶體管理、進程管理、安全管理、I/O管理等模組。在微軟的術語中,這些管理模組稱為Manager,即“管理者”或“主管”,例如對象管理模組就稱為“Object Manager”,意為“高管(Executive)”領導下的部門主管。在特定的語境下,也有把這些模組稱為“子系統”的,讀者需要注意分辨,不要跟“Windows子系統”、“POSIX子系統”和“OS/2子系統”相混淆。值得注意的是,這些模組雖然在管理層,但是它們所管理的目標和操作並不局限在管理層內部,就好像具體的管理部門雖然在公司總部,但是所管理的具體操作卻可能在基層的營業所或辦事處。例如記憶體管理,其上層在管理層中,其底層的操作卻可能在HAL層中。所以,層次是橫向的概念,而模組是縱向的概念。
    再往下就是微軟所稱的“核心(Kernel)”了。所以微軟所稱的“核心”其實是核心中較為核心的、比較接近底層的一層。這一層中包含了跟裝置驅動底層中斷處理、異常處理等有關的功能。這下面就是“硬體抽象層”HAL了。
    當CPU運行於核心中時,儘管都是在核心中,有些操作只允許在特定的層次上進行,或者只允許針對特定的層次進行。例如在中斷處理的內部就不允許線程切換,線程切換隻有在完成了中斷處理以後才能進行。所以,Windows核心規定只有在從所謂的“核心”層(而不是整個核心)退出來時才可以進行線程切換。
線上程調度/切換的問題上,有兩種不同的方式:一種稱為“剝奪式(Preemptive)”,或稱“搶佔式”,意思是只要有優先順序更高的線程就緒,哪怕CPU已經在為別的優先順序較低的線程所用而且無意主動讓路,也要立即把它奪過來;另一種是“非剝奪式(Non-Preemptive)”,或稱“不搶佔式”,如果CPU已經在執行別的線程,就先忍一忍,等待適當的機會。什麼機會呢?就是等正在系統空間啟動並執行線程返回使用者空間的時候。可是,優先順序較高的線程之所以變成就緒,其觸發的條件也必為系統調用、中斷或異常其中之一,總之是在CPU運行於系統空間的時候變成就緒的。所以,所謂“剝奪式”和“非剝奪式”,其實不是剝奪不剝奪的問題(那取決於調度策略),而是什麼時候剝奪的問題。2.6版以前的Linux主要面對分時應用,所以是不剝奪的,其特徵就是要到CPU從核心退回使用者空間時才進行調度和切換。現在的Linux則更多地面向案頭應用,所以也是剝奪式的了。但是,即使是剝奪式的調度和切換,也不表示在核心中的任何一個角落都可以切換線程,例如在中斷服務程式中就不允許,所以這裡也有個時機的問題。如上所述,Windows核心規定只有在從所謂的“核心”退出來時才可以進行線程切換。那麼Windows的線程調度/切換是剝奪式的還是不剝奪式的呢?微軟一直聲稱是剝奪式的(後面讀者將看到確實是),但是又說只有在從“核心”退出來時才可以進行線程切換,這就引起了質疑和困惑,因為當時很多人都把從“核心”退出來理解成從整個核心退出來而返回使用者空間的時候。可見,一方面不公開技術細節,另一方面對術語的使用又與人不同,這就很容易引起困惑。
    與Linux相比,Windows的核心還有個明顯的不同,就是其核心的相當一部分頁面是可倒換的。後面讀者將看到,記憶體管理即虛存技術的重要內容之一是物理頁面的倒換,就是可以將已經有映射但是暫時不受到訪問的頁面倒換到外存中去,到實際受到訪問時再倒換進來,使外存成為記憶體的擴充。但是,在Linux中,屬於核心的頁面無論是用於代碼還是資料(用於檔案內容緩衝的除外),是不受倒換的。這一方面是為了簡化核心的設計和實現,一方面也是因為覺得價值不大,因為核心畢竟是全域的,所有進程都公用同一個系統空間,即使要倒換也油水不大。但是,Windows的核心卻不同,其相當一部分頁面是可倒換的。究其原因,另一方面可能是來自VMS的影響,另一方面也可以從Windows核心的體積得到一些解釋。如前所述,微軟把圖形操作/視窗服務的實現也搬到了核心中,這就使核心的體積增加了很多。再說每個線程的系統空間堆棧也因此而增大了許多,當線程數量很大時也確實不可忽視。所以,在這樣的情況下,使核心的部分頁面成為可倒換的確實有其合理的一面。不過當然不是所有頁面都可倒換,有些頁面註定是不可倒換的。例如,與中斷和異常有關的代碼和資料所在的頁面,以及與頁面倒換有關的代碼和資料所在的頁面,就顯然是不能被倒換出去的,否則就無法把這些頁面倒換回來了。這樣,我們就大致可以推斷,Windows核心中“核心”層及其以下應該是不允許倒換的。
    與此相關,Windows為CPU的運行狀態定義了許多“IRQ層級”,即IRQL。在任一時間中,CPU總是運行於其中的某一個層級,這個層級就表明了什麼事情可以做、什麼事情不可以做。下面是這些層級的定義:
#define PASSIVE_LEVEL                          0
#define LOW_LEVEL                             0
#define APC_LEVEL                              1
#define DISPATCH_LEVEL                        2
#define PROFILE_LEVEL                         27
#define CLOCK1_LEVEL                          28
#define CLOCK2_LEVEL                          28
#define IPI_LEVEL                               29
#define POWER_LEVEL                           30
#define HIGH_LEVEL                             31
其基本的意圖是,如果CPU從而一個線程已經處於某個層級,其操作就不能受同級或更低層級的操作所幹擾。
    這裡的PASSIVE_LEVEL是層級最低的,但是卻對應著系統結構中較高的層次。當CPU運行於使用者空間,或者雖然進入了核心但還只是運行於管理層的時候,其運行層級就是PASSIVE_LEVEL。比其略高的是APC_LEVEL,那是在(核心中)為APC函數(見本書“進程與線程”一章)的執行進行準備時的運行層級,APC請求相當於對使用者空間程式的(軟體)中斷。注意IRQL在x86系統結構中並沒有硬體的支援(CPU中並沒有這麼一個寄存器)而只是一個變數。與CPU只能通過特殊的指令或中斷/異常才能進入系統態不同,IRQL是CPU可以自由設定的,每當CPU進入更底層、更核心的層次時就提高IRQL,反之則降低IRQL。不過,表明IRQL的變數在核心中,運行於使用者空間時是無法改變IRQL的。
再高一級是DISPATCH_LEVEL,這大致相當於CPU運行於Windows核心中的核心層,即“核心”層。線程的切換隻能發生於CPU行將從DISPATCH_LEVEL層級下降的時候。
    IRQL層級3及以上用於硬體中斷。顯然,設計者的意圖是採用中斷優先順序,即優先順序較高的中斷源可以中斷優先順序較低的中斷服務。但是x86的系統結構並不支援中斷優先順序,所以這實際上是來自VMS的遺迹,因為VAX和PDP的系統結構都是支援中斷優先順序的。
    回到頁面換出的問題上,只要CPU的IRQL層級不高於APC_LEVEL的層次,其代碼都是允許倒換的,但是從DISPATCH_LEVEL開始就不允許了。顯然,如果在這一點上搞錯了,後果是很嚴重的。所以在管理層的代碼中幾乎每個函數的開頭都要放上一個宏操作PAGED_CODE(),說明代碼作者的意圖是讓這個函數所佔的頁面可以被倒換出去。這個宏操作的定義如下:
#ifdef DBG
#define PAGED_CODE() { \
  if (KeGetCurrentIrql() > APC_LEVEL) { \
    KdPrint( ("NTDDK: Pageable code called at IRQL > APC_LEVEL (%d)\n",
                                                  KeGetCurrentIrql() )); \
    ASSERT(FALSE); \
  } \
}
#else
#define PAGED_CODE()
#endif
    在Debug模式下,這個宏操作檢查CPU當前的運行層級,如果發現高於APC_LEVEL就說明這個函數有可能在DISPATCH_LEVEL或更高的層級上受到調用,因而是不應該被倒換出去的,所以就發出警告。至於在正式啟動並執行版本中,則這個宏操作定義為空白。
當然,光是在程式中引用宏操作PAGED_CODE()不會使一個函數所在的頁面可倒換,真正使其可倒換的是編譯指示“#pragma alloc_text()”。例如NtQueryObject()中的第一行就是PAGED_CODE(),與此相應,這個函數所在的源檔案中就有這麼一行:
#pragma alloc_text(PAGE, NtQueryObject)
    正是這一行編譯指示讓編譯工具將為此函數產生的可執行代碼放在可被倒換的區間。
    在本書所引的代碼中,許多函數的開頭都有對PAGED_CODE()的引用,但是為了壓縮篇幅而將其省略了。此外,原來的代碼中為增加可讀性而插有一些空行,為壓縮篇幅也把它們刪去了。還有,在一些if語句中,也是為增加可讀性,許多人主張哪怕只有一行代碼也要加上前後花括弧。筆者很贊同這些主張,但是那樣一來就又得多佔兩行,所以書中有些地方把前後花括弧刪去了,這隻是為壓縮篇幅,而不表示筆者認為不應該使用這些花括弧。
    書中所引代碼中凡是黑體字的部分都是筆者覺得需要提醒讀者注意的,但是為不同目的而閱讀同一段代碼時的側重面可能不同,所以這隻是就一般的閱讀、就代碼的“主旋律”而言,並不表示別的代碼就不重要。

 

本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/broadview2006/archive/2009/05/13/4171397.aspx

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.