多核启动过程

下面我们看多核处理器的启动过程,在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(性能计数器的频率)初始化。