Windows 啟動過程
引言
啟動過程是我們了解操作系統的第一個環節。了解 Windows 的啟動過程,可以幫助我們解決一些啟動的問題,也能幫助我們了解 Windows 的整體結構。
以下內容將分為【加載內核】、【內核初始化】和【應用程序初始化】三個部分。
加載內核
如 啟動過程概覽 所示,加載過程分為兩種方式。傳統的 BIOS(Basic Input/Output System)和 UEFI(Unified Extensible Firmware Interface)。這兩種方式大致相似,本文以 BIOS 為例來說明引導說過。
加載 BIOS
當開機鍵被按下後,電源將執行初始化,確保電源可靠後,主板上的硬件電路給 CPU 發複位(reset)信號。
CPU 收到複位信號後,將從 RAM 中讀取指令執行。此時內存中是沒有任何指令需要執行的。於是 CPU 就去 BIOS ROM 中特定的位置(FFFF0H)中執行指令,這個指令是一個跳轉指令 ,將告訴 CPU 真正的 BIOS 程序所在的位置。跳轉指令如下:
jmp far f000:e05b ; 跳轉到0xfe05b執行
運行 BIOS
BIOS 主要包含以下四個部分的程序:
- POST:Power On Self Test。用於檢測硬件的可用性,如果有問題,會發出蜂鳴聲並且停止進行下一步。
- Bootstrap Loader:用來確定操作系統的位置,幫助引導操作系統的啟動。此程序會遍歷所有的存儲設備的0盤0道1扇區的內容,如果這一扇區最後兩位元組的內容是0x55 0xaa,則認為它是啟動區(也就是 MBR,Master Boot Record),並將扇區的內容(也就是ntldr)複製到內存0x7c00位置。至此,代碼的控制權就交給了操作系統代碼了。
Windows 的做法是,讓引導扇區中的代碼讀入其他扇區的數據,然後跳轉到下一個扇區的代碼區。這樣就可以不受單個引導扇區長度的限制,這種做法相當於將第一個引導扇區當做一個加載器(loader),而真正完成引導扇區功能的扇區隨後被加載進來並執行。這一過程對於 MBR 是透明的,從而保持良好的兼容性。
- BIOS:用於軟件和硬件的通信。
- CMOS Setup:一個配置程序,用於配置操作系統的時間、日期和密碼等
加載 NT 內核
此時代碼的控制權已經來到了 ntldr(也可以是 WinLoad 或者 os loader),此時處理器還運行實模式下,所以 ntldr 也分為兩部分:實模式代碼和保護模式代碼。
在實模式下,ntldr 的主要任務是:
- 完成在實模式下執行的初始化。比如清除鍵盤緩衝區。
- 為切換到保護模式做好基本的環境準備。
- 將處理器切換到保護模式下,然後將控制權交給保護模式下的代碼。
在保護模式下,需要完成以下步驟:
- 由於虛擬機制轉譯機制還未就緒,首先要做的就是把物理內存管理起來。採用一個內存描述符數組將每一段內存的大小和用途記錄下來,然後打開頁面映射機制
- 繼續執行其他的初始化工作。包含 I/O 設備的初始化等
- 從系統分區的根目錄下讀取 boot.ini 文件。並檢查是否存在有效的 hiberfil.sys 文件,如果存在,則將引導過程轉移到休眠系統的恢復過程。否則,將解析 boot.ini 文件 。
- 運行 NTDETECT.COM 程序。此程序運行在實模式下,它將利用 BIOS 來查詢系統的基本設備和配置信息,並收集起來。在引導後期,將其存放到註冊表 HKLM\HARDWARE\DESCRIPTION 下。
- 加載 ntoskrnl.exe、hal(默認為 hal.dll,具體是哪個映像文件,在 boot.ini 中會有記錄),再通過加載註冊表的 system 儲巢,即 WINDOWS\system32\config\system 文件 拿到哪些設備驅動程序必須被加載進來,然後再把必要的設備驅動加載進來。
- 此時加載的準備工作基本就緒,將準備的信息構造成一個參數塊 LOADER_PARAMETER_BLOCK.如下定義來自 WRK。
typedef struct _LOADER_PARAMETER_BLOCK {
LIST_ENTRY LoadOrderListHead; // 加載的模塊鏈表,每個元素都為 KLDR_DATA_TABLE_ENTRY
LIST_ENTRY MemoryDescriptorListHead; // 內存描述符鏈表,每個元素都為 MEMORY_ALLOCATION_DESCRIPTOR
LIST_ENTRY BootDriverListHead;// 引導驅動程序鏈表,每個元素都為 BOOT_DRIVER_LIST_ENTRY
ULONG_PTR KernelStack;// 內核棧頂
ULONG_PTR Prcb;// 進程環境,指向一個進程控制塊
ULONG_PTR Process;// 初始進程,EPROCESS
ULONG_PTR Thread;// 初始線程,ETHREAD
ULONG RegistryLength;// System 儲巢的長度
PVOID RegistryBase;// System 儲巢的基地址
PCONFIGURATION_COMPONENT_DATA ConfigurationRoot;// 配置樹,包含 ISA、磁盤和 ACPI 的配置數據
PCHAR ArcBootDeviceName;// 引導分區的 ARC 名稱
PCHAR ArcHalDeviceName;// 系統分區的 ARC 名稱
PCHAR NtBootPathName;// OS 目錄的路徑名稱,比如「\Windows」
PCHAR NtHalPathName;// OS 加載器的路徑名稱,比如「\」
PCHAR LoadOptions;// 引導選項,來自 boot.ini
PNLS_DATA_BLOCK NlsData;// 包含 ANSI 代碼頁、OEM 代碼頁和 Unicode 碼錶
PARC_DISK_INFORMATION ArcDiskInformation;// 所有磁盤的簽名結構
PVOID OemFontFile;// OEM 字體文件
struct _SETUP_LOADER_BLOCK *SetupLoaderBlock;// 網絡引導或文字模式安裝引導
PLOADER_PARAMETER_EXTENSION Extension;// 擴展部分
union {
I386_LOADER_BLOCK I386;
// ALPHA_LOADER_BLOCK Alpha;
// IA64_LOADER_BLOCK Ia64;
} u;
} LOADER_PARAMETER_BLOCK, *PLOADER_PARAMETER_BLOCK;
- 最後,ntlrd 將控制權交給 ntoskrnl.exe 的入口函數。表示內核加載完成。
內核初始化
到這裡,接下來的過程,我們就可以通過 WRK 來觀察具體的過程了。
ntoskrnl.exe 的入口函數是 _KiSystemStartup,可以順着這個方法往下看。
整個初始化分為兩個階段:Phase0 和 Phase1。
階段1的初始化入口是 Phase1InitializationDiscard 方法。此方法也是做了各種初始化,具體可以去看代碼。由於其時間比較長,所以內部做了一個進度估計。
待 階段1 完成初始化,執行體的各個組件都進入了一個正常狀態。但只有內核是沒有意義的,系統必須讓應用程序跑起來。應用程序的啟動就得依賴剛啟動的 smss 進程繼續了。
應用初始化
現在控制權來到了 smss,smss 是 Windows 中一個重要的進程。
smss 還會繼續完成引導過程。在內核初始化期間,只有 System 儲巢被加載到內存當中了。其他的就得由 smss 來加載。其他初始化必要的工作,可以參看 HKLM\SYSTEM\CurrentControlSet\Control\Session Manager 。
註冊表中能發現一些有趣的子鍵。
- 建立系統的全局環境變量,這些環境變量由 Environment 鍵下的值指定
- 運行在啟動時執行的程序,這些程序由 BootExecute 值指定
- 執行啟動時的文件刪除或重命名任務,這由 FileRenameOperations 子鍵來指定
- 啟動 Windows 子系統進程(csrss.exe)。子系統進程的命令行字符串由 SubSystems 子鍵的 Windows 值指定
然後 smss 便會啟動 winlogon 進程,隨後接下來的引導便交由 winlogon 進程了。 winlogon 主要職責如下:
- 創建初始窗口
- 創建登錄桌面和默認桌面
- 啟動服務控制管理器(SCM,Service Control Manager)進程(services.exe)
- 啟動本地安全權威子系統(lsass)進程
在登錄過程的最後,winlogon 檢查註冊表 HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Userinit 的值,並創建一個進程來運行該值字符串。該值串的默認值為 userinit.exe 程序的路徑。Userinit 進程加載當前登錄用戶的輪廓,然後檢查 HKCU\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\Shell 的值,並創建一個進程來運行該值字符串;如果該值不存在,則運行 HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Shell 的值,其默認值為 explorer.exe。然後,userinit 進程退出。由於當前登錄會話的 Shell 程序(explorer.exe)已經啟動,因此用戶可以在桌面上操作了。
至此,引導就全部結束了。