/ Windows / 130浏览

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 主要包含以下四个部分的程序:

  • POSTPower 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 的主要任务是:

  • 完成在实模式下执行的初始化。比如清除键盘缓冲区。
  • 为切换到保护模式做好基本的环境准备。
  • 将处理器切换到保护模式下,然后将控制权交给保护模式下的代码。

在保护模式下,需要完成以下步骤:

  1. 由于虚拟机制转译机制还未就绪,首先要做的就是把物理内存管理起来。采用一个内存描述符数组将每一段内存的大小和用途记录下来,然后打开页面映射机制
  2. 继续执行其他的初始化工作。包含 I/O 设备的初始化等
  3. 从系统分区的根目录下读取 boot.ini 文件。并检查是否存在有效的 hiberfil.sys 文件,如果存在,则将引导过程转移到休眠系统的恢复过程。否则,将解析 boot.ini 文件 。
  4. 运行 NTDETECT.COM 程序。此程序运行在实模式下,它将利用 BIOS 来查询系统的基本设备和配置信息,并收集起来。在引导后期,将其存放到注册表 HKLMHARDWAREDESCRIPTION 下。
  5. 加载 ntoskrnl.exe、hal(默认为 hal.dll,具体是哪个映像文件,在 boot.ini 中会有记录),再通过加载注册表的 system 储巢,即 WINDOWSsystem32configsystem 文件 拿到哪些设备驱动程序必须被加载进来,然后再把必要的设备驱动加载进来。
  6. 此时加载的准备工作基本就绪,将准备的信息构造成一个参数块 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;
  1. 最后,ntlrd 将控制权交给 ntoskrnl.exe 的入口函数。表示内核加载完成。

内核初始化

到这里,接下来的过程,我们就可以通过 WRK 来观察具体的过程了。

ntoskrnl.exe 的入口函数是 _KiSystemStartup,可以顺着这个方法往下看。

整个初始化分为两个阶段:Phase0 和 Phase1。

阶段1的初始化入口是 Phase1InitializationDiscard 方法。此方法也是做了各种初始化,具体可以去看代码。由于其时间比较长,所以内部做了一个进度估计。


待 阶段1 完成初始化,执行体的各个组件都进入了一个正常状态。但只有内核是没有意义的,系统必须让应用程序跑起来。应用程序的启动就得依赖刚启动的 smss 进程继续了。

应用初始化

现在控制权来到了 smss,smss 是 Windows 中一个重要的进程。

smss 还会继续完成引导过程。在内核初始化期间,只有 System 储巢被加载到内存当中了。其他的就得由 smss 来加载。其他初始化必要的工作,可以参看 HKLMSYSTEMCurrentControlSetControlSession Manager 。

注册表中能发现一些有趣的子键。

  • 建立系统的全局环境变量,这些环境变量由 Environment 键下的值指定
  • 运行在启动时执行的程序,这些程序由 BootExecute 值指定
  • 执行启动时的文件删除或重命名任务,这由 FileRenameOperations 子键来指定
  • 启动 Windows 子系统进程(csrss.exe)。子系统进程的命令行字符串由 SubSystems 子键的 Windows 值指定

然后 smss 便会启动 winlogon 进程,随后接下来的引导便交由 winlogon 进程了。 winlogon 主要职责如下:

  1. 创建初始窗口
  2. 创建登录桌面和默认桌面
  3. 启动服务控制管理器(SCM,Service Control Manager)进程(services.exe)
  4. 启动本地安全权威子系统(lsass)进程

在登录过程的最后,winlogon 检查注册表 HKLMSOFTWAREMicrosoftWindows NTCurrentVersionWinlogonUserinit 的值,并创建一个进程来运行该值字符串。该值串的默认值为 userinit.exe 程序的路径。Userinit 进程加载当前登录用户的轮廓,然后检查 HKCUSoftwareMicrosoftWindows NTCurrentVersionWinlogonShell 的值,并创建一个进程来运行该值字符串;如果该值不存在,则运行 HKLMSOFTWAREMicrosoftWindows NTCurrentVersionWinlogonShell 的值,其默认值为 explorer.exe。然后,userinit 进程退出。由于当前登录会话的 Shell 程序(explorer.exe)已经启动,因此用户可以在桌面上操作了。


至此,引导就全部结束了。

如何通过 C++ 实时监听 ETW 事件
如何通过 C++ 实时监听 ETW 事件
【译】调查并确定 Windows 运行速度变慢问题
【译】调查并确定 Windows 运行速度变慢问题
【译】丢失的 WPA 文档 —— 磁盘使用
【译】丢失的 WPA 文档 —— 磁盘使用
【译】丢失的 WPA 文档 —— CPU 调度
【译】丢失的 WPA 文档 —— CPU 调度
【译】丢失的 WPA 文档 —— CPU 采样
【译】丢失的 WPA 文档 —— CPU 采样
如何通过 PDH(Performance Data Helper) 获取性能计数器的值

0

  1. This post has no comment yet

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注