上面显示的�� � ! ∀∀# ∃% & ∋ ( )# ∗&∋字段的值# + ,−.∃∀ ! 便
代表着内核文件的入 口函数在模块中的偏移 , 加上模块的
基地址便可以得到入 口函数的线性地址 , 使用,& 命令查找
这个地址对应的符号 /
勺01幸叭口二,口口。皿�口
当 � !∀ # ∃ % & ∋ ( ) ! ∗ + 或 , −. !∀ # ∃ / 调 用
01 234 5% 6 45 # &5 7 8 时 , 它 会 将 启 动 选 项 以 一 个 名 为
!9 :9; +<< =: + :> ;下;凡? !9 ≅ 0 的 数 据 结 构 传 递 给
012345% 6 4 5# &5 7 =函数。 , Α. ∃ ∀ Β 4 Χ Α45# 的内核符号文件包
含了这个结构的符号 , 因此在对, Α.∃ ∀ Β 4 ΧΑ 45 # 做内核调试
时可以观察到这个结构的详细定义 。
三 、 调 用 0Α Δ.Α 5Α #ΔΑ Ε % 0 % 6 %Δ 开始 内核初始化 这个
函数会调用0ΑΔ .Α5 434 5% 6 来初始化系统的全局数据结构 ,
调 用 0%Δ .Α 5Α #ΔΑ Ε % 尸&∀ Φ% 4 4 创 建 并初始化 Δ∃Δ % 进 程 , 调 用
0% Δ. Α5Α# ΔΑΕ % ) Γ&% #∃ 初始化 Δ∃ Δ% 线程 。
对 于 多≅尸Η 的系统 , 每个 ≅户Η 都会执行 0Α Δ.Α 5Α #ΔΑ <
Ε% 0% 6 %Δ 函数 , 但只有第一个 ≅户7 会执行其中的所 有
初始化工作 , 包括全局性的初始化 其 它≅尸Η会 只执
行 ≅尸Η相 关的部分 。 比如只有 � 号 ≅尸Η 会调用和执行
011 .Α5 4 34 5% 6 , 初始化 Δ∃Δ % 进程的工作也只有� 号 ≅户Η执
行 , 因为只需要一个 1∃Δ % 进程 , 但是因为每个 ≅尸Η都需要
一个 Δ∃Δ % 线程 , 所以每个≅户9都会执行初始化 Δ∃Δ % 线程的
代码 。 0Α Δ.Α 5Α# ΔΑΕ % 0 %& . %Δ 函数使用参数来了解当前的≅ 尸Η号 。
全 局变量 0% ( 7 6 Ι %& 尸&∀ Φ∀ 44 ∀ &4 标志着 系统 中的≅ 8 Η 个
数 , 其初始值为∀ , 因此当� 号≅ 8 Η执行 0−434 5% 6 4 5#&5 7 8
函数时 , 0 % ( 7 6 Ι % &=&∀ % % 4 4 ∀ &4 的值 刚好是 当前的 ≅=Η
号 。 当第二个 ≅尸Η开始运行时 , 这个全局变量会被递增
,
, 因此 01 2 34 5% 6 45 # &5 7 8 函数仍然可 以从这个全局变量
了解到 ≅户Η 号 , 依此类推 , 直到所有≅户Η 都开始运行 。
; ϑ=Δ .Α5 Α#Δ 泛% ; ϑ % ≅7 5ΑΚ % 函数的第一个参数也是 ≅尸Η号 , 在
这个函数中也有很多代码是根据≅户Η号来决定是否执行的 。
霭刊洲绍
内核初始化
0Α 4 34 5% 6 45 # &5 7 8 函数开始执行后 , 它首先会进一步
完善基本的执行环境 , 包括建立和初始化处理器控制结构
∋ =≅+ / 、 建立任务状态段 ∋)2 2 / 、 设置用户调用内核服务
的>2 + 寄存器等。 在这些基本的准备工作完成后 , 接下来
的过程可以分为图 , 所示
的左右两个部分 。 左侧为
发生在初始的启动进程中
的过程 , 这个初始的进程
就是启动后的 Δ∃Δ % 进程 。
右侧为发 生在 系统进程
∋ 2 34 5% 6 / 中的所谓的执
行体阶段 Λ初始化过程 ∋如
图 Λ / 。
首先我们来看 01 23<
45 % 6 45# &57 8函数的执行过程 , 它所做的主要工作有 Μ
一 、 调用 Ν # ΔΔ. Α5Α# ΔΑΕ % 一=&∀ Φ% 4 4 ∀ &∋/初始化 ≅=Η 。
二 、 调用 0∃ Δ. Α54 34 一5% 6 初始化内核调试引擎 , 我们稍
后将详细介绍这个函数 。
执行体的阶段�初始化
在 01 1.Α 5Α #ΔΑ Ε% 0% 6 %Δ 函数结束基本的内核初始化后 , 它
会调用 ; ϑ =Δ. Α5Α# ΔΑΕ % ; ϑ % % 7 5ΑΚ% ∋/开始初始化执行体 。 如果把
操作系统看作是一个国家机器 , 那么执行体便是这个国家
的各个行政机构。 典型的执行体部件有进程管理器 、 对象
管理器 、 内存管理器 、 Δ9 管理器等。 考虑到各个执行体之
间可能有相互依赖关系 , 所以每个执行体会有两次初始化
的机会 , 第一次通常是做基本的初始化 , 第二次做可能依
赖其它执行体的动作。 通常前者叫阶段 � 初始化 后者叫
阶段 Λ初始化 。
; ϑ=Δ .Α5 Α#Δ ΑΕ % ; ϑ % ≅7 5ΑΚ % 的主要任务是依次调用各个执
行体的阶段 �初始化函数 , 包括调用 >6 Δ.Α 54 34 Ο% 6 构建
页表和内存管理器的基本数据结构 , 调用9 Ι Δ.Α 54 34 5% 6 建
立名称空间 , 调用 2%Δ .Α 54 345 % 6 初始化 5∀ Π% . 对象 , 调用
户4Δ .Α5 434 5% 6 对进程管理器做阶段 9初始化 ∋ 稍后详细说
明 / , 调用 尸=Δ .Α5 4 34 5% 6 让即插即用管理器初始化设备链表 。
下面我们仔细看一下进程管理器的阶段 � 初始化 它
所做的主要动作有 Μ
Θ 定义进程和线程对象类型 。
Θ 建立记录系统中所有进程 的链 表结构 , 并使用
=4: Φ5 ΑΚ % 8 &∀ % % 4 4 Ν% # ∃ 全 局 变 量 指 向 这 个 链 表 。 此 后
, Α. ∗ ?Ρ 的 Σ8 &∀ Φ% 4 4命令才能工作。
Θ 为初始的进程创建一个进程对象 ∋ 尸引∃Δ % =& ∀ Φ % 44 / ,
并命名为Δ∃ Δ% 。
, � Τ 程序员
2 创建系统进程和线程 , 并将户34∀ !, 5&∗ ∋∗4 ,∗64 ∋∗# & 函数
作为线程的起始地址 。
注意上面的最后一步 , 因为它衔接着系统启动的下
一个阶段 , 即执行体的阶段 7 初始化 。 但是这里并没有
直接调用阶段 , 的初始化函数 , 而是将它作为新创建系
统线程的入 口函数。 此时由于当前的 58 9 :很高 , 所以这
个线程还得不到执行。 在;∗, &∗∋ ∗4, ∗6! ;! &!, 函数返回后 ,
;5<(∀∋! = ∀ ∋4 ∋ > )函数将当前 ? )≅ 的中断请求级别 Α58 9 :Β
降低到 Χ5 < )�Δ?日Ε :%Φ %: , 然后跳转到 ;∗ ,�, ! :。。)ΑΒ, 退
化为5�, ! 进程中的第一个,�, ! 线程 。 当再有时钟中断发生时 ,
内核调度线程时 , 便会调度执行刚刚创建的系统线程 , 于
是阶段 7 初始化便可以继续了 。
执行体的阶段 7初始化
阶段 , 初始化占据了系统启动的大多数时间 , 其主要
任务就是调用执行体各机构的阶段 7 初始化函数。 有些执
行体部件使用同一个函数作为阶段 1和阶段 , 初始化函数 ,
使用参数来区分 。 图 7 列出了这一阶段所调用的主要函数 ,
简要说明其中几个 /
2 调用 ;! ∀ ∋4叭55) # Γ! ∀∀ # ∀ ΑΒ初始化所有? Η ≅ 。 这个
函数会构建并初始化好一个处理器状态结构 , 然后调用硬
件抽象层的 Ι45 <∋4 ∋ ϑ ! Κ∋ 尸 # Γ ! ∀∀ # 函数将这个结构赋给一
个新的?尸≅ 。新的?尸≅仍然是从 ;5<(∀∋!= ∀ ∋4 ∋ > Η开始执行。
2 再 次 调 用 ;�, &∗∋ ∀(∀ ∋! = 函 数 , 并 且 调 用
;� # ! − >Λ Λ ! ,&∗ ∋∗4 ,∗6! ,来 初始化内核调试 通信扩展 1::
Α;Χ ? 1Μ 2 Χ ::等 Β。
2 调用 51 管理器的阶段 7初始化函数 ,#, &∗∋ ∀ (∀ ∋! = 做设
备枚举和驱动加载工作 , 需要花很长的时间 。
在这一阶段结束前 , 会创建第一个使用映像文件创建
的进程 , 即会话管理器进程 Α< Μ< < 2 %Κ % Β。 会话管理器进
程会初始化Ν ∗&� #Ο ∀ 子系统 , 创建Ν ∗&� # Ο ∀ 子系统进程和
登录进程 〔Ν ∗&:# Λ # & 2任+ % Β , 我们以后再介绍 。
# + Π Θ蓝屏
上面介绍的过
程不总是一帆风顺
的。 如果遇到意外 ,
那么系统通常会以
图Ρ >+ ΠΘ 蓝屏 蓝屏形式报告错误 。
比如图 Σ 所示的Τ+ Π日蓝屏就是发生在内核和执行体初始化
期间的 Α我们上一期的问
题
快递公司问题件快递公司问题件货款处理关于圆的周长面积重点题型关于解方程组的题及答案关于南海问题
Β。
注意这个蓝屏的下方没有转储有关的信息 Α稍后你就
会明白原因了 Β 。
那么应该如何寻找这个蓝屏的故障原因呢 Υ
首先可以根据蓝屏的停止代码 Τ+ Π日查阅Ν ∗& 1 Θς 的帮
助文件或者Μ< 1 ϑ 了解它的含义 。 于是我们知道 , # + Π Θ是
5ϑ �? ?%< <5 Θ :%ΩΩ Θ1 1ΔΩΩ 1%Φ5 ?% 错误的代码 , 其含义是
不可访问的引导设备。 意思是系统在读或者写引导设备时
出错了 , 进一步来说 , 也就是在访问包含有系统文件的磁
盘分区时出问题了。
访问系统分区怎么会出问题呢 Υ 操作系统加载程序刚
刚不是还读过系统分区来加载系统文件了的 , 现在怎么不
能访问了呢 Υ 磁盘设备在这两个时间点之间损坏的概率很
低 , 因此 , 主要的原因还是因为访问的方式不同了 。 操作
系统加载程序是使用简单的方式来访问磁盘的 , 而操作系
统内核开始运行后 , 开始改用更为强大的驱动程序来访问
磁盘 , 而这里恰恰是常出问题的地方。 对于典型的旧% 硬
盘 , 需要使用八7Ω� ), 2 <丫< 这个驱动程序来进行访问。 那么
周队), 这个驱动是谁来加载的呢 Υ 让内核自己来加载 , 肯
定不行 , 因为内核是依赖它来访问磁盘的 , 正所谓 “ 自己
的刀刃削不了自己的刀把 ” 。 那么应该由谁来加载呢 Υ Τ Ξ
:# 4 � ! , 也就是 ϑ下:18 或者Ν ∗& :# 4 � 。 它们怎么知道要
加载这个驱动呢 Υ
是根据注册表。 图
Ψ显示 了注册表 中
叼人), 驱动 程序的
各个 键 值 。 其 中
的 <∋! ∋ 键 值等 于
Τ 代表是引导类型 , ς # > Η键值标志着这个驱动属于 < ?<5
= ∗& ∗)# ∋ 这个组 。 Τ Ξ :# 4 � ! 看到< ∋4 ∋键值为Τ 后 , 就会将— 这个驱动程序加载到内存中。 我们不妨把以这种方式加载的驱动程序称为第一批加载的驱动程序 Α如图 Ψ Β。图Ζ 第一批加载的驱动程序清单 如果选择调试方式或者安全模式启动 , 那么 ϑΔ :18 会显示出它加载的第一批驱动程序的清单 Α图 Ζ Β。
在上面的清单中 , 没有八7Ω� )5 2 <丫< , 这正是问题所在。
事实上笔者就是将 <∋ 4 ∋ 值改为 Ψ 来“制造 ”出这个蓝屏的Α读
者一定不要草率模仿 , 以免丢失数据 Β。
除了观察访问磁盘的关键驱动程序是否加载 , 还可以
使用内核调试来做进一步的分析 。 如果目标系统事先没有
启用内核调试 , 那么可以在引导初期按 [<调出高级引导菜
单 , 然后选择1 ! − > Λ 。 这时系统通常会使用串行口 ΣΑ ?1 ΜΣ Β
以波特率 7∴ Σ ΤΤ 来启用内核调试引擎 Α参见 《软件调试》
7]2 Ψ2 Ψ Η Ζ Π ] Β。 然后使用一根串口通信电缆将目标机器与
调试主机相连接 Α主机不一定要使用? 1ΜΡ Β 。
Σ Τ Τ ∴ Τ Σ , Τ ∴