Windows启动过程概述

时间:2021-12-31 16:53:22

Windows启动过程概述

计算机开机后,先执行的是系统的固件(firmware),即BIOS(Basic Input/Output System,基本输入输出系统)或EFI(Entexed Firmware Interface)。BIOS或EFI在完成基本的硬件检测和平台初始化工作后,将控制权移交给磁盘上的引导程序。磁盘引导程序再执行操作系统的加载程序(OS Loader),即NTLDR(Vista之前)或WinLoad.exe(Vista)。

系统加载程序首先会对CPU做必要的初始化工作,包括从16位实模式切换到32位保护模式,启用分页机制等,然后通过启动配置信息(Boot.INI或BCD)得到Windows系统的系统目录并加载系统的内核文件,即NTOSKRNL.EXE。当加载这个文件时,会检查它的PE文件头导入节中所依赖的其他文件,并加载这些依赖文件,其中包括用于内核调试通信的硬件扩展DLL(KDCOM.DLL、KD1394.DLL或KDUSB.DLL)。加载程序会根据启动设置加载这些DLL中的一个,并将其模块名统一称为KDCOM。

而后系统加载程序会读取注册表的System Hive,加载其中定义的启动(boot)类型(SERVICE_BOOT_START (0))的驱动程序,包括磁盘驱动程序。

在完成以上工作后,系统加载程序会从内核文件的PE文件头找到它的入口函数,即KiSystemStartup函数,然后调用这个函数。调用时将启动选项以一个名为LOADER_PARAMETER_BLOCK的数据结构传递给KiSystemStartup函数。于是,NT内核文件得到控制权并开始执行。

可以把接下来的启动过程分为图18-10所示的3个部分。左侧是发生在初始启动进程中的过程,这个初始的进程就是启动后的Idle进程。中间是发生在系统进程(System)中的所谓的执行体阶段1初始化过程。右侧是发生在会话管理器进程(SMSS)的过程。

首先我们来看KiSystemStartup函数的执行过程,它所做的主要工作有。

第一,调用HalInitializeProcessor()初始化CPU。

第二,调用KdInitSystem初始化内核调试引擎,我们稍后将详细介绍这个函数。

第三,调用KiInitializeKernel开始内核初始化,这个函数会调用KiInitSystem来初始化系统的全局数据结构,调用KeInitializeProcess创建并初始化Idle进程,调用KeInitializeThread初始化Idle线程,调用ExpInitializeExecutive()进行所谓的执行体阶段0初始化。ExpInitializeExecutive会依次调用执行体各个机构的阶段0初始化函数,包括调用MmInitSystem构建页表和内存管理器的基本数据结构,调用ObInitSystem建立名称空间,调用SeInitSystem初始化token对象,调用PsInitSystem对进程管理器做阶段0初始化(稍后详细说明),调用PpInitSystem让即插即用管理器初始化设备链表。


图18-10 Windows启动过程概览
在KiInitializeKernel函数返回后,KiSystemStartup函数将当前CPU的中断请求级别(IRQL)降低到DISPATCH_LEVEL,然后跳转到KiIdleLoop(),退化为Idle进程中的第一个Idle线程。

对于多CPU的系统,每个CPU都会执行KiInitializeKernel函数,但只有第一个CPU才执行其中的所有初始化工作,包括全局性的初始化,其他CPU只执行CPU相关的部分。比如只有0号CPU才调用和执行KiInitSystem,初始化Idle进程的工作也只有0号CPU执行,因为只需要一个Idle进程。但是,由于每个CPU都需要一个Idle线程,所以每个CPU都会执行初始化Idle线程的代码。KiInitializeKernel函数使用参数来了解当前的CPU号。全局变量KeNumberProcessors标志着系统中的CPU个数,其初始值为0,因此,当0号CPU执行KiSystemStartup函数时,KeNumberProcessors的值刚好是当前的CPU号。当第二个CPU开始运行时,这个全局变量会被递增1,因此KiSystemStartup函数仍然可以从这个全局变量了解到CPU号,依此类推,直到所有CPU都开始运行。ExpInitializeExecutive函数的第一个参数也是CPU号,在这个函数中有很多代码是根据CPU号来决定是否执行的。

下面我们仔细看看进程管理器的阶段0初始化,它所做的主要动作有。

定义进程和线程对象类型。

建立记录系统中所有进程的链表结构,并使用PsActiveProcessHead全局变量指向这个链表。此后WinDBG的!process命令才能工作。

为初始的进程创建一个进程对象(PsIdleProcess),并命名为Idle。

创建系统进程和线程,并将Phase1Initialization函数作为线程的起始地址。

注意上面的最后一步,因为它衔接着系统启动的下一个阶段,即执行体的阶段1初始化。但是这里并没有直接调用阶段1的初始化函数,而是将它作为新创建系统线程的入口函数。此时由于当前的IRQL很高,所以这个线程还得不到执行,只有当KiInitializeKernel返回,KiSystemStartup将IRQL降低后,内核下次调度线程时,才会开始执行这个线程。

阶段1初始化占据了系统启动的大多数时间,其主要任务就是调用执行体各机构的阶段1初始化函数。有些执行体部件使用同一个函数作为阶段0和阶段1初始化函数,用参数来区分。图18-10中列出了这一阶段所调用的主要函数,下面简要说明其中几个。

调用KeStartAllProcessors()初始化所有CPU。这个函数会先构建并初始化好一个处理器状态结构,然后调用硬件抽象层的HalStartNextProcessor函数将这个结构赋给一个新的CPU。新的CPU仍然从KiSystemStartup开始执行。

再次调用KdInitSystem函数,并且调用KdDebuggerInitialize1来初始化内核调试通信扩展DLL(KDCOM.DLL等)。

在这一阶段结束前,它会创建第一个使用映像文件创建的进程,即会话管理器进程(SMSS.EXE)。

会话管理器进程会初始化Windows子系统,创建Windows子系统进程和登录进程(WinLogon.EXE),后者会创建LSASS(Local Security Authority Subsystem Service)进程和系统服务进程(Services.EXE)并显示登录画面,至此启动过程基本完成。