4.1.1 Linux的启动过程详解
1.从BIOS到内核
(1)BIOS自检
计算机在接通电源之后首先由BIOS进行自检,即进行所谓的POST(Power On Self Test),然后依据BIOS内设置的引导顺序从硬盘、软盘或CDROM中读入"引导块"。在PC中,引导Linux是从BIOS中的地址0xFFFF0处开始的。BIOS的第一个步骤是加电自检(POST),POST的工作是对硬件进行检测。BIOS的第二个步骤是进行本地设备的枚举和初始化。给定BIOS功能的不同用法之后,BIOS由两部分组成:POST代码和运行时服务。当POST完成之后,它从内存中清理出来,但是BIOS运行时服务依然保留在内存中,目标操作系统可以使用这些服务。要引导一个操作系统,BIOS运行时会按照CMOS的设置的顺序来搜索处于活动状态并且可以引导的设备。引导的设备可以是软盘、CD-ROM、硬盘上的某个分区、网络上的某个设备,甚至是USB闪存。通常,Linux都是从硬盘上引导的,其中主引导记录(MBR)中包含主引导加载程序。MBR是一个512字节大小的扇区,位于磁盘上的第一个扇区中(0道0柱面1扇区)。当MBR被加载到RAM中之后,BIOS就会将控制权交给MBR。
(2)提取MBR的信息
要查看MBR的内容,请使用下面的命令:
# dd if=/dev/hda of=mbr.bin bs=512 count=1 # od -xa mbr.bin
这个dd命令需要以root用户的身份运行,它从/dev/hda(第一个IDE盘)上读取前512个字节的内容,并将其写入mbr.bin文件中。od命令会以十六进制和ASCII码格式打印这个二进制文件的内容。
2.启动GRUB/LILO
GRUB和LILO都是引导加载程序。简单地讲,引导加载程序(boot loader)会引导操作系统。当机器引导它的操作系统时,BIOS会读取引导介质上最前面的512字节(主引导记录,即master boot record,MBR)。在单一的MBR中只能存储一个操作系统的引导记录,所以当需要多个操作系统时就会出现问题,因此需要更灵活的引导加载程序。
所有引导加载程序都以类似的方式工作,满足共同的目的。不过,LILO和GRUB之间有很多不同之处:
- LILO没有交互式命令界面,而GRUB拥有。
- LILO不支持网络引导,而GRUB支持。
- LILO将关于可以引导的操作系统位置的信息在物理上存储于MBR中。如果修改了LILO配置文件,必须将LILO第一阶段的引导加载程序重写到MBR。对于GRUB,这是一个危险的选择,因为错误配置的MBR可能会让系统无法引导。使用GRUB,如果配置文件错误,则默认转到GRUB命令行界面。
安全提示 关于安全性,任何可以接触到引导磁盘/CD的人,只需要使用没有设置安全性的grub.conf或lilo.conf,就可以绕过本节中提及的所有安全措施。特别是使用GRUB时,因为能够引导到单用户模式,所以是一个严重的安全漏洞。解决此问题的一个简单方法是在机器的BIOS中禁止通过CD和软盘进行引导,并确保为BIOS设置了口令,使得其他人不能修改这些设置。
3.加载内核
当内核映像被加载到内存之后,内核阶段就开始了。内核映像并不是一个可执行的内核,而是一个压缩过的内核映像。通常它是一个zImage(压缩映像,小于512KB)或bzImage(较大的压缩映像,大于512KB),它是提前使用zlib进行压缩的。在这个内核映像前面是一个例程,它实现少量硬件设置,并对内核映像中包含的内核进行解压,然后将其放入高端内存中,如果有初始RAM磁盘映像,就会将它移动到内存中,并标明以后使用。然后该例程会调用内核,并开始启动内核引导的过程。
在GRUB命令行中,我们可以使用initrd映像引导一个特定的内核,方法如下:
grub> kernel /bzImage-2.6.14.2
[Linux-bzImage, setup=0x1400, size=0x29672e]
grub> initrd /initrd-2.6.14.2.img
[Linux-initrd @ 0x5f13000, 0xcc199 bytes]
grub> boot
Uncompressing Linux... Ok, booting the kernel.
如果你不知道要引导的内核的名称,只需使用斜线(/)然后按下Tab键即可。GRUB会显示内核和initrd映像列表。
安全提示 通过MD5进行GRUB密码加密,这样会更安全。
① 原来GRUB的密码是123456,所以先要用MD5对123456这个密码进行加密。
# /sbin/grub-md5-crypt
Password: 在这里输入123456
Retype password: 再输入一次123456
$1$7uDL20$eSB.XRPG2A2Fv8AeH34nZ0
$1$7uDL20$eSB.XRPG2A2Fv8AeH34nZ0就是通过grub-md5-crypt进行加密后产生的值。
② 修改配置文件/etc/grub.conf,加入"password --md5 $1$7uDL20$eSB.XRPG2A2Fv8AeH34nZ0"一行,注意在title之前。修改后的文件格式如下:
timeout=10
splashimage=(hd0,7)/boot/grub/splash.xpm.gz
password --md5 $1$7uDL20$eSB.XRPG2A2Fv8AeH34nZ0
title Fedora Core (2.4.22-1.2061.nptl)
root (hd0,7)
kernel /boot/vmlinuz-2.4.22-1.2061.nptl ro root=LABEL=/
initrd /boot/initrd-2.4.22-1.2061.nptl.img
title WindowsXP
rootnoverify (hd0,0)
chainloader +1
4.执行init进程
init进程是系统所有进程的起点,内核在完成核内引导以后,即在本线程(进程)空间内加载init程序,它的进程号是1。init进程是所有进程的发起者和控制者。因为在任何基于UNIX的系统(比如Linux)中,它都是第一个运行的进程,所以init进程的编号(Process ID,PID)永远是1。如果init出现了问题,系统的其余部分也就随之不可用。
init进程有两个作用:
① 扮演终结父进程的角色。因为init进程永远不会被终止,所以系统总是可以确信它的存在,并在必要的时候以它为参照。如果某个进程在它衍生出来的全部子进程结束之前被终止,就会出现必须以init为参照的情况。此时那些失去了父进程的子进程都会以init作为它们的父进程。快速执行一下"ps-af"命令,可以列出许多父进程的编号(Parent Process ID,PPID)为1的进程来。
② 在进入某个特定的运行级别(Runlevel)时运行相应的程序,以此对各种运行级别进行管理。它的这个作用是由/etc/inittab文件定义的。
5.通过/etc/inittab文件进行初始化
init的工作是根据/etc/inittab来执行相应的脚本进行系统初始化,如设置键盘、字体,装载模块,设置网络等。对于Red Hat Linux来说,按以下顺序执行。
(1)执行/etc/rc.d/rc.sysinit(由init执行的第一个脚本)
Red Hat Linux的/etc/rc.d/rc.sysinit要做在各个运行模式中相同的初始化工作,包括:
- 设置初始的$PATH变量。
- 配置网络。
- 为虚拟内存启动交换。
- 设置系统的主机名。
- 检查root文件系统,以进行必要的修复。
- 检查root文件系统的配额。
- 为root文件系统打开用户和组的配额。
- 以读/写的方式重新装载root文件系统。
- 清除被装载的文件系统表/etc/mtab。
- 把root文件系统输入到mtab。
- 使系统为装入模块做准备。
- 查找模块的相关文件。
- 检查文件系统,以进行必要的修复。
- 加载所有其他文件系统。
- 清除几个/etc文件,如/etc/mtab、/etc/fastboot和/etc/nologin。
- 删除UUCP的lock文件。
- 删除过时的子系统文件。
- 删除过时的pid文件。
- 设置系统时钟。
- 打开交换。
- 初始化串行端口。
- 装入模块。
(2)执行/etc/rc.d/rcX.d/[KS]
首先终止"K"开头的服务,然后启动"S"开头的服务。
对每一个运行级别来说,在/etc/rc.d子目录中都有一个对应的下级目录。这些运行级别的下级子目录的命名方法是rcX.d,其中X就是代表运行级别的数字。比如说,运行级别3的全部命令脚本程序都保存在/etc/rc.d/rc3.d子目录中。在各个运行级别的子目录中,都建立有到/etc/rc.d/init.d子目录中命令脚本程序的符号链接,但是,这些符号链接并不使用命令脚本程序在/etc/rc.d/init.d子目录中原来的名字。如果命令脚本程序是用来启动一个服务的,其符号链接的名字就以字母"S"打头;如果命令脚本程序是用来关闭一个服务的,其符号链接的名字就以字母"K"打头。
许多情况下,这些命令脚本程序的执行顺序都很重要。如果没有预先配置网络接口,就没有办法使用DNS服务解析主机名。为了安排执行顺序,在字母S或者K的后面紧跟着一个两位数字,数值小的在数值大的前面执行。比如:/etc/rc.d/rc3.d/S50inet就会在 /etc/rc.d/rc3.d/S55named之前执行。存放在/etc/rc.d/init.d子目录中的、被符号链接上的命令脚本程序是真正的实干家,是它们完成了启动或者停止服务的操作过程。当/etc/rc.d/rc通过每个特定的运行级别子目录的时候,它会根据数字的顺序依次调用各个命令脚本程序。它先运行以字母K打头的命令脚本程序,然后再运行以字母S打头的命令脚本程序。对以字母K打头的命令脚本程序来说,会传递Stop参数;类似地对以字母S打头的命令脚本程序来说,会传递Start参数。
(3)执行/etc/ec.d/rc.local
Red Hat Linux中的运行模式2、3、5都把/etc/rc.d/rc.local作为初始化脚本中的最后一个文件,所以用户可以自己在这个文件中添加一些需要在其他初始化工作之后,登录之前执行的命令。在维护Linux系统运转时,肯定会遇到需要系统管理员对开机或者关机命令脚本进行修改的情况。如果所做的修改只在引导开机的时候起作用,并且改动不大的话,可以考虑简单地编辑一下/etc/rc.d/rc.local脚本。这个命令脚本程序是在引导过程的最后一步执行的。
(4)执行/bin/login
login程序会提示使用者需输入账号及密码,接着编码并确认密码的正确性,若二者相合,则为使用者进行初始化环境,并将控制权交给shell,即等待用户登录。到此为止,Linux启动过程全部结束。如图4-1所示是Linux启动流程图。

图4-1 Linux启动流程图
4.1.2 Linux运行级
(1)什么是运行级
一个运行级仅仅是一个数字,Linux根据这个数字来区分不同类型的高层次配置,系统将按照不同的高层次配置来进行引导。绝大部分运行级数字都定义了明确的含义,Red Hat Linux系统的主要运行级如表4-1所示。
运 行 级 | 说 明 |
0 | 关闭 |
1 | 单用户模式(一般仅用于管理目的) |
2 | 多用户模式,不允许使用网络 |
运 行 级 | 说 明 |
3 | 多用户模式,允许使用网络 |
4 | 没有用到的运行级 |
5 | 多用户模式,允许使用网络,X Window 方式(图形登录界面) |
6 | 重新引导 |
表4-1 Red Hat Linux系统的主要运行级
(2)运行级别的切换
如果使用的是文本登录,那么运行级别就是3。如果使用的是图形化登录,那么运行级别就是5。
① 查看运行级别命令
#runlevel
② 切换运行级别的命令
#init 〔0123456Ss〕
③ 其他方法
可以通过修改/etc/inittab文件来改变:
id:5:initdefault:
把这一行中的数字改成你想要的运行级别。所做的改变在系统重新引导之后才会生效。要立即改变运行级别,使用telinit命令,之后跟随运行级别号码。必须是根用户才能使用这个命令。telinit命令并不改变/etc/inittab文件,它只改变当前的运行级别。当系统重新引导后,它会导入/etc/inittab中指定的运行级别。
4.1.3 /etc/inittab文件详解
/etc/inittab文件如图4-2所示。

图4-2 /etc/inittab文件
/etc/inittab文件每一行包括四个字段:id、runlevel、action、process。详细解释如下。
1.id
登记项标志符,是一个任意指定的、4个字符以内的序列标号,在本文件内必须唯一。
2.runlevels
系统运行级,即执行登记项的init级别。用于指定相应的登记项适用于哪一个运行级,即在哪一个运行级中被处理。如果该字段为空,那么相应的登记项将适用于所有的运行级。在该字段中,可以同时指定一个或多个运行级,其中各运行级分别以数字0, 1, 2, 3, 4, 5, 6或字母a, b, c表示,且无须对其进行分隔。
3.action
表示进入对应的runlevel时,init应该运行process字段的命令的方式,常用的字段值及解释在附录内。图4-2中的wait表示需要运行这个进程一次并等待其结束。
有效的action值如下。
- boot:只有在引导过程中,才执行该进程,但不等待该进程的结束。当该进程死亡时,也不重新启动该进程。
- bootwait:只有在引导过程中,才执行该进程,并等待进程的结束。当该进程死亡时,也不重新启动该进程。实际上,只有在系统被引导后,并从单用户模式进入多用户模式时,这些登记项才被处理;如果系统的默认运行级设置为2(即多用户模式),那么这些登记项在系统引导后将马上被处理。
- initdefault:指定系统的默认运行级。系统启动时,init将首先查找该登记项,如果存在,init将依据此决定系统最初要进入的运行级。具体来说,init将指定登记项"run_level"字段中的最大数字(即最高运行级)为当前系统的默认运行级;如果该字段为空,那么将其解释为"0123456",并以"6"作为默认运行级。如果不存在该登记项,那么init将要求用户在系统启动时指定一个最初的运行级。
- off:如果相应的进程正在运行,那么就发出一个告警信号,等待20秒后,再通过关闭信号强行终止该进程。如果相应的进程并不存在,那么就忽略该登记项。
- once:启动相应的进程,但不等待该进程结束便继续处理/etc/inittab文件中的下一个登记项;当该进程终止时,init也不重新启动该进程。在从一个运行级进入另一个运行级时,如果相应的进程仍然在运行,那么init就不重新启动该进程。
- ondemand:与"respawn"的功能完全相同,但只用于运行级为a、b或c的登记项。
- powerfail:只在init接收到电源失败信号时,才执行该进程,但不等待该进程结束。
- powerwait:只在init接收到电源失败信号时,才执行该进程,并在继续对/etc/inittab文件进行任何处理前等待该进程结束。
- respawn:如果相应的进程还不存在,那么init就启动该进程,同时不等待该进程的结束就继续扫描/etc/inittab文件;当该进程终止时,init将重新启动该进程。如果相应的进程已经存在,那么init将忽略该登记项并继续扫描/etc/inittab文件。
- sysinit:只有在启动或重新启动系统并首先进入单用户模式时,init才执行这些登记项。而在系统从运行级1~6进入单用户模式时,init并不执行这些登记项。"action"字段为"sysinit"的登记项在"run_level"字段不指定任何运行级。
- wait:启动进程并等待其结束,然后再处理/etc/inittab文件中的下一个登记项。
- ctrlaltdel:用户在控制台键盘上按下Ctrl+Alt+Del组合键时,允许init重新启动系统。注意,如果该系统放在一个公共场所,系统管理员可将Ctrl+Alt+Del组合键配置为其他行为,比如忽略等。
4.process
具体应该执行的命令。图4-2中的/etc/init.d/rc命令启动运行级别2中应该运行的进程/命令,并负责在退出运行级时将其终止(当然在进入的runlevel中仍要运行的程序除外)。当运行级别改变,并且正在运行的程序并没有在新的运行级别中指定需要运行时,那么init会先发送一个SIGTERM 信号终止,然后是SIGKILL。
4.1.4 init和/etc/inittab
init的手册页指出"init是所有进程之父"。它的主要任务是根据存储于/etc/inittab中的脚本创建进程。当装入内核之后,许多Linux启动进程的方式来源于另一个UNIX系统的SystemV。事实上,Linux的init命令与SystemV的init命令是兼容的,并且启动脚本也模仿SystemV的init的方式,虽然init作为"引导内核的最后一步",但它却是进行初始化和配置的第一个命令,通过从控制台或X终端窗口的命令行使用pstree命令可以查看该进程。
pstree命令用ASCII字符显示树状结构,清晰地表达了程序间的相互关系。如果不指定程序识别码或用户名称,则会把系统启动时的第一个程序视为基层,并显示之后的所有程序;若指定用户名称,便会以隶属该用户的第一个程序当作基层,然后显示该用户的所有程序。
主要参数:
- -a:显示每个程序的完整指令,包含路径、参数或常驻服务的标志。
- -c:不使用精简标志。
- -G:使用VT100终端机的列绘图字符。
- -h:列出树状图时,特别标明现在执行的程序。
- -H<程序识别码>:此参数的效果和指定"-h"参数类似,但特别标明指定的程序。
- -l:采用长列格式显示树状图。
- -n:用程序识别码排序,默认是以程序名称来排序。
- -p:显示程序识别码。
- -u:显示用户名称。
- -U:使用UTF-8列绘图字符。
- -V:显示版本信息。
在命令行输入"pstree -l",如图4-3所示。
可以看到init是系统的第一个进程,所有子进程的父进程。init通过解析/etc/inittab并参照默认或设定的运行级运行/etc/rc.d中的脚本。每个脚本可以启动或终止一个诸如网络、邮件、新闻或Web的服务。

图4-3 pstree命令输出