3.1 引导
引导(bootstrapping)是“启动计算机(starting up a computer)”的标准说法。操作系统的正常功能在启动过程中还不能使用,因此,计算机必须“用自己的引导程序把自己启动起来”。引导过程中,内核先被加载到内存里,然后开始执行。执行完各种初始化任务之后,用户就能够使用系统了。
计算机开机后,先执行保存在 ROM 中的引导代码。接下来,这段代码试着确定如何加载并启动内核。内核检测系统的硬件,然后产生系统的 init 进程。
系统完全引导起来之前,先要检查并挂载文件系统,并且启动系统的守护进程。这些步骤是由 init 进程按照顺序运行的一系列 shell 脚本(有时候也叫 init 脚本)来管理的。
3.1.1 恢复模式下引导进入 shell
在正常运行的情况下,系统可以自行引导,引导起来以后,系统管理员和用户就能够远程访问它。如果系统有异常,不能完成正常的引导过程,系统管理员就需要恢复工具来恢复。
UNIX 系统可以不追求完整启动,而只是部分引导,只够在系统主控台上运行一个shell就可以了。这种方式传统上称为引导进入单用户模式、恢复模式或者维护模式。
大多数系统,可以通过在引导时传给内核一个参数,引导进入单用户模式。
3.1.2 引导过程的步骤
典型的引导过程包括6个不同的阶段:
- 从 MBR 读取引导加载程序(boot loader);
- 加载并初始化内核;
- 检测和配置设备;
- 创建内核进程;
- 系统管理员干预(仅限单用户模式);
- 执行系统启动脚本;
以上大多数步骤系统管理员几乎无法通过交互的方式去控制。如果要改变大多数的引导配置,可以通过编辑系统启动脚本的配置文件,或者改变引导加载程序传给内核的参数来做到。
3.1.3 初始化内核
内核本身就是一个程序,系统引导过程中的第一项任务就是把这个程序载入内存,以便执行它。内核的路径名随厂商的不同而不同,但是传统上应该像 /vmlinuz
或者 /boot/vmlinuz
这样。
大多数系统实现了包括两个阶段的加载过程。在第一阶段中,系统 ROM 把一个小的引导程序从硬盘载入到内存中。这个程序叫做引导加载程序(boot loader),由它再安排载入内核。这个过程在 UNIX 的控制范围之外,所以在不同系统上并没有统一标准。
内核会探测系统有多少 RAM 可用,内核的一些内部数据结构大小是静态确定的,因此,当内核启动时,它就为自己划分出一块固定大小的内存空间。这块空间保留给内核使用,用户级进程不能使用。内核在控制台上输出一条消息,报告物理内存的总量以及用户进程可用的内存量。
3.1.4 配置硬件
内核执行的第一批任务中包括检查机器的环境以确定机器有什么硬件。内核一边探测各种系统总线,记录各种硬件,一边为它找到的每个设备输出一行像密码一样的信息。
3.1.5 创建内核进程
一旦完成了基本的初始化任务,内核就在用户空间创建几个“自发”的进程。它们之所以被称为自发进程,是因为这些进程不是通过系统正规的 fork
机制所创建的。
尽管 init
的进程号始终是1,但自发进程的确切数量随系统的不同而不同。在大多数 UNIX
系统上,sched
的进程号时0。
Linux上看不到 PID 0 进程。和 init
进程一起的是几个内存和内核处理进程,包括下表所列出的进程。在 ps
命令的输出中,它们的名字都被中括号括起来了(例如,[kacpid])。有时还会附带斜线和数字,比如[kblockd/0],数字表明该线程在哪个处理器上运行。
Linux 系统上的一些内核进程:
线程 | 作用 |
---|---|
kjournald | 向磁盘提交文件系统日志更新信息(每个已经挂载的 ext3 或者 ext4 文件系统对应一个 kjournald) |
kswapd | 物理内存不足时执行交换操作的进程 |
ksoftirqd | 处理不能在上下文切换时刻处理的软中断 |
khubd | 配置USB设备 |
所有的这些进程中,只有 init
是真正完整的用户进程。其它进程实际上都是内核的组成部分,出于调度或者结构上的原因伪装成进程而已。
这些进程一创建好,内核在引导阶段的任务就完成了。不过,处理基本操作(比如接受登录)的进程还一个都没有创建,大多数的系统守护进程也没有启动。这些任务都是由 init
(有些是间接)来负责的。
3.1.6 操作员干预(仅限恢复模式)
如果以恢复模式引导系统,那么在 init
启动时,内核会传给它一个命令行标志,告诉它实际要引导的是恢复模式。
单用户模式中,有的时候只挂载了根分区,为了使用不在 /bin
, /sbin
, /etc
,下的程序,用户必须手工挂载其它文件系统。许多单用户环境下,文件系统的根目录是按只读方式挂载的。如果需要修改,那么就要以读写的方式重新挂载。
在正常的引导过程中,会运行 fsck
命令检查并修复文件系统。在以单用户模式启动系统时,可能需要手工执行 fsck
。
3.1.7 执行启动脚本
当系统准备运行其启动脚本的时候。我们就能认出它是Unix系统了。尽管它看起来还不很像全部启动完毕的系统,但在接下来的启动过程中已经再没有“有变数的”步骤了。启动脚本就是普通的 Shell
脚本,由 init
根据一定的算法来选择并运行它们。尽管算法有时候有些复杂,但还是相当容易理解的。
3.1.8 引导进程完成
在初始化脚本运行过以后,系统就是完全运行的系统了。像 DNS 和 smtp 服务器这样的系统,守护进程已经提供服务了。请记住,即使在引导完成以后,init
还继续担当重要的角色。
3.2 引导 PC
在引导一台机器的时候,他是从执行保存在ROM中的代码开始的。在明确为Unix或其他专有操作系统而设计的机器上,这些代码通常是固件固件,知道怎样使用连接到机器的设备,知道怎样和网络进行基本通信,还知道怎样理解基于磁盘的文件系统。
在PC上,这种初始化的引导代码通常叫做BIOS(Basic Input/Output System,基本输入/输出系统),同专有工作站上的固件相比,它极为简单。实际上PC上有几个层面的BIOS:一个用于机器本身,一个用于显卡,另一个用于SCSI卡。有时候还有一个用于其他外设(比如网卡)。
内置的BIOS知道主板上一些设备的信息,如 IDE 和 SATA 控制器(和硬盘)、网卡、电源和温度传感器以及系统硬件。
BIOS通常让用户选择想从什么设备进行引导,一旦确定从什么设备引导系统,它将尝试加载设备上开头512个字节的信息。这个512字节的数据段也叫 MBR(master boot record,主引导记录)。MBR 里有一个程序,它告诉计算机从哪个分区加载第二阶段的引导程序,即“引导加载程序(boot loader)”。默认的MBR里有一个简单的程序,它告诉计算机从硬盘上的第一个分区获取引导加载程序。有些系统提供了一种更为复杂的 MBR,他知道怎样去处理多操作系统和多内核。一旦 MBR 已经选定从什么分区进行引导,他就试图加载针对那个分区的引导加载程序。之后就由引导加载程序负责加载内核。
3.3 GRUB:全面统一的引导加载程序
GRUB 的任务是从预先编排好的清单中选择一个内核,用系统管理员指定的参数加载这个内核。 DRUB的家族有2个分支。原来的GRUB现在叫做“GRUB Legay(传统 GRUB)”,还有就是比较新的 GRUB 2。
GRUB 默认从 /boot/grub/menu.lst
或者 /boot/grub/grub.conf
读取它的缺省引导配置。 GRUB在系统引导时刻读取这个配置文件,所以可以在每次系统引导的时候动态修改配置。
GRUB 支持一种功能强大的命令界面,而且能随时编辑配置文件中的配置项。在GRUB的启动画面键入 C 键就可以进入命令行模式。从命令行可以启动 grub.conf
文件里没有列出的操作系统。可以显示系统信息,还可以执行对文件系统的基础测试。通过 grub.conf
文件能做到的事情也能通过GRUB命令行实现。
GRUB 的命令行选项:
命令 | 含义 |
---|---|
reboot | 重启系统 |
find | 在所有可以挂载的分区上寻找一个文件 |
root | 指定根设备(一个分区) |
kernel | 从根设备加载的内核 |
help | 获得一条命令的交互式帮助信息 |
boot | 用指定的内核映像文件(kernel image)启动系统 |
3.3.1 内核选项
GRUB 能把命令行选项传给内核。这些选项往往用来修改内核参数的取值,指导内核探测特殊的设备、指定 init
所在的路径,或者指派一个特定的根设备。
内核启动选项举例:
选项 | 含义 |
---|---|
acpi=off | 禁用 ACPI(Advanced Configuration and Power Interface)功能 |
init=/bin/bash | 只启用 bash:用于紧急恢复 |
root=/dev/foo | 告诉内核用 /dev/foo 作为根设备 |
single | 引导进入单用户模式 |
3.3.2 多重引导
由于PC上可以运行多种操作系统,因此配置一台机器使它能够引导几种不同的系统,就成为相当常见的做法。要实现这一点,需要配置一个引导加载程序,能认出磁盘上所有不同的操作系统。
每个磁盘分区都可以有它自己的第二阶段的引导加载程序。但是,整个磁盘却只有一个 MBR。在建立多重引导配置时,必须决定哪一个引导加载程序将成为“主”引导加载程序。
在双重引导Windows系统和Linux系统的时候,一定要让GRUB成为整个系统的引导程序。然后通过配置 grub.conf
文件,进行多系统引导。
3.4 引导进入单用户模式
略
3.5 启动脚本
从单用户模式(或者按照标准的引导顺序,在应该运行单用户模式的那个地方)退出来以后,init
执行系统的启动脚本。这些脚本实际上只是由 sh
或者是 bash
解释的普通 Shell 脚本。
大多数系统采用一种把脚本按数字编号并顺序执行的方式。脚本保存在 /etc/init.d
下,在 /etc/rc0.d
、/etc/rc1.d
等目录下建立这些脚本的链接。这种组织方式很干净,因为脚本按顺序执行,所以这种体系可以让各服务之间有依赖关系。这些启动脚本既能启动服务,也能停止服务。所以这种结构也能让系统以有序的方式关机。
经常在启动脚本中执行的一些任务有:
- 设置计算机的名称。
- 设置时区。
- 使用
fsck
检查磁盘。 - 挂载系统的磁盘。
- 删除
/tem
目录的旧文件。 - 配置网络接口。
- 启动守护进程和网络服务。
3.5.1 init 及其运行级
init
是系统引导起来以后第一个运行的进程,从很多方面来说,它都是最重要的守护进程。它的进程号始终是一,而且是除了几个系统进程之外,所有用户进程的祖先。
init
至少定义了7个“运行级(run level)”每一个运行级都代表系统应该补充运行的一组特定服务:
- 运行级 0 完全关闭系统;
- 运行级 1 或 S 代表单用户模式;
- 运行级 2~5 包含联网支持;
- 运行级 6 是“重新引导(reboot)”;
运行级 0 和 运行级 6 比较特殊,因为系统实际上不能停留在这两个级别里。进入这两个级别的效果是关闭系统或者重新引导系统。在大多数系统上,正常的多用户运行级是 2 或 3。在Linux系统上。运行级 5 经常用于 X Windows 的登录进程。运行级 4 很少使用。
/etc/inittab
文件告诉 init
在它的每个运行级上要做什么事情。随着机器的引导,init
从运行级 0 开始,一级一级往上运行到在 /etc/inittab
中所设置的默认运行级。为了完成在每一对相邻运行级别之间的过渡。init
执行在 /etc/inittab
中为这种过渡而说明的一些操作。当关闭机器时,以相反的顺序执行同样的处理过程。
在系统起来以后 telinit
命令可以改变 init
的运行级。例如 telinit 3
,迫使 init
进入运行级 3。 telinit
最有用的参数是 -q
,这个参数,让 init
重读 /etc/inittab
文件。
inittab
文件的语义相当原始,所以 init
实现了另一层抽象。会由 inittab
调用一个命令。执行位于与运行级有关的目录下的其他脚本,从而把系统带入到新的状态。
3.5.2 启动脚本概述
启动脚本的主拷贝位于 /etc/init/d
这个目录下,每个脚本负责一个守护进程或者系统的某个特定方面。这些脚本都知道 start
和 stop
这两个参数表示他们所处理的服务是应该启动还是应该停止。大多数脚本还认识参数 restart
。该参数一般等同于先执stop
,再执行 start
。
尽管 /etc/init.d
中的脚本能够启动和停止各个服务,但是由 init
运行的主控脚本还要知道其他一些信息,这些信息说明了要进入任何指定的运行级别需运行哪些脚本(并带什么参数)。当主脚本把系统引入到一个新的运行级别时,它不是直接在 init.d
目录下找,而是查找叫做 rc[level].d
的目录,这里的 level 就是要进入的运行级别编号(例如,rc0.d、rc1.d 等)。
这些 rc[level].d
目录包含的符号连接都连接到了 init.d
目录中的脚本上。这些符号连接的名称都以 S 或 K 开头。后跟一个数字以及该脚本所控制的服务名(例如,S34named)。当 init
从低运行级向高运行级过渡时,他按照数字递增的顺序。带 start
参数运行所有以 S 开头的脚本。当 init
从高运行级向低运行级过渡时,他按照数字递减的顺序。带 stop
参数运行所有以 K 开头的脚本。
为了告诉系统什么时候启动一个守护进程,必须在相应的目录下创建符号链接。例如,要告诉系统在运行级 2 上启动 cupsd(用于打印的守护进程)
。并在关机前妥善地停止这个守护进程。那么创建下面这一对链接就够了:
ln -s /etc/init.d/cups /etc/rc2.d/S80cups
ln -s /etc/init.d/cups /etc/rc0.d/K80cups
# 有些系统以不同方式处理系统关闭和重新引导。所以我们需要在这些系统的 /etc/rc6.d 这个目录中也放一个符号连接,以确保当系统重新引导时,该守护进程能够被正常关闭。
ln -s /etc/init.d/cups /etc/rc6.d/K80cups
第一行命令告诉系统:当进入运行级 2 时,把带 start
参数运行启动脚本 /etc/init.d/cups
作为最后要做的事情之一。第二行告诉系统:当关闭系统时,要早点带 stop
参数运行 /etc/init.d/cups
这个脚本。
3.5.3 Red Hat 启动脚本
略
3.5.4 SUSE 的启动脚本
略
3.5.5 Ubuntu 的启动脚本和 Upstart 守护进程
从 Ubuntu 7.04 开始,Ubuntu 就用 Upstart 代替了传统的 init。 Upstart 是一个事件驱动的服务管理系统,处理系统状态的过渡(比如硬件的变化)比 init 更优美,它也大大的缩短了启动时间。
Upstart 根据系统事件(比如增加一个设备,或者切断一个网络驱动程序)来启动和停止服务。为了保持兼容性,他也模仿传统的 init 运行级。不过处理启动和关闭脚本的方式和 init 的有所不同。
Upstart 使用 /etc/event.d
目录里的作业定义文件。代替 inittab
文件。一个作业(job)在概念上和一个启动脚本类似:它执行一系列的命令,然后把控制权还给 Upstart。
为了保持兼容性的需要,Ubuntu 的系统管理员应该使用 Ubuntu 的 update-rc.d
的命令来维护 rc
目录下的给启动脚本的链接。
update-rc.d service {start | stop} sequence runlevels .
update-rc.d
命令接受一个次序号(启动脚本应该按照这个号的顺序执行)和可应用的运行级作为参数,用一个点(.)做结尾。在一次运行级过渡的过程中后启动的服务应该在系统退出该运行级时先停止。
# 这条命令把CUPS的启动链接按次序号80加入运行级2、3、4、5,把停止链接按次序号20加入运行级S、1和6
update-rc.d cups start 80 2 3 4 5 . stop 20 S 1 6.
默认运行级由 /etc/event.d/rc-default
里的两行 telinit 2 来控制。Upstart 也能通过名为 tty*
的作业来控制各终端上的登录进程。
3.5.6 HP-UX 的启动脚本
略
3.6 引导 Solaris
Sun引入了自己的 SMF(Service Management Facility),比起Linux差别较大,可能是一种更先进的引导方式。
3.7 重新引导和关机
3.7.1 shutdown:停止系统的妥善方式
shutdown
是停止或重新引导系统,或者返回到单用户模式的最安全、考虑最周到的、最彻底的方式。
3.7.2 halt 和 reboot:关闭系统的更简单方式。
halt
命令执行关闭系统所需要的基本任务。它可以用 shutdown -h
调用,也可以单独使用。reboot
几乎和 halt
完全一样。只不过它是让机器重新启动。由 shutdown -r
来调用。