过并行化 Linux 系统服务来提高引导速度(zt)

转自:http://www-128.ibm.com/developerworks/cn/linux/l-boot/index.html#resources

作者:James Hunt, 软件工程师, IBM

下面这篇,是BlueSilence看了ibm的工程师的文章的 应用总结:http://blog.chinaunix.net/article.php?articleId=13889&blogId=4133

作者:BlueSilence

查看已经激活了多少服务: /sbin/chkconfig --list

查看当前有哪些服务正在运行: /sbin/service --status-all



本文向您描述了如何在不影响可用性的前提下加快 Linux 操作系统的引导速度。当然,这种方法要求您对系统服务及服务之间的依赖关系有所理解,如果它们可以并行启动,就让它们并行启动而不是串行启动。

毫无疑问 Linux 是一个优秀的系统,但仍然无法摆脱一个常见的责难(尤其是来自具有 Microsoft Windows 背景的人),那就是 Linux 系统从按下“on”键开始到可以使用,需要的时间太长。其实他们说的没错,Linux 确实需要比较长的引导时间。

在这里我所描述的加快 Linux 引导速度的技术虽然理解起来很简单,但真正实现却需要谨慎行事。我希望 Linux 的发行商能采用这种方法,这样用户就可以省去那些配置任务。不过如果您喜欢冒险,请继续阅读本文。

写在开始之前
如果您想体验一下这种方法,您首先必须得熟悉 Linux 的配置脚本。修改系统的启动设置可能会带来危险,甚至可能会导致您的系统无法启动。如果出现这种情况,请重新启动机器并进入单一用户模式(运行级1),把 您所做的修改还原回来,然后再重新启动。永远记住要备份您所修改过的所有文件,为了防止最坏的情况发生,您还需要有至少一个系统备份的映像。

我强烈建议您在考虑用我所建议的方法修改一个正式的系统之前,先去修改一个无关紧要的测试系统。如果您只有一台机器,那么您可以使用 UML (User Mode Linux) 这一非常有用的工具。UML是一个内核补丁,它可以将Linux内核编译成为一个二进制文件,然后您可以像运行一个普通的程序一样去运行这个内核。也就是说,您可以在您的正常的系统之上以一个进程的方式来运行一个完整的 Linux 系统。您可以将其想象为在一个正常的系统 运行一个 Linux 系统。(请参阅本文末尾的 参考资料,可以找到可以下载UML的站点以及 developerWorks网站上关于UML的教程)。

使用UML您可以工作于一个测试系统,哪怕把这个测试系统完全破坏掉,也不会影响您正常的系统。

概述
本文的第一部分介绍当 Linux 内核(Linux 机器的的“核心”)加载后,一个 Linux 系统怎样在后台启动。然后介绍加快您的系统引导速度的技术。

如果您对运行级和服务启动脚本已经熟悉,您可能希望直接跳转到 传统服务框架的局限

Linux 引导次序和运行级

一个 Linux 系统的引导过程可以分为几个阶段。本文并不会解释所有的不同阶段,因为我们所关心只是当内核加载后的那一个阶段。

您可以运行 /sbin/runlevel 命令来确定您的系统当前的运行级。(更多详细信息请查阅 man runlevel)。

当内核被加载并开始运行时会调用 /sbin/init 程序。这个程序以 root 身份运行,并且在开始引导时按照要求设定为“运行级”。(更多关于 init 程序的详细信息,请参考 man init)

什么是运行级?

一个 运行级仅仅是一个数字,Linux根据这个数字来区分不同类型的高层次配置,系统将按照不同的高层次配置来进行引导。由于绝大部分运行级数字都定义了明确的含义,因而它们基本上是“众所周知”的。Red Hat Linux 系统的主要运行级见表1。

表 1. Red Hat Linux运行级

运行级 说明
0关闭
1单一用户模式(一般仅用于管理目的)
2多用户模式,不允许使用网络
3多用户模式,允许使用网络
4没有用到的运行级
5多用户模式,允许使用网络,X-Windows 方式(图形登录界面)
6重新引导

init如何初始化系统
init 通过一个ASCII配置文件(/etc/inittab)来确定如何改变运行级。通常,init 会根据这个配置文件去运行 /etc/rc.d/rc 脚本,并将运行级数字传递给这一脚本。

rc.sysinit 脚本

在 Red Hat 系统中,在运行 rc 脚本之前,init 将首先运行 /etc/rc.d/rc.sysinit 脚本,这个脚本执行那些必需的底层设置任务,比如设置系统时钟,检查磁盘错误,然后挂载文件系统。

在本文看来,正是从运行 rc 脚本开始,事情才变得有趣。

系统服务
rc 脚本负责启动用户需要的所有服务。就像名字所描述的一样,所谓服务就是系统提供的有用的工具。可能会有很多服务需要启动。大部分的 Linux 系统会启动 sshd(安全Shell服务)、syslog(系统日志工具)和 lpd(打印服务),但还会有更多的服务需要启动。比如,我的 Red Hat 9 系统现在运行着29个服务,但如果我把所有的服务都启动,那么我的系统中将会有近50服务在运行。

还有一点很重要,我们应该明白有的服务可能只能由特定的运行级来启动。比如,除了运行级5(多用户图形方式)以外,几乎不会启动某种形式的图形服务,因为其它所有的运行级都是非图形方式的。接下来我们将深入讨论这一问题。

服务程序在哪里?

可选的服务程序目录

在一些 Linux 系统中,服务程序有时候是在 /etc/init.d 目录下。

通常在 /etc/rc.d/init.d/ 目录下可以找到服务程序。

如果你浏览一下这个目录,你就会发现相当多的(如果不是全部都是的话)服务程序实际上都是 shell 脚本,用于调用其他程序完成实际的工作。

rc 脚本如何知道在每个运行级下去运行哪些脚本?
回顾一下,如果我们不希望在某个运行级下运行某个脚本,我们如何告诉系统这样去做?答案是在 /etc/rc.d/ 目录下,在这个目录下,除了我们已经讨论过的 init.d/ 目录以外,还有一组目录,每一个目录对应一个运行级。这些目录以 rc.d 的形式来命名,比如,对应运行级5的目录为 /etc/rc.d/rc5.d/ 。在这些rc.d目录中,每一个目录下都有一组符号链接,指向 /etc/rc.d/init.d 中的真正的服务程序。实际上,后边我们会发现,每个服务事实上有两个符号链接。

服务链接名
这些指向实际服务程序的符号链接的名字很重要,它们遵循严格的命名约定,这样 rc 脚本就知道如何处理它们。

为了便于标识,每个链接的名字都以它们所指向的服务的名字做为后缀。

前缀由两部分构成:一个大写字母,紧跟着是一个两位的十进制数。前缀中的大写字母是“S”(表示“启动”),或者“K”(表示“杀死”,或者停止)。两位数的大小范围是自00到99。

服务链接名正则表达式

符号链接的名字可以用 egrep 正则表达式来描述,[SK][0-9]{2}[a-zA-Z]+。(更多详细信息请参阅 man egrep)。

启动和停止服务
如果我们决定让 Linux 机器引导到图形模式(运行级5),当 init 调用 rc 脚本并传递给它运行级数字时,rc 脚本将到 /etc/rc.d/rc5.d/ 中查找,并且去运行它所能找到的所有符号链接(也就是说,它将运行每个链接指向的程序/脚本)。它将在两个截然不同的阶段来运行这些链接;首先它会执行所有以“K”开头的链接,同时传递给它们参数“stop”。执行完以后,所有这些链接指向的服务都被停止。

当 rc 脚本把所有需要停止的服务都停止后,它将去执行所有以“S”开头的链接,同时传递给它们参数“start”。执行完以后,所以这些链接指向的服务都被启动。rc 脚本也把参数“start”传递给每一个程序。

rc 把参数“tart”或者“stop”传递给每一个服务程序,这样做是为了只用一个服务程序可以启动或停止那个服务——服务程序根据传递给它的参数值分辨系统是正在引导还是正在关闭。

有一个重要的方面我还没有解释——链接名的数字部分。在“S”或者“K”之后的两位十进制数是 rc 脚本用来确定启动链接(就是链接指向的服务)的 顺序的。数字较小(比如00,01,等等)的链接在数字较大(99是最大的)链接之前运行。我们会在本文后边的内容中再次提到这一重点问题。

现在还迷惑吗?清单1列出了运行级5对应目录下的所有链接。当引导到运行级5的时候,最先被执行的链接将是 K05saslauthd,因为它以“K”开头,并且在所有的以“K”开头的链接中两位十进制数是最小。最先被执行的启动链接将是 S05kudzu,因为它以“S”开头,并且在所有以“S”开头的链接中两位十进制数是最小的。最后一个运行的链接将是 S99local。

清单 1. 运行级5的指向服务程序的链接

# cd /etc/rc.d/rc5.d
# ls -al
total 8
drwxr-xr-x 2 root root 4096 Jul 15 09:29 .
drwxr-xr-x 10 root root 4096 Jun 21 08:52 ..
lrwxrwxrwx 1 root root 19 Jan 1 2000 K05saslauthd -> ../init.d/saslauthd
lrwxrwxrwx 1 root root 20 Feb 1 2003 K15postgresql -> ../init.d/postgresql
lrwxrwxrwx 1 root root 13 Jan 1 2000 K20nfs -> ../init.d/nfs
lrwxrwxrwx 1 root root 14 Jan 1 2000 K24irda -> ../init.d/irda
lrwxrwxrwx 1 root root 17 Jan 1 2000 K35winbind -> ../init.d/winbind
lrwxrwxrwx 1 root root 15 Jan 1 2000 K50snmpd -> ../init.d/snmpd
lrwxrwxrwx 1 root root 19 Jan 1 2000 K50snmptrapd -> ../init.d/snmptrapd
lrwxrwxrwx 1 root root 16 Jun 21 09:43 K50vsftpd -> ../init.d/vsftpd
lrwxrwxrwx 1 root root 16 Jun 21 08:57 K73ypbind -> ../init.d/ypbind
lrwxrwxrwx 1 root root 14 Jun 21 08:54 K74nscd -> ../init.d/nscd
lrwxrwxrwx 1 root root 18 Feb 8 11:15 K92iptables -> ../init.d/iptables
lrwxrwxrwx 1 root root 19 Feb 1 2003 K95firstboot -> ../init.d/firstboot
lrwxrwxrwx 1 root root 15 Jan 1 2000 S05kudzu -> ../init.d/kudzu
lrwxrwxrwx 1 root root 14 Jun 21 08:55 S09isdn -> ../init.d/isdn
lrwxrwxrwx 1 root root 17 Jan 1 2000 S10network -> ../init.d/network
lrwxrwxrwx 1 root root 16 Jan 1 2000 S12syslog -> ../init.d/syslog
lrwxrwxrwx 1 root root 17 Jan 1 2000 S13portmap -> ../init.d/portmap
lrwxrwxrwx 1 root root 17 Jan 1 2000 S14nfslock -> ../init.d/nfslock
lrwxrwxrwx 1 root root 18 Jan 1 2000 S17keytable -> ../init.d/keytable
lrwxrwxrwx 1 root root 16 Jan 1 2000 S20random -> ../init.d/random
lrwxrwxrwx 1 root root 16 Jun 21 08:52 S24pcmcia -> ../init.d/pcmcia
lrwxrwxrwx 1 root root 15 Jan 1 2000 S25netfs -> ../init.d/netfs
lrwxrwxrwx 1 root root 14 Jan 1 2000 S26apmd -> ../init.d/apmd
lrwxrwxrwx 1 root root 16 Jan 1 2000 S28autofs -> ../init.d/autofs
lrwxrwxrwx 1 root root 14 Jan 1 2000 S55sshd -> ../init.d/sshd
lrwxrwxrwx 1 root root 20 Jan 1 2000 S56rawdevices -> ../init.d/rawdevices
lrwxrwxrwx 1 root root 16 Jan 1 2000 S56xinetd -> ../init.d/xinetd
lrwxrwxrwx 1 root root 14 Feb 1 2003 S58ntpd -> ../init.d/ntpd
lrwxrwxrwx 1 root root 13 Jun 21 10:42 S60afs -> ../init.d/afs
lrwxrwxrwx 1 root root 13 Jan 1 2000 S60lpd -> ../init.d/lpd
lrwxrwxrwx 1 root root 16 Feb 8 17:26 S78mysqld -> ../init.d/mysqld
lrwxrwxrwx 1 root root 18 Jan 1 2000 S80sendmail -> ../init.d/sendmail
lrwxrwxrwx 1 root root 13 Jan 1 2000 S85gpm -> ../init.d/gpm
lrwxrwxrwx 1 root root 15 Mar 22 08:24 S85httpd -> ../init.d/httpd
lrwxrwxrwx 1 root root 15 Jan 1 2000 S90crond -> ../init.d/crond
lrwxrwxrwx 1 root root 13 Jan 1 2000 S90xfs -> ../init.d/xfs
lrwxrwxrwx 1 root root 17 Jan 1 2000 S95anacron -> ../init.d/anacron
lrwxrwxrwx 1 root root 13 Jan 1 2000 S95atd -> ../init.d/atd
lrwxrwxrwx 1 root root 15 Jun 21 08:57 S97rhnsd -> ../init.d/rhnsd
lrwxrwxrwx 1 root root 14 Jul 15 09:29 S98wine -> ../init.d/wine
lrwxrwxrwx 1 root root 13 Feb 8 17:26 S99db2 -> ../init.d/db2
lrwxrwxrwx 1 root root 11 Jun 21 08:52 S99local -> ../rc.local
#

这看起来好象是非常复杂的系统,但实际上它提供了极好的灵活性,因为如果您想临时禁止某个特定运行级中的服务,只要把适当的符号链接删除即可。不过,手工管理这些链接可能会让人感觉厌烦,并且容易出错(尤其当您累了的时候),所以可以采用一个相对好一些的方法,使用 chkconfig 命令。

如果您有一个新版本的 chkconfig ,您会在主输出的最后一部分看到有关 xinetd (Internet services daemon)的配置。为了减化说明,此部分没有列入清单2中。

如何找出激活的服务
想查看您已经激活了多少服务,运行这个命令:

/sbin/chkconfig --list

清单 2列出了这个命令的输出。您可以看到,每一行有八列。

chkconfig 命令还可以用来切换任何一个服务的开或关。详细信息请参考手册页(man chkconfig)。


清单 2. chkconfig --list|sort的输出

afs 0:off 1:off 2:off 3:on 4:off 5:on 6:off
anacron 0:off 1:off 2:on 3:on 4:on 5:on 6:off
apmd 0:off 1:off 2:on 3:on 4:on 5:on 6:off
atd 0:off 1:off 2:off 3:on 4:on 5:on 6:off
autofs 0:off 1:off 2:off 3:on 4:on 5:on 6:off
crond 0:off 1:off 2:on 3:on 4:on 5:on 6:off
db2 0:off 1:off 2:off 3:on 4:off 5:on 6:off
firstboot 0:off 1:off 2:off 3:off 4:off 5:off 6:off
gpm 0:off 1:off 2:on 3:on 4:on 5:on 6:off
httpd 0:off 1:off 2:off 3:off 4:off 5:on 6:off
iptables 0:off 1:off 2:off 3:off 4:off 5:off 6:off
irda 0:off 1:off 2:off 3:off 4:off 5:off 6:off
isdn 0:off 1:off 2:on 3:on 4:on 5:on 6:off
keytable 0:off 1:on 2:on 3:on 4:on 5:on 6:off
kudzu 0:off 1:off 2:off 3:on 4:on 5:on 6:off
lpd 0:off 1:off 2:on 3:on 4:on 5:on 6:off
mysqld 0:off 1:off 2:off 3:on 4:off 5:on 6:off
netfs 0:off 1:off 2:off 3:on 4:on 5:on 6:off
network 0:off 1:off 2:on 3:on 4:on 5:on 6:off
nfs 0:off 1:off 2:off 3:off 4:off 5:off 6:off
nfslock 0:off 1:off 2:off 3:on 4:on 5:on 6:off
nscd 0:off 1:off 2:off 3:off 4:off 5:off 6:off
ntpd 0:off 1:off 2:off 3:on 4:off 5:on 6:off
pcmcia 0:off 1:off 2:on 3:on 4:on 5:on 6:off
portmap 0:off 1:off 2:off 3:on 4:on 5:on 6:off
postgresql 0:off 1:off 2:off 3:off 4:off 5:off 6:off
random 0:off 1:off 2:on 3:on 4:on 5:on 6:off
rawdevices 0:off 1:off 2:off 3:on 4:on 5:on 6:off
rhnsd 0:off 1:off 2:off 3:on 4:on 5:on 6:off
saslauthd 0:off 1:off 2:off 3:off 4:off 5:off 6:off
sendmail 0:off 1:off 2:on 3:on 4:on 5:on 6:off
snmpd 0:off 1:off 2:off 3:off 4:off 5:off 6:off
snmptrapd 0:off 1:off 2:off 3:off 4:off 5:off 6:off
sshd 0:off 1:off 2:on 3:on 4:on 5:on 6:off
syslog 0:off 1:off 2:on 3:on 4:on 5:on 6:off
vsftpd 0:off 1:off 2:off 3:off 4:off 5:off 6:off
winbind 0:off 1:off 2:off 3:off 4:off 5:off 6:off
wine 0:off 1:off 2:on 3:on 4:on 5:on 6:off
xfs 0:off 1:off 2:on 3:on 4:on 5:on 6:off
xinetd 0:off 1:off 2:off 3:on 4:on 5:on 6:off
ypbind 0:off 1:off 2:off 3:off 4:off 5:off 6:off

清单 2中第一列是服务的名字,接下来的列是运行级和每一个运行级中服务的状态。例如,ntpd (Network time daemon)服务被配置为只在运行级3中(多用户,无图形)和运行级5(多用户,有图形)中启动,sshd 服务在运行级2,3,4和5中都被切换到开的状态。

注意在运行级0和6中没有一个服务要启动。回顾表 1,原因显而易见。运行级1表示要关闭或停止系统,因此在机器将要“关闭”时,您不会想要 启动任何服务。运行级6中也是如此。

运 行级1——“单一用户模式”——是一个特别的运行级,一般在系统出问题的时候使用。一直以来,在运行级1中运行的唯一一个应用程序是 shell,允许超级用户来修复系统或者让超级用户在一个安全的环境中修改系统。这样是安全的——就像它的名字“单一用户模式”的含意一样——只有超级用 户可以访问系统。并且,联网是禁用的,所以没有人可以远程登录。如表 1所示,单一用户模式中运行的唯一一个服务是keytable,这样使得超级用户的键盘可以正常使用。

激活服务与运行服务的对比
有时服务会由于某种原因无法启动,用下面这个命令查看当前有哪些服务正在运行:

/sbin/service --status-all

这个命令将为每个服务输出一行或多行,指出每个服务是否在运行,如果在运行,则列出服务的一些特定的输出,比如服务运行的PID(进程号)。service 命令没有手册页,但是您可以在运行这个命令时使用--help 选项,您就可以得到有关它的操作的一些帮助信息。



回页首

传统服务框架的缺陷

关键的是,只有当配置中的所有服务都启动以后,您才可以登录进入您的 Linux 系统。等待50个服务启动可能会需要若干分钟,而这本来应该是您享用Linux系统的时间。

我已经找到了一个加速这个过程的方法。注意这种方法不会停止任何服务。不管怎样,停掉那些不用的服务是很明智的,不仅是因为这样可以加快引导的速度(在机器可以登录之前需要运行的服务少了),而且,由于很多服务要以 root 用户身份来运行,停掉不用的服务会减少您的安全隐患。

扼要重述一下,当一个 Linux 系统引导时,它以一种连续的方式来运行所有的某个运行级所配置的所有服务—— 一个接一个地。这是一个耗时的操作。

或 许一个很明显的加快服务启动速度的方法是并行地启动所有的服务,这样它们就可以同时启动。不幸的是,虽然这听起来很吸引人,却不可行。原因是各个服务之间 存在依赖的关系。Linux没有把这些依赖关系完全显式地表示出来,但是事实上这些依赖关系是存在的。还记得我们先前讨论的关于链接名字格式的问题吗?在 “S”和“K”之后的两位数决定了链接(也就是它们指向的服务)的运行顺序。这些数字确定了一个硬性的顺序,这样一定程度上也强化了服务之间的依赖关系。




回页首


服务之间的依赖关系

回顾 清单 1,我们可以看到 network 服务(S10network)将在ntpd服务(S58ntpd)之前运行。这是我们所期望的,因为 ntpd 服务要求网络可达,以使它可以连接一个本地时间服务器。不幸的是,这个硬性的顺序并不能告诉我们足够的信息,并且会让人误解。例如,在 清单 1中我们可以看到 lpd 服务(S60lpd)将在 network 服务之后运行。虽然这样对那些连接到网络并且使用网络打印机的 Linux 系统来说是正确的,但是这并不说明当背板上有一个 inkjet 打印机连接到本地系统时,lpd 服务还是必须要在 network 服务之后运行。实际上,在这种情况下,在启动 network 之前先启动 lpd 会更好一些。

再来看另外一个例子:crond (cron daemon)服务 (在 清单 1中的 S90crond)也是在 network 启动之后运行。可是,如果您没有使用远程机器文件的 cron 文件,那么就应该让 crond 在 network 之前启动。

由于我刚才介绍的 Linux 下启动服务的传统方法有一定的局限性,往往倾向于“安全第一",让所有的重要的服务先启动,然后再启动余下的那些。

所以,尽管我们不能并行地启动 所有的服务,但我们可以并行地启动那些 相互间没有依赖关系的服务。当这些相互间无依赖的服务启动以后,我们可以启动那些所有依赖条件已经满足(也就是说,那些服务所依赖的服务已经启动)的服务。然后重复这一过程,直到所有服务全部启动。

这个看起来是一个复杂的问题,不过幸运的是,已经有一个现成的可以用来解决这个问题的程序。这个程序不是别的,正是 make。

通常当编译软件时,make 会提供我们所需要的严密的框架。所有我们要做的就是告诉 make 什么是服务之间的依赖;它可以去做所有的计算交叉依赖的艰难工作,并且,使用它的鲜为人知的标记 -j ,它可以作为许多"作业"而 同步运行。

得出服务间依赖关系
如先前我间接提到的,传统的 Linux 系统没有显式地表示服务间的依赖关系,所以现在我们不得不自己去做一些艰难的工作,得出这些依赖关系。这可能会需要一段时间,因为您可能都不知道每个服务 在做什么,更别提服务之间的关系了。然而,如果您没有完成这些工作,这种方法对您来说没有任何益处。(如前面所提到的,如果这种方法有实用价值,希望 Linux 发行商可以采用它,并且为我们做这些艰难的工作。)

认识您的服务

如果当您运行命令 /sbin/chkconfig --list时,您有可能会遇到一些您所不认识的服务,那么花一些时间去弄明白它。一个简单的方法是读那些控制相应服务的脚本中开头处的注释。
这样您就可以把不用的那些工具所对应的服务关掉。即便是您需要它,这样您也可以更加了解您的系统。

现在,我们来做一个简单的实例。我们都知道,ntpd 服务需要网络,这说明 ntpd 服务依赖于network 服务。在 make 语法中这个依赖关系这样表示:

ntpd : network

我们还可以确定 netfs 服务(挂载我们所需要的所有NFS目录)依赖于网络。在我的系统(您的可能会不一样)上,autofs 服务(自动挂载网络文件系统)也依赖于 network 服务,因为我曾经自动挂载远程文件系统(您可能挂载光驱或者软驱)。我们的“依赖表”现在是这样:


ntpd : network


netfs : network


autofs : network


这看起来没什么,但是您知道这意味着什么吗?这意味着一旦 network 服务启动完成,我们可以 并行地启动 ntpd,netfs 和 autofs 服务。

做为一个特定的例子,假设所有的服务都需要10秒才能启动。用传统的服务启动方法,启动 network,ntpd,netfs 和 autofs 服务需要40秒。而用这种技术,只需要20秒——节约的50%的时间。

为什么会这样?好了请看,network 服务启动需要10秒时间,但是(因为当 rc 脚本在运行时,机器处于完全多任务的状态)其余三个服务可以 同时启动,所以这三个服务 合起来的启动时间是10秒。

事实上,大部分服务需要的启动时间可能不是10秒,但是既然每一个服务要做一些完全不同的事情,启动它们所需要的时间会很可观。

样例实现
我在
参考资料部分提供的压缩文件中有一个使用上述技术的样例实现。包括一个修改过的用于调用 make 命令的 rc 脚本,以及样例GNU makefile文件,分别是 runlevel.mk,start5.mk和stop5.mk。makefile 文件 runlevel.mk 是控制程序,start5.mk 文件和stop5.mk 文件分别是运行级5时启动和停止服务时的服务依赖描述文件。

注意所给出的启动和停止 makefile 文件提供的不是完全的服务间依赖关系列表,而仅仅是一个例子。同时也要注意,如果您不修改这些文件就在您的系统上使用,几乎不可能成功,因为您的服务列表可能和我的并不一样。

结束语(以及一些补充说明)
我提出了一种用来加快 Linux 机器引导速度的方法。这种方法允许系统在启动服务时启动顺序中靠后的部分服务并行启动,而不是以传统的串行方式启动,以实现引导的加速。这种方法在理论上没有问题,并且可以利用现有的系统工具实现。

这种方法的效率取决于需要启动的服务 的数量和每个服务启动所需要的时间。并行的可行性主要取决于服务间的依赖关系。对于某些系统来说,使用这种方法可能只会有很小的改进,但对于其他系统,它 可能会显著地影响引导速度。可以这样理解,每个系统都有不同的一组服务被激活,并且每一个服务需要不同的时间来启动。再强调一次,要使用这种方法,您需要 确定 您的特定系统的服务之间的依赖关系。

补充说明:

另一个哥们,BlueSilence写的如下:

并行启动服务加快系统启动速度

今天无意间看到一篇使系统服务并行运行加快系统启动速度的文章,于是
尝试了下,感觉办法很不错,同时让我对lfs的系统引导脚本有了更深的理解
现在系统从加载内核到login只要5秒,飞一样的感觉 ^_^
Ok ! Let`s Go !

因为要修改系统启动的脚本,很可能让系统启动不正常,所以最好有双linux系统,即使修改错误了
还可以从另一个系统引导修复。
如果很不幸,没有的话,可以先学下这招,紧急救护系统也有用 :p

紧急救援模式:

grub菜单中选择linux,按e,e,进入编辑模式,kernel......那一行最后加上
init=/bin/bash,这样引导系统可以得到一个bash shell
这样进入的系统会是read-only的,首先先使用fsck检查系统
fsck -a /dev/hdaX 处理根分区
fsck -R -A -a 处理其他分区
如果系统在上次重启,是正常重启的,文件系统是正常卸载的,
可以不用fsck检查直接把系统挂载成read-write的
mount / -o remount,rw
mount /proc
swap on -a 打开所有的交换分区
在修复好系统后将系统重新挂载成ro的,就可以安全重启了。
mount / -o remount,ro


简单的说一下linux系统的启动过程。

当grub加载内核后,内核执行/sbin/init,init程序读取/etc/inittab内容,开始系统的初始化。

init读取initdefault字段,取得系统运行级别

id:3:initdefault: 这里启动级别是3,通常是多用户字符登陆模式

init读取sysinit字段,开始系统基本的初始化

si::sysinit:/etc/rc.d/init.d/rc sysinit

这一行表示init会去执行/etc/rc.d/init.d/rc这个文件,sysinit是传入的参数,这里要做的是:
挂载proc系统拉,检查根分区拉,开启swap拉。等等
但是在不同的发行版可能不同,比如在Mandrakelinux中是:si::sysinit:/etc/rc.d/rc.sysinit
这样初始化的脚本就成了/etc/rc.d/rc.sysinit

init读取wait字段,开始系统服务初始化

l3:3:wait:/etc/rc.d/init.d/rc 3

经过基本初始化后,init读取run-level中的脚本,这些脚本位于
/etc/rc.d/rcX.d目录下。X是当前的运行级别.这里是系统启动的服务,比如network,alsa,httpd....等
/etc/rc.d/init.d/rc接收一个运行级别作为参数,然后逐一的开启/关闭/etc/rc.d/rcX.d目录下的脚本。
wait表示init会等待它结束再去执行其他程序。


有些发行版在执行rcX.d之后去执行/etc/rc.d/rc.local

最后就看到可爱的login拉
在/etc/rc.d/rcX.d中的脚本都是以"SXX+服务名"或者"KXX+服务名"组成的
并且都是到/etc/rc.d/init.d中相应脚本的符号链接,有的发行版是/etc/init.d,只是位置不同而已。
其中XX是0-9的数字,数字越小,则启动的时间越早。
以S开头的表示系统启动时传递start参数的服务,就是开启拉。K开头的就是
传递stop参数。
/etc/rc.d/rc3.d:
S10sysklogd@ S20network@ S25random@ S30httpd S40alsa@ S85numlock@
可以看出,我的系统进入rc3.d时首先启动的服务是sysklogd,最后是numlock
很明显,httpd服务必须要在network之后运行,不然没有网络哪来的web服务?
在lfs中是通过ls -v 列出它们,然后逐一的执行它们,这样就会使系统启动的速度很慢,服务越多越明显
现在希望做的就是:
让那些相互之间没有依赖关系的服务可以同时开启,而不是逐一的执行
这样系统启动的速度就会大大的提高了。
make 就是实现这个功能的工具。
相互有依赖关系的服务,让make去解决它们的依赖性。
再使用make -j 参数使服务可以并行启动。
在makefile中写入服务之间的相依赖关系:
httpd : network
这样就表示httpd依赖network,当network启动完毕,就可以立刻启动httpd
而其他不相依赖的服务列在makefile中,同时开启。
原理就是这样,下面是我的实际做法:

首先是写make的配置文件,该文件的样例在本文的结尾可以找到.
/etc/rc.d/runlevel.mk

########################################################################
# Description : Gnu Makefile to control the services in the specified
# runlevel. It will run the required services, and log
# the output of the services to the file
# /var/log/initd.start (for service startup) and
# /var/log/initd.stop (for service shutdown).
#
# This controlling program is designed to be invoked by
# the "/etc/rc.d/rc" script.
#
# Author : jameshunt@uk.ibm.com
#
# Notes :
#
# - Run as,
#
# make [-n] -j -f runlevel.mk
# RUNLEVEL=
# JOB=
#
# - $(JOB) is not validated - that is left to the service program.
# - $(RUNLEVEL) is not validated - that is left to the calling program
# (usually /etc/rc.d/rc).
# - It wouldn't take too much effort to auto-generate this Makefile.
#
########################################################################

# passed as a parameter
RUNLEVEL =

# passed as a parameter (start, stop, status, etc)
JOB =

# set to a value to enable debug output
DEBUG =

########################################################################
# START CONFIGURATION

# system commands used by this facility
CAT = /bin/cat
RM = /bin/rm
ECHO = /bin/echo
DATE = /bin/date

# Directory containing scripts/programs to run.
INITD_DIR := /etc/rc.d/init.d #这里要修改成自己系统的所有服务脚本存放目录
#有些发行版是/etc/init.d 这个目录必须正确

# Directory into which a lock file is created when a service starts.
# (Note that the lock file is created by the service).
SUBSYS_FILE_DIR := /var/lock/subsys #这个目录必须存在,如果没有自己建立

# Used to create temporary files, before collating them all into
# $(FINAL_OUTPUT_FILE).
TMP_DIR := /tmp

TMPFILE_PREFIX := .runlevel
TMP_FILE = $(TMP_DIR)/$(TMPFILE_PREFIX).$(JOB).$@

# File that contains all output of programs/scripts run.
FINAL_OUTPUT_FILE = /var/log/initd.$(JOB)

# List of *all* services.
#
# (Important Note: if you don't include a service in this list,
# it won't get run!)
#这里写上所有的需要启动的服务,这些服务的名字必须要与/etc/rc.d/init.d
#中的名字一致的。这样make 就会去并行的开启它们拉。
ALL =
sysklogd
network
httpd
random
alsa
numlock

# END CONFIGURATION
########################################################################

# Check command-line parameters
ifndef RUNLEVEL
$(error must specify RUNLEVEL, so I know what to run)
endif

ifndef JOB
$(error must specify JOB, so I know what to do)
endif

default: $(ALL) create_final_output_file
ifneq ($(DEBUG),)
@$(ECHO) "RUNLEVEL=$(RUNLEVEL)"
@$(ECHO) "JOB=$(JOB)"
@$(ECHO) "FINAL_OUTPUT_FILE=$(FINAL_OUTPUT_FILE)"
@$(ECHO) "TMP_FILE=$(TMP_FILE)"
@$(ECHO) "ALL=|$(ALL)|"
#@$(ECHO)
#@$(ECHO) "ALL (less local)=|$(filter-out local,$(ALL))|"
#@$(ECHO)
#@$(ECHO) "ALL (less kudzu)=|$(filter-out kudzu,$(ALL))|"
#@$(ECHO)
endif

##############################################################
# Generic rule to control a service.
#
# Note that we capture all output to a file.

$(ALL) : $(SUBSYS_FILE_DIR)/$@
@$(ECHO) "Begin "$(JOB) $@" at `$(DATE)`" > $(TMP_FILE)
@$(INITD_DIR)/$@ $(JOB) >> $(TMP_FILE) 2>&1
@$(ECHO) "End "$(JOB) $@" at `$(DATE)`" >> $(TMP_FILE)

##############################################################
# List of services that have dependencies.
#
# (Note: It is not necessary to list services that have no
# dependencies).

# Include the relevant dependencies. If you intend to use this facility,
# you must provide 2 makefiles / runlevel, one for starting the services
# in the runlevel, and one for stopping the services in the runlevel.
#
# WARNING: If make attempts to include a file that does not exist, it will
# exit. This could cause your system to boot in an unfamiliar way.
include /etc/rc.d/start3.mk #这里是服务有依赖关系的记录文件位置.
#
# Lastly, merge all the service output files into a single file.
# Note that the order of the service output in the merged file is not
# chronological.
create_final_output_file :
$(CAT) $(TMP_DIR)/$(TMPFILE_PREFIX).$(JOB).*
> $(FINAL_OUTPUT_FILE)
$(RM) -f $(TMP_DIR)/$(TMPFILE_PREFIX).$(JOB).*

# EOF

注意的地方:

/etc/rc.d/start3.mk 由上面的runlevel.mk决定位置名称
httpd : network
其中的空格必须是用TAB键打出来的

makefile写好了,现在修改rc脚本,让它当传递进来参数为3时
不去逐一的执行/etc/rc.d/rc3.d中的各个脚本,而去通过make
执行刚刚写好的runlevel.mk,实现并行启动服务。
很简单,一行判断语句 ^_^

/etc/rc.d/init.d/rc :

#!/bin/sh
# Begin $rc_base/init.d/rc - Main Run Level Control Script

# Based on rc script from LFS-3.1 and earlier.
# Rewritten by Gerard Beekmans - gerard@linuxfromscratch.org

. /etc/sysconfig/rc
. $rc_functions

# This sets a few default terminal options.
stty sane

# These 3 signals will not cause our script to exit
trap "" INT QUIT TSTP

[ "" != "" ] && runlevel=

if [ "$runlevel" = "" ]
then
echo "Usage: " >&2
exit 1
fi

previous=$PREVLEVEL
[ "$previous" = "" ] && previous=N

if [ ! -d $rc_base/rc$runlevel.d ]
then
echo "$rc_base/rc$runlevel.d does not exist"
exit 1
fi

# Attempt to stop all service started by previous runlevel,
# and killed in this runlevel
if [ "$previous" != "N" ]
then
for i in $(ls -v $rc_base/rc$runlevel.d/K* 2> /dev/null)
do

check_script_status

suffix=$
prev_start=$rc_base/rc$previous.d/S[0-9][0-9]$suffix
sysinit_start=$rc_base/rcsysinit.d/S[0-9][0-9]$suffix

if [ "$runlevel" != "0" ] && [ "$runlevel" != "6" ]
then
if [ ! -f $prev_start ] && [ ! -f $sysinit_start ]
then
echo -n -e $WARNING
echo "$i can't be executed because it was"
echo "not started in the previous runlevel ($previous)"
echo -n -e $NORMAL
continue
fi
fi
$i stop
error_value=$?

if [ "$error_value" != "0" ]
then
print_error_msg
fi
done
fi
################# 由于我都是从文本模式登陆,所以加上判断句,如果
##runlevel为3的话,就去执行runlevel.mk,而不逐一执行这些脚本
if [ "$runlevel" = 3 ]
then

make -j -f /etc/rc.d/runlevel.mk RUNLEVEL=3 JOB=start
# -j 表示以并行的方式去启动在runlevel.mk中定义的服务
# -f 指定MakeFile为runlevel.mk,否则make只尝试运行
#当前目录下的MakeFile.RUNLEVEL=3在这里没有任何意义,因为
#我没有使用$runlevel可以在任何run-level下并行服务
#
#JOB=start表示去start /etc/rc.d/rc3.d中的服务

else

#如果启动级别不是3的话,仍然按照正常去引导系统.
#这样就不用写stop的脚本,我只想让系统启动的更快些
#因为lfs的启动脚本比较怪,它不象其他的发行版使用/etc/rc.d/rc.sysinit
#作为系统的初始化,而是把那些过程分开成几个脚本运行
#si::sysinit:/etc/rc.d/init.d/rc sysinit 传入的sysinit参数去执行
#/etc/rc.d/rcsysinit.d目录下的脚本,这样我就不能轻易的使用$runlevel
#去使在任何运行级别都可以并行服务了,它会传进来个sysinit @_@
#
for i in $( ls -v $rc_base/rc$runlevel.d/S* 2> /dev/null)

do

if [ "$previous" != "N" ]
then
suffix=$
stop=$rc_base/rc$runlevel.d/K[0-9][0-9]$suffix
prev_start=$rc_base/rc$previous.d/S[0-9][0-9]$suffix

[ -f $prev_start ] && [ ! -f $stop ] && continue
fi

check_script_status

case $runlevel in
0|6) $i stop ;;
*) $i start ;;
esac
error_value=$?

if [ "$error_value" != "0" ]
then
print_error_msg
fi
done
fi
# End $rc_base/init.d/rc

要注意的地方:


每个发行版的rc脚本都不相同,要先熟悉系统启动过程,然后再尝试修改,这样才不会出错。
修改rc脚本时,语法必须完全正确。修改后用sh -n rc 测试有没有语法错误
有一点错误的话,系统就进不去了。:(
另外这个rc脚本位置是由/etc/inittab决定的。就是 "l3:3:wait:/etc/rc.d/init.d/rc 3 "啦

相关资源:

《通过并行化 Linux 系统服务来提高引导速度》
http://www-900.ibm.com/developerWorks/cn/linux/l-boot/index.shtml
我就是看了这篇文章做的了,打算试试的话,必须要看看啦。里面有作者的各个脚本样例。
《From PowerUp To Bash Prompt》
http://www.faqs.org/docs/Linux-HOWTO/From-PowerUp-To-Bash-Prompt-HOWTO.html
这篇文章详细的介绍了sys V风格的启动过程,绝对的好文!不可错过!
请使用浏览器的分享功能分享到微信等