多核啟動過程
下面我們看多核處理器的啟動過程,在part 1中介紹過每個CPU都會執行KiInitializeKernel函數,但只有第一個CPU才執行其中的所有初始化工作,包括全域的初始化,其他CPU只執行CPU的相關的部分。0號CPU才調用和執行KiInitSystem,初始化Idle進程的工作也只有0號CPU執行,因為只需要一個Idle進程。但是由於每個CPU都需要一個Idle線程,因此每個CPU都會執行初始化Idle線程的代碼。KiInitializeKernel函數使用參數來瞭解當前的CPU號。全域變數KeNumberProcessors標誌著系統中CPU個數,其初始值為0,所以KeNumberProcessors指向當前的CPU號。多核系統是按CPU號從小到大依次執行,直到所有的CPU都開始運行。ExpInitializeExecutive函數的第一參數也是CPU號,這個函數中很多的代碼是根據CPU號來決定是否執行的。
來具體看0號CPU才能夠執行的KiInitSystem。KiInitSystem來初始化系統的全域資料結構,調用KeInitializeProcess建立並初始化Idle進程,調用keInitializeThread初始化Idle線程,調用ExpinitializeExcutive()進行所謂的執行體階段0初始化。ExpInitializeExecutive會依次調用執行體各個機構的階段0初始化函數,包括調用MmInitSystem構建頁表和記憶體管理器的基本資料結構,調用PsInitSystem對進程管理器做階段0初始化,調用PpInitSystem讓隨插即用管理器初始化裝置鏈表。
階段0初始化
下面來具體看看進程管理器的階段0初始化,它的主要工作有:
1. 定義進程和線程物件類型
2. 建立記錄系統中所有進程的鏈表結構,並使用PsActiveProcessHead全域變數指向這個鏈表。此後WinDBG的!process命令才能工作。
3. 為初始的進程建立一個進程對象(PsIdleProcess),並命名為Idle
4. 建立系統進程和線程,並將Phase1Initialization函數作為線程的起始地址,Phase1Initialization函數做為0階段的結束並銜接著1階段的開始。Phase1Initialization函數並沒有直接調用階段1的初始化函數,而是將它作為新建立系統線程的入口函數。此時由於當前的IRQL很高,所以這個線程還得不到執行,只有當KiInitlizeKernel返回,KiSystemStartup將IRQL降低後,核心下次調度線程時,才開始執行這個線程。
階段1初始化
階段1初始化佔據了系統啟動的大多數時間,其主要任務是調用執行體各機構階段1的初始化函數。有些執行體組件使用同一個參數作為階段0和階段1初始化函數,用參數來區分,調用KeStartAllProcessors()初始化所有的CPU,這個函數會先構建並初始化好一個處理器狀態結構,然後調用硬體抽象層的HalStartNextProcessor函數將這個結構付給一個新的CPU。新的CPU仍然從KiSystemStartup開始執行。然後再次調用KdInitSystem函數,並且調用KdDebuggerInitialize1來初始化核心調試擴充DLL(KDCOM.DLL等)。
兩次調用KdInitSystem
Windows在啟動的過程中會兩次調用核心偵錯引擎初始化函數KdInitSystem(Kd,是Kernel Debug的縮寫,凡是用kd開頭的函數都是用於核心調試的)。KdInitSystem函數的第一參數為階段號,0代表第一次調用,1代表第二次調用。第一次調用是在核心開始執行後由入口函數KiSystemStartup調用,主要工作是初始化資料鏈表與資料結構,通訊模組的初始化,一些全域變數的初始化。在階段1初始化時會第二次調用KdInitSystem,主要對變數KdPerformanceCounterRate(效能計數器的頻率)初始化。