(一)System V init
首先我们来讨论PC上运行的Linux系统,它启动后从BIOS开始,进入bootloader,有bootloader加载内核,进入内核初始化。内核启动的最后一步就是创建一个pid为1的init进程。这是系统的第一个进程,它负责生产其他所有用户进程,因此它是所有其他进程的祖先。
Linux如果仅仅将内核运行起来,实际上对用户来说没有任何可操作性,所以需要init进程进行系统的初始化工作,来把操作系统带入可操作状态。init 以守护进程的方式存在,它会按照inittab中定义的行为来工作。init进程要负责产生子进程来进行系统的初始化工作,从而让计算机系统进入某种用户预订的运行模式。这些工作包括:设置机器名、检查和安装磁盘及文件系统、启动系统日志、配置网络接口并启动网络和邮件服务,启动打印服务等。
大多数 Linux 发行版的 init 系统是和 System V 相兼容的,被称为 sysvinit。这是人们最熟悉的 init 系统。当然也有一些发行版采用了更新的init系统,比如upstart和systemd。本文将只介绍sysvinit这种类型,它是大家最熟悉和常见的类型。
SysVinit 运行非常良好,概念简单清晰。它主要依赖于 Shell 脚本,这就决定了它的最大弱点:相对其他两种init系统启动较慢。
init进程的执行主要依赖于inittab配置文件,而其中有一个重要的概念就是运行模式。Sysvinit用术语 runlevel 来定义”运行模式”,而在inittab中,是使用initdefault指定一个默认的运行级别,如果 inittab文件中没有包含initdefault的记录项,则在系统启动时请求用户为它指定一个初始运行级别。
Sysvinit通常把系统运行模式分为8种,即运行模式0~6和S,其中的S模式是用来简单的启动系统。不同的Linux发行版对于运行模式的定义都不太一样,不过0,1,6这三种模式却得到大家的一致认可。
模式0:代表关机
模式1:代表单用户模式
模式6:代表重启
这个运行级别将决定系统按照那种runlevel来进行初始化操作,而针对每个运行级别,inittab还需要定义相应的执行脚本,由此进行不同的初始化操作。
inittab文件中每一记录都从新的一行开始,每个记录项最多可有512个字符,每一项的格式通常如下所示:
id:runlevels:action:process
解释:
id
该字段是最多4个字符的字符串,用来标志该项记录。
runlevels
该字段定义该记录项被调用时的运行级别,可以由一个或多个运行级别构成,空则代表运行级别0~6。当请求init改变运行级别时,那些runlevels字段中不包括新运行级别的进程将会被杀死(先收到SIGTERM信号,然后是SIGKILL信号);
action
该字段告诉init进程要执行的动作,即如何处理process字段指定的进程,会在后面详细解读。
action字段允许的值及对应的动作:
(1)respawn:如果process字段指定的进程不存在,则启动该进程,init不等待处理结束,而是继续扫描inittab文件中的后续进程,当这样的进程终止时,init会重新启动它,如果这样的进程已存在,则什么也不做。
(2)wait:启动process字段指定的进程,并等到处理结束才去处理inittab中的下一记录项。
(3)once:启动process字段指定的进程,不等待处理结束就去处理下一记录项。当这样的进程终止时,也不再重新启动它,在进入新的运行级别时,如果这样的进程仍在运行,init也不重新启动它。
(4)boot:只有在系统启动时,init才处理这样的记录项,启动相应进程,并不等待处理结束就去处理下一个记录项。当这样的进程终止时,系统也不重启它。
(5)bootwait:系统启动后,当第一次从单用户模式进入多用户模式时处理这样的记录项,init启动这样的进程,并且等待它的处理结束,然后再进行下一个记录项的处理,当这样的进程终止时,系统也不重启它。
(6)powerfail:当init接到断电的信号(SIGPWR)时,处理指定的进程。
(7)powerwait:当init接到断电的信号(SIGPWR)时,处理指定的进程,并且等到处理结束才去检查其他的记录项。
(8)off:如果指定的进程正在运行,init就给它发SIGTERM警告信号,在向它发出信号SIGKILL强制其结束之前等待5秒,如果这样的进程不存在,则忽略这一项。
(9)ondemand:功能通respawn,不同的是,与具体的运行级别无关,只用于runlevels字段是a、b、c的那些记录项。
(10)sysinit:指定的进程在访问控制台之前执行,这样的记录项仅用于对某些设备的初始化,目的是为了使init在这样的设备上向用户提问有关运行级别的问题,init需要等待进程运行结束后才继续。
(11)initdefault:指定一个默认的运行级别,只有当init一开始被调用时才扫描这一项,如果runlevels字段指定了多个运行级别,其中最大的数字是默认的运行级别,如果runlevels字段是空的,init认为字段是0123456,于是进入级别6,这样便陷入了一个循环,如果 inittab文件中没有包含initdefault的记录项,则在系统启动时请求用户为它指定一个初始运行级别。
process
指定要执行的程序或者脚本。
我们接下来介绍两个例子:
This is an example of a inittab which resembles the old Linux inittab,This inittab file executes /etc/rc during boot and starts gettys on tty1-tty4.
# inittab for linux
id:1:initdefault:
rc::bootwait:/etc/rc
1:1:respawn:/etc/getty 9600 tty1
2:1:respawn:/etc/getty 9600 tty2
3:1:respawn:/etc/getty 9600 tty3
4:1:respawn:/etc/getty 9600 tty4
这个例子是一个老的Linux中的启动流程。initdefault指定了运行模式为1,而bootwait指定了一个脚本/etc/rc,这个脚本中会读取当前所处的运行级别,根据不同的运行级别来进行初始化操作。
当然了,我们也可以自定义更复杂启动流程,比如:
# Level to run in
id:2:initdefault:
# System initialization before anything else.
si::sysinit:/etc/rc.d/bcheckrc
# Runlevel 0,6 is halt and reboot, 1 is maintenance mode.
l0:0:wait:/etc/rc.d/rc.halt
l1:1:wait:/etc/rc.d/rc.single
l2:2345:wait:/etc/rc.d/rc.multi
l6:6:wait:/etc/rc.d/rc.reboot
# What to do at the "3 finger salute".
ca::ctrlaltdel:/sbin/shutdown -t5 -rf now
# Runlevel 2&3: getty on console, level 3 also getty on modem port.
1:23:respawn:/sbin/getty tty1 VC linux
2:23:respawn:/sbin/getty tty2 VC linux
3:23:respawn:/sbin/getty tty3 VC linux
4:23:respawn:/sbin/getty tty4 VC linux
S2:3:respawn:/sbin/uugetty ttyS2 M19200
一般Linux发行版的初始化过程会通过在inittab中的配置和脚本的嵌套调用来执行如下几个脚本,按照顺序分别是:
/etc/rc.d/rc.sysinit
/etc/rc.d/rcX.d/ (X 代表运行级别 0-6)
/etc/rc.d/rc.local
rc.local就是在一切初始化工作后,Linux留给用户进行个性化的地方。你可以把你想设置和启动的东西放到这里
(二)嵌入式busybox init
以上这些都是介绍的标准的linux System V的标准的init系统,而对于使用busybox init的嵌入式系统而言,还是有一些区别的。
BusyBox是嵌入式的一个工具包,除了提供基础的命令以外,还支持init功能,如同其它的init一样,busybox的init也是完成系统的初始化工作,关机前的工作等等。
嵌入式linux系统的内核启动之后,会找到根文件系统里面的init程序并执行它,BusyBox的init进程会依次进行以下工作:
1. 为init设置信号处理过程
2. 初始化控制台
3. 解析/etc/inittab文件
4. 执行所有的sysinit命令,缺省情况下会使用/etc/init.d/rcS(动作类型:sysinit)
5. 执行所有的wait命令(动作类型:wait)
6. 执行所有的once命令(动作类型:once)
完成以上工作后init进程便会循环执行以下进程:
1.执行所有终止时必须重新启动的inittab命令(动作类型:respawn)
2.执行所有终止时必须重新启动但启动前必须询问用户的inittab命令(动作类型:askfirst)
和上面的介绍一样,inittab文件中每一行的格式如下所示:
id:runlevel:action:process
尽管此格式与传统的Sytem V init类似,但是其中的含义还是有些不同的:
(1)id:对BusyBox而言,id用来指定启动进程的控制tty。如果所启动的进程并不是可以交互的shell,例如BusyBox的sh(ash),应该会有个控制tty,如果控制tty不存在,Busybox的sh会报错。
(2)runlevel:BusyBox将会完全忽略runlevel字段,该字段为了和传统的Sytem V init格式保持一致而保留,一般空着它即可。
(3)process字段用来指定所执行程式的路径,包括命令行选项。
(4)action字段用来指定下面的8个可应用到process的动作之一。
action的定义是和sysvinit不同的,具体如下所示:
(1)sysinit
系统起来后首先运行的程序,init进程需要等待该动作完成以后才能继续
(2)respawn
每当相应的进程终止执行便会重新启动
(3)askfirst
类似respawn,不过它的主要用途是减少系统上执行的终端应用程序的数量。它将会促使init在控制台上显示“Please press Enter to active this console”的信息,并在重新启动之前等待用户按下enter键
(4)wait
告诉init必须等到相应的进程完成之后才能继续执行
(5)once
仅执行相应的进程一次,而且不会等待它完成
(6)ctratldel
当按下Ctrl+Alt+Delete组合键时,执行相应的进程
(7)shutdown
当系统关机时,执行相应的进程
(8)restart
当init重新启动时,执行相应的进程,通常此处所执行的进程就是init本身
举个示例:
::sysinit:/etc/init.d/rcS
tty2::askfirst:-/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
::restart:/sbin/init