第一章:概念及工具

xslidian發表於2013-01-23

本章中,我們將介紹 Microsoft Windows 作業系統最核心的概念及術語——如 Windows API、程式、執行緒、虛擬記憶體、核心模式及使用者模式、物件、控制程式碼、安全及登錄檔——這些概念將貫穿全書。我們還會介紹一些可以探索 Windows 內部體系的工具,如核心偵錯程式、“效能監視器”、以及 Windows Sysinternals 網站(www.microsoft.com/technet/sysinternals)提供的一些重要工具。此外,我們還將講解如何利用 Windows 驅動工具包(WDK)和 Windows 軟體開發工具包(Windows SDK)提供的資源,深入瞭解 Windows 內部體系的情況。

請務必吃透本章——本書其餘部分的編撰均以此為前提。

Windows 作業系統版本

本書講解的內容會涉及到最新版本的 Microsoft Windows 客戶機及伺服器版作業系統:Windows 7(32 及 64 位版本)和 Windows Server 2008 R2(僅 64 位版本)。除非特別註明,內容適用於所有版本。表 1-1 列出了這些 Windows 產品的名稱、內部版本號及其釋出日期。

**表 1-1:**Windows 作業系統已釋出版本

產品名稱內部版本號釋出日期
Windows NT 3.13.11993年7月
Windows NT 3.53.51994年9月
Windows NT 3.513.511995年5月
Windows NT 4.04.01996年7月
Windows 20005.01999年12月
Windows XP5.12001年8月
Windows Server 20035.22003年3月
Windows Vista6.0 (Build 6000)2007年1月
Windows Server 20086.0 (Build 6001)2008年3月
Windows 76.1 (Build 7600)2009年10月
Windows Server 2008 R26.1 (Build 7600)2009年10月

 

注:產品“Windows 7”名稱中的“7”不代表內部版本號,而是換代所編的序號。為儘可能減少應用程式的相容性問題,Windows 7 的版本號實際上是 6.1,如表 1-1 所示。這樣那些檢查主版本號的應用程式會和在 Windows Vista 上的表現一致。 事實上,Windows 7 與 Windows Server 2008 R2 的版本號與內部編號(build number)完全相同,因為它們是從同一版本的 Windows 程式碼庫構建而來的。

基本概念及術語

基本概念及術語

部分讀者可能並不熟悉本書中的一些結構與概念。 在本節中,我們將定義整本書會用到的一些術語。您應當先熟悉它們,然後再繼續閱讀之後的章節。

Windows API

Windows 應用程式程式設計介面(Windows API)是用來對 Windows 系列作業系統進行使用者模式系統程式設計的介面。 在 64 位 Windows 還沒釋出以前,32 位 Windows 作業系統的程式設計介面被稱作“Win32 API”,以便與原來為 16 位系統程式設計準備的 16 位 Windows API 區別開來。 在本書中,“Windows API”這一術語同時指代 32 位及 64 位的 Windows 程式設計介面。

注:**Windows API 的說明請參閱 Windows 軟體開發工具包(SDK)文件。(參見本章的“Windows 軟體開發工具包”一節。)該文件可在 www.msdn.microsoft.com 免費線上瀏覽。微軟開發者網路(MSDN,微軟為開發者準備的支援專案)所有級別的訂戶也可以免費獲取。詳情請見 www.msdn.microsoft.com。 Jeffrey Richter 與 Christophe Nasarre 所著《**Windows 核心程式設計(第五版)》(Windows via C/C++, Fifth Edition,Microsoft Press 2007 年出版,中譯本由清華大學出版社 2008 年出版)一書中有關於利用 Windows 系統 API 進行程式設計的出色講解。

Windows API 由數千組可呼叫的函式組成。這些函式主要分為以下幾類:

  • 基本服務
  • 元件服務
  • 使用者介面服務
  • 圖形及多媒體服務
  • 訊息傳遞及協作
  • 網路通訊
  • Web 服務

本書著重講解關鍵了基礎服務,如程式與執行緒、記憶體管理、輸入/輸出(I/O)以及安全。

.NET 簡介

Microsoft .Net Framework(←專有術語,通常不作翻譯)由稱為框架類庫(FCL)的類庫和通用語言執行時(CLR)組成,後者提供受管控的程式碼執行環境,具備及時編譯(just-in-time compilation)、型別驗證、垃圾收集以及程式碼訪問許可權管理等功能。通過提供這些功能,CLR 提供的開發環境既可以提高程式設計師的生產力,又可以減少常見的程式設計錯誤。Jeffrey Richter 所著《CLR via C#(第3版)》(Microsoft Press 2010 年出版,同年中譯版由清華大學出版社出版)一書對 .NET Framework 及其核心架構有出色的講解。

CLR 以經典的 COM 服務程式實現:其程式碼位於標準的使用者模式 Windows DLL 當中。事實上,.NET Framework 的所有元件均以標準使用者模式 Windows DLL 的形式實現,對不受管控的 Windows API 函式進行了一層封裝。(.NET Framework 沒有任何一個元件在核心模式下執行。) 圖 1-1 展示了這些元件之間的關係:

使用者模式
(受管控程式碼)
.NET 應用程式
(標準使用者模式 EXE)
框架類庫指令集
(標準使用者模式 DLL)
使用者模式
(不受管控程式碼)
CLR DLL
(COM 服務程式)
Windows API DLL
核心模式 Windows 核心

圖 1-1:.NET Framework 各元件之間的關係

 

Win32 API 的歷史

有意思的是,Win32 原先並非作為 Windows NT 的程式設計介面而開發。由於 Windows NT 專案是作為 OS/2 第二版的替代品啟動開發的,其主要程式設計介面是 32 位的 OS/2 演示管理器1 API。然而,專案開展一年後 Microsoft Windows 3.0 上市,銷量節節攀升。於是微軟改變了方向,將 Windows NT 作為 Windows 家族產品未來的替代品,而不再針對 OS/2。制定 Windows API 的需求正是在這時凸顯——在此之前的 Windows 3.0 中,API 僅僅作為 16 位介面存在。

儘管 Windows API 提供的很多新函式(功能)還不能在 Windows 3.1 上呼叫,微軟決定讓新的 API 向下與 16 位 Windows API 相容,只要可能,不論是函式名稱、語義或是資料型別全部統一,以此減輕將現有 Windows 應用程式移植到 Windows NT 的難度。這便是很多函式名稱與介面看起來不一致的緣由:為確保當時較新的 Windows API 與舊的 16 位 Windows API 相相容。

服務、函式及例程

Windows 使用者和程式設計文件中的部分術語,根據上下文的不同,其含義也不盡相同。 例如,服務service)一詞可以指作業系統中可呼叫的例程,也可以指裝置驅動程式,或是服務程式程式。 下面列出了本書中特定術語的含義:

  • Windows API 函式:Windows API 中有文件說明的可呼叫子例程。例如 CreateProcessCreateFileGetMessage
  • 原生系統服務(或系統呼叫):作業系統中無文件說明的底層服務,可從使用者模式呼叫。例如 NtCreateUserProcess 是 Windows CreateProcess 函式呼叫的內部系統服務,可建立新的程式。關於系統呼叫的定義,請參見第三章“系統機制”中的“系統服務排程”一節。
  • 核心支援函式(或例程):Windows 作業系統內部的子例程,只能從核心模式(本章後續部分有定義)呼叫。例如,ExAllocatePoolWithTag是裝置驅動程式呼叫以便從 Windows 系統堆(亦稱“池”pools)分配記憶體的例程。
  • Windows 服務:由 Windows 服務控制管理器啟動的程式。例如以使用者模式程式執行的“計劃任務”服務,它支援 at 命令(與 UNIX 命令 atcron 類似)。(注:儘管登錄檔將 Windows 裝置驅動程式定義為“服務”,但本書中我們不會這樣提。)
  • 動態連結庫(DLL):一組可呼叫的子例程連結在一起,形成可由需要的應用程式動態載入的二進位制檔案。例如 Msvcrt.dll(C 執行時庫)與 Kernel32.dll(Windows API 子系統庫之一)。Windows 使用者模式元件及應用程式廣泛使用 DLL。DLL 相對於靜態庫的優勢在於,應用程式可以共享 DLL,並且 Windows 會確保在所有引用同一個 DLL 的應用程式的記憶體中,該 DLL 程式碼的副本只有一份。注意不可執行的 .NET 指令集也編譯為 DLL,但不含任何匯出子例程,而是由 CLR 解析已編譯的後設資料,以此訪問相應的型別與成員。

程式、執行緒與業務

儘管程式與程式表面上看起來比較相似,它們有根本上的區別。 程式program)是靜態的指令集,而程式process)是執行程式例項時所使用資源集合的容器。 從最抽象的層面看,一個 Windows 程式包括:

  • 一段私有的虛擬地址空間,該程式可以使用的一組虛擬記憶體地址
  • 一段可執行程式,定義了最初的程式碼及資料,對映至程式的虛擬地址空間
  • 一組可訪問各類系統資源的開放控制程式碼——如訊號量、通訊埠和檔案——可供程式內所有執行緒訪問
  • 一段稱為訪問令牌access token)的安全上下文,指明使用者、安全分組、特權、使用者賬戶控制(UAC)虛擬化狀態、會話以及與該程式關聯的限制使用者賬戶狀態
  • 一個稱為程式 IDprocess ID,簡稱 PID)的唯一識別符號(在程式內部,ID 的一部分稱為客戶 IDclient ID))
    • 至少一個執行執行緒(儘管“空”程式可以存在,但沒有任何用處)

每個程式同時會指明其上級2程式或建立者程式。即使上級程式不再存在,該資訊也不會得到更新。因此,允許程式指向不存在的上級程式。這不會造成任何問題,因為沒有東西需要實時更新的程式樹資訊。 ProcessExplorer 這款軟體會同時考慮上級程式的啟動時間,以免因 PID 重新分配,而將子程式排在新程式之下。下面的實驗將演示本特性。

實驗:檢視程式樹

上級程式或建立者程式的 ID 是每個程式的一項唯一屬性,不過大多數工具不會去顯示。您可以通過“效能監視器”(或以程式設計的方式)查詢“建立程式 ID”。Tlist.exe 工具(Windows 除錯工具包中有提供)可以通過 /t 開關顯示程式樹。下面是 tlist /t 輸出內容的例子:

C:\>tlist /t  
System Process (0) 
System (4) 
  smss.exe (224) 
csrss.exe (384) 
csrss.exe (444) 
  conhost.exe (3076) OleMainThreadWndName 
winlogon.exe (496) 
wininit.exe (504) 
  services.exe (580) 
    svchost.exe (696) 
    svchost.exe (796) 
    svchost.exe (912) 
    svchost.exe (948) 
    svchost.exe (988) 
    svchost.exe (244) 
      WUDFHost.exe (1008) 
      dwm.exe (2912) DWM Notification Window 
    btwdins.exe (268) 
    svchost.exe (1104) 
    svchost.exe (1192) 
    svchost.exe (1368) 
    svchost.exe (1400) 
    spoolsv.exe (1560) 
    svchost.exe (1860) 
    svchost.exe (1936) 
    svchost.exe (1124) 
    svchost.exe (1440) 
    svchost.exe (2276) 
    taskhost.exe (2816) Task Host Window 
    svchost.exe (892) 
  lsass.exe (588) 
  lsm.exe (596) 
explorer.exe (2968) Program Manager 
  cmd.exe (1832) Administrator: C:\Windows\system32\cmd.exe - "c:\tlist.exe"  /t 
    tlist.exe (2448)

列表中的每個程式均以縮排的形式顯示其上級/子程式關係。上級程式不再存在的程式靠左對齊(如本例中的 explorer.exe),因為即使上上級程式仍然存在,也不可能找到這種跨級關係。Windows 只維持建立者程式 ID,而非完整的建立者追溯鏈。

要演示 Windows 不保留上級以上的程式 ID 這一事實,請按下述步驟操作:

  1. 開啟“命令提示符”視窗。
  2. 輸入 title 上級程式(將視窗標題改為“上級程式”)。
  3. 輸入 start cmd(將開啟第二個命令提示符)。
  4. 在第二個命令提示符下輸入 title 子程式
  5. 開啟“工作管理員”。
  6. 在第二個命令提示符下輸入 mspaint(將執行微軟“畫圖”工具)。
  7. 回到第二個命令提示符視窗,輸入 exit。(注意“畫圖”仍然繼續執行。)
  8. 切換到“工作管理員”視窗。
  9. 單擊“應用程式”標籤頁。
  10. 右擊“上級程式”這項任務,並選擇“轉到程式”。
  11. 右擊這個 cmd.exe 程式,並選擇“結束程式樹”。
  12. 點選“工作管理員”確認對話方塊中的“結束程式樹”按鈕。

這時第一個命令提示符視窗將消失,但您應仍能看到“畫圖”視窗,因為它是剛才所結束程式的子程式的子程式;因為中間程式(“畫圖”的上級程式)已經結束,所以最上級程式與它子程式的子程式不再有關聯。

有很多工具可以檢視(或修改)程式及其資訊。 下面的實驗演示了部分工具以多種檢視顯示的程式資訊。其中不少工具是 Windows 自帶的,或在 Windows 除錯工具和 Windows SDK 提供,其他則是 Sysinternals 提供下載的獨立工具。 這些工具中有很多會顯示核心程式及執行緒資訊的交叉子集,有時以不同名稱區分。

檢查程式活動最通用的工具可能就是“工作管理員”了。(由於 Windows 核心並沒有所謂的“任務”,“工作管理員”這個名字有點讓人莫名其妙。)下面的實驗將展示“工作管理員”所列出的應用程式列表與程式列表的區別。

實驗:通過“工作管理員”檢視程式資訊

~~~

Sysinternals 釋出的 Process Explorer 可以顯示比其他工具更詳盡的程式與執行緒的細節情況,因此本書中會有相當一部分實驗用到它。 Process Explorer 可以顯示的“獨家情報”,或者說它的獨特功能包括:

  • 程式安全令牌(如使用者組及特權列表,以及虛擬化狀態)
  • 高亮顯示程式及執行緒列表中的變化
  • 列出服務託管程式內部的服務列表,包括其顯示名稱及描述
  • 屬於一項任務的程式,以及任務的詳細資訊
  • 託管 .NET 應用程式的程式以及 .NET 相關的詳細資訊(如 AppDomain 列表、已載入指令集列表,以及 CLR 效能計數器)
  • 程式及執行緒的啟動時間
  • 完整的對映到記憶體的檔案列表(不僅僅是 DLL 檔案)
  • 能夠暫停程式或執行緒
  • 能夠單獨結束執行緒
  • 輕鬆判斷哪些程式在一段時間內消耗的 CPU 時間最多(“效能監視器”可以顯示選中的一組程式的 CPU 利用率,但不會自動顯示效能監視會話開始後建立的程式——只能通過手動跟蹤其二進位制格式輸出的檔案實現。)

Process Explorer 還可以方便地同時顯示一些資訊,如:

  • 程式樹(允許展開/收縮樹的每一層)
  • 程式的開放控制程式碼(包括未命名控制程式碼)
  • 程式中 DLL(及對映到記憶體的檔案)的列表
  • 程式中的執行緒活動
  • 使用者模式及核心模式的執行緒棧(包括 Dbghelp.dll 提供的地址向名稱對映功能,該庫隨 Windows 除錯工具釋出)
  • 通過執行緒週期統計得出的更精確的 CPU 佔用百分比(可精確表示 CPU 活動情況,將在第五章“程式與執行緒”中講解)
  • 獨立級別(Integrity level)
  • 記憶體管理器提供提交消耗峰值、核心記憶體已分頁及未分頁池限制等詳細資訊(其他工具只能顯示當前大小)

下面是 Process Explorer 的入門實驗。

實驗:通過 Process Explorer 顯示程式詳細資訊

~~~

執行緒thread)是程式中的實體,Windows 可規劃其執行順序。如果沒有執行緒,程式的程式將無法執行。執行緒包括下述基本元件:

  • 一組 CPU 暫存器的內容,代表處理器的狀態。
  • 兩個棧——一個在核心模式執行時使用,另一個用於使用者模式。
  • 一段稱為執行緒本地儲存thread-local storageTLS)的私有儲存區域,可由子系統、執行時庫及 DLL 使用。
  • 一個稱為執行緒 IDthread ID)的唯一識別符號(屬於稱為“客戶 ID”(client ID)的內部結構的一部分——程式 ID 與執行緒 ID 由相同的名字空間生成得出,因此它們不會有重疊)。
  • 執行緒有時也有自己的安全上下文,或稱令牌,通常被多執行緒伺服器應用程式用來模擬它們所服務客戶的安全上下文。

暫存暫存器、棧及私有儲存區域稱為執行緒的上下文context)。由於此資訊隨 Windows 所處機器架構的不同而不同,上下文的結構必然是架構相關的。Windows 提供的 GetThreadContext 函式可訪問架構相關的資訊(稱為 CONTEXT 區塊)。

**注:**32 位應用程式在 64 位 Windows 執行時建立的執行緒會同時包含 32 位與 64 位上下文,Wow64 在需要時會通過上下文從 32 位模式切換到 64 位模式執行。這樣的執行緒會有兩個使用者棧、兩個 CONTEXT 區塊,而通用的 Windows API 函式則將返回 64 位上下文。不過 Wow64GetThreadContext 函式會返回 32 位上下文。關於 Wow64 的詳細資訊請參見第三章。

 

纖程與使用者模式規劃執行緒

~~~

儘管執行緒有自己的執行上下文,程式中的每個執行緒共享該程式的虛擬地址空間(此外還有其他屬於該程式的資源),也就是說程式中的所有執行緒對程式的虛擬地址空間有完全的讀寫許可權。執行緒不可以任意引用其他程式的地址空間,除非其他程式將其私有地址空間的一部分作為共享記憶體區段(Windows API 中稱為檔案對映物件)或者一個程式有許可權使用跨程式記憶體函式(如 ReadProcessMemoryWriteProcessMemory)開啟其他程式(的記憶體)。

除了私有地址空間、一或多個執行緒以外,每個程式還具有安全上下文,以及一系列指向核心物件(如檔案、共享記憶體區段或同步物件,如互斥鎖、事件、訊號量)的開放控制程式碼,如圖 1-2 所示。

程式物件(訪問令牌) → 虛擬地址描述符(VAD)
↓ ↘
執行緒(訪問令牌) 控制程式碼表(控制程式碼→物件)

圖 1-2:程式及其資源

每個程式的安全上下文儲存在稱為訪問令牌access token)的物件中。程式的訪問令牌包含該程式的安全識別符號及憑據。預設情況下,執行緒沒有自己的訪問令牌,但它們可以申請取得,這樣執行緒也可以模擬其他程式——包括遠端 Windows 系統中的程式——的安全上下文,而不會影響程式中的其他執行緒。(關於程式及執行緒安全的詳細情況,請參見第六章“安全”。)

虛擬地址描述符virtual address descriptorVAD)是記憶體管理器用於跟蹤程式所使用的虛擬地址的資料結構。第二部分第十章有這些資料結構的深入講解。

Windows 還為程式模型提供了擴充套件模型,稱為業務job)。業務物件的主要功能是可以將一組程式作為一個整體管理與操作。業務物件允許控制特定屬性,還可以限制單個程式或與該業務關聯的程式。它還會為與業務關聯的所有程式以及與業務關聯但已結束的程式記錄基本的核算資訊。 在一定程度上,業務物件彌補了 Windows 沒有結構化的程式樹的缺陷,但同時也使它在一些情況下更勝 UNIX 式程式樹一籌。

第五章將進一步講解業務、程式與執行緒的內部結構,程式機制與執行緒建立,以及執行緒規劃演算法。

虛擬記憶體

Windows 通過平面(線性)地址空間實現虛擬記憶體系統,可為每個程式提供假想的獨立、大範圍、私有地址空間。虛擬記憶體所反映的記憶體邏輯檢視與其物理佈局往往不相一致。執行時,記憶體管理器在硬體的幫助下,將虛擬地址翻譯——或者說對映map)——為實體地址,後者才是資料實際存放的位置。通過控制保護與對映機制,作業系統可以確保各程式不相互衝突,也不會覆蓋作業系統自己的資料。圖 1-3 展示了虛擬記憶體中三個相鄰分頁對映到實體記憶體中三個不相連的分頁的過程。

虛擬記憶體 ⇛ 實體記憶體

圖 1-3:由虛擬記憶體向實體記憶體對映

由於大多數系統的實體記憶體遠不如正在執行的程式所用到的總虛擬記憶體來得大,於是記憶體管理器將一部分記憶體中的內容傳輸——或者說分頁page)——至硬碟。將資料分頁至硬碟可以騰出實體記憶體空間,供其他程式或作業系統自身使用。當執行緒訪問到已被分頁至硬碟的虛擬地址時,虛擬記憶體管理器將資訊從硬碟載入回到記憶體。在硬體支援的輔助下,記憶體管理器無需區分程式或執行緒,也不需要它們的協助便可完成分頁,因此應用程式不需要作任何變動即可享受到分頁帶來的優勢。

虛擬地址空間的大小隨硬體平臺的不同而不同。在 32 位 x86 系統下,總虛擬地址空間的理論最大值為 4GB。預設情況下,Windows 將此地址空間的一半(4GB 虛擬地址空間中地址值較小的一半,即 0x00000000 ~ 0x7FFFFFFF)分配給程式,供其獨立私有儲存使用,並將另一半(地址值較大的一半,即 0x80000000 ~ 0xFFFFFFFF)作為自身的作業系統保護記憶體使用。 低半段的對映會隨著當前正在執行的程式的虛擬地址空間的變化而變化,而高半段的對映總與作業系統的虛擬記憶體一致。 Windows 支援設定引導時選項(boot-time option)(第二部分第 13 章“啟動與關機”講到的“引導配置資料庫”中的 increaseuserva 限定符)授予執行特別標註過的程式(大地址空間相關標記必須在可執行映象的頭部設定)的程式使用高達 3GB 私有地址空間(為作業系統保留 1GB)的資格。該選項允許資料庫伺服器等應用程式在程式地址空間中保留大量資料,從而減輕對映資料庫子集檢視的需要。 圖 1-4 展示了 32 位 Windows 所支援的兩種典型虛擬地址空間佈局。(increaseuserva 選項允許特別標註過的應用程式使用從 2GB 到 3GB 的任意地址。)

預設 3GB 使用者地址空間
2GB 使用者程式空間 3GB 使用者程式空間 2GB 系統空間 1GB 系統空間

**圖 1-4:**32 位 Windows 典型地址空間佈局

儘管 3GB 的虛擬地址空間比 2GB 略大,但還是不足以對映超大型的(數 GB)資料庫。為解決這種情況下的編址需求,Windows 提供了地址視窗投影擴充套件Address Windowing ExtensionAWE)機制,可為 32 位應用程式分配高達 64GB 的實體記憶體,然後將檢視——或者說“視窗”——對映到 2GB 的虛擬地址空間。儘管 AWE 的使用為程式設計師管理虛擬-實體記憶體對映帶來了不便,這一機制確實能夠為超出 32 位程式地址空間所能對映範圍之外的記憶體地址提供直接訪問。

64 位 Windows 為程式提供了更大的地址空間:IA-64 系統為 7 152 GB,而 x64 系統為 8 192 GB。圖 1-5 展示了 64 位系統地址空間佈局的簡化檢視。(詳細描述請見第二部分第 10 章。)注意這裡給出的大小數值並不是這些平臺的架構限制。64 位二進位制數的編址空間超過 17 000 000 000 GB,但目前 64 位硬體將地址限制在較小的數值上。Windows 在當前 64 位版中採用的實現方案進一步降低了標準,限制值為 8 192 GB(合 8TB)。

x64 IA-64

8 192 GB (8TB) 使用者程式空間 7 152 GB (7TB) 使用者程式空間

8 192 GB 系統空間 7 152 GB 系統空間

**圖 1-5:**64 位 Windows 的地址空間佈局

關於記憶體管理器實現的詳細情況,包括地址轉換如何進行,以及 Windows 如何管理實體記憶體,將在第二部分第 10 章介紹。

核心模式與使用者模式

終端服務與多會話支援

物件與控制程式碼

安全

登錄檔

Unicode

譯註:
  1. Presentation Manager,簡稱 PM,由 IBM 與微軟聯合開發的視窗管理介面,可實現視窗浮動堆放。開發代號為 Winthorn。
  2. 又譯“父”。
  3. 慎重改動的片語:特權 privilege、上級 parent、XX“相關”(對應 -specific)、應用程式(Win8 才有“應用”的概念)、規劃 schedule(r)(又譯“排程”,例外:“計劃任務”元件 Task Scheduler)、區塊 block

相關文章