启动过程是我们了解操作系统的第一个环节。了解 Windows 的启动过程,可以帮助我们解决一些启动的问题,也能帮助我们了解 Windows 的整体结构。
以下内容将分为【加载内核】、【内核初始化】和【应用程序初始化】三个部分。
如 启动过程概览 所示,加载过程分为两种方式。传统的 BIOS(Basic Input/Output System)和 UEFI(Unified Extensible Firmware Interface)。这两种方式大致相似,本文以 BIOS 为例来说明引导说过。
此部分参考:How BIOS Works (yale.edu)
当开机键被按下后,电源将执行初始化,确保电源可靠后,主板上的硬件电路给 CPU 发复位(reset)信号。
CPU 收到复位信号后,将从 RAM 中读取指令执行。此时内存中是没有任何指令需要执行的。于是 CPU 就去 BIOS ROM 中特定的位置(FFFF0H)中执行指令,这个指令是一个跳转指令 ,将告诉 CPU 真正的 BIOS 程序所在的位置。跳转指令如下:
jmp far f000:e05b ; 跳转到0xfe05b执行
BIOS 主要包含以下四个部分的程序:
Windows 的做法是,让引导扇区中的代码读入其他扇区的数据,然后跳转到下一个扇区的代码区。这样就可以不受单个引导扇区长度的限制,这种做法相当于将第一个引导扇区当做一个加载器(loader),而真正完成引导扇区功能的扇区随后被加载进来并执行。这一过程对于 MBR 是透明的,从而保持良好的兼容性。
此时代码的控制权已经来到了 ntldr(也可以是 WinLoad 或者 os loader),此时处理器还运行实模式下,所以 ntldr 也分为两部分:实模式代码和保护模式代码。
在实模式下,ntldr 的主要任务是:
在保护模式下,需要完成以下步骤:
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;
到这里,接下来的过程,我们就可以通过 WRK 来观察具体的过程了。
ntoskrnl.exe 的入口函数是 _KiSystemStartup,可以顺着这个方法往下看。
整个初始化分为两个阶段:Phase0 和 Phase1。
阶段1的初始化入口是 Phase1InitializationDiscard 方法。此方法也是做了各种初始化,具体可以去看代码。由于其时间比较长,所以内部做了一个进度估计。
待 阶段1 完成初始化,执行体的各个组件都进入了一个正常状态。但只有内核是没有意义的,系统必须让应用程序跑起来。应用程序的启动就得依赖刚启动的 smss 进程继续了。
现在控制权来到了 smss,smss 是 Windows 中一个重要的进程。
smss 还会继续完成引导过程。在内核初始化期间,只有 System 储巢被加载到内存当中了。其他的就得由 smss 来加载。其他初始化必要的工作,可以参看 HKLM\SYSTEM\CurrentControlSet\Control\Session Manager 。
注册表中能发现一些有趣的子键。
然后 smss 便会启动 winlogon 进程,随后接下来的引导便交由 winlogon 进程了。 winlogon 主要职责如下:
在登录过程的最后,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)已经启动,因此用户可以在桌面上操作了。
至此,引导就全部结束了。