根据前文Linux根文件系统挂载流程中的分析,内核通过调用根文件系统中的init程序跳转到用户空间,并对用户空间所需的基础框架进行初始化。类比于内核启动时,需要对各个功能模块进行初始化,当进入用户空间时,也需要启动各项服务来搭建基础的应用环境。对于不同的init系统管理器,服务启动的方式有所区别,下文主要对yocto中使用的sysVinit、busybox init和systemd进行介绍。
在sysvinit中,服务由shell脚本提供,并且不同的runlevel包含不同的shell脚本集合,可将系统启动到具有不同服务的用户模式。其中,所有能够提供服务的shell脚本都包含在/etc/init.d目录下,对目录下的shell脚本进行不同的组合便可以组成具有不同服务的用户模式,即runlevel,其结构映射如下所示:
如上图,可以将包含所有shell脚本的/etc/init.d看作一个服务库,通过提取库中的不同服务并组合便可以定制不同的系统模式,而系统启动到哪种模式,由配置文件/etc/inittab指定,/etc/inittab文件的内容由多个指定格式的项所组成,每一项的格式规定如下:
label:runlevel:action:process
以一个具体的文件作为示例:
id:5:initdefault: //默认的运行级别为5
si::sysinit:/etc/init.d/rcS //系统初始化首先运行/etc/init.d/rcS脚本
~~:S:wait:/sbin/sulogin //单用户模式需要运行sbin/sulogin脚本
l0:0:wait:/etc/init.d/rc 0 //执行/etc/init.d/rc 传入参数0,等待脚本执行完毕
l1:1:wait:/etc/init.d/rc 1
l2:2:wait:/etc/init.d/rc 2
l3:3:wait:/etc/init.d/rc 3
l4:4:wait:/etc/init.d/rc 4
l5:5:wait:/etc/init.d/rc 5
l6:6:wait:/etc/init.d/rc 6
sysvinit系统启动流程:
根据以上示例可以看出:系统默认启动到图形化多用户模式(runlevel 5);然后执行初始化的rcS脚本,该rcS脚本会启动单用户模式(S)的脚本集合;进入单用户模式,启动sulogin;进入多用户模式,启动/etc/init.d/rc脚本并输入参数5,该进程将执行属于runlevel=5的脚本集合。
每个runlevel的脚本集合存储在对应的/etc/rc$(runlevel).d目录下,该目录下所有的文件为/etc/init.d的符号链接。当init.d中的文件不存在时,runlevel对应目录下的文件将不复存在。在yocto项目中,符号链接在编译阶段,通过 update.rc映射产生,并且生成shell脚本的执行顺序。update.rc的示例如下:
update-rc.d apache2 start 20 2 3 4 5 . stop 80 0 1 6 .
apache2为可执行程序的名称,start 20 2 3 4 5表示该服务在进入runlevel 2 3 4 5时需要执行,并在第20个启动。此时,update.rc将在runlevel 2 3 4 5中分别映射一个指向/etc/init.d/apache2的符号链接,名称均为S20apache2。stop 80 0 1 6表示在进入runlevel 0 1 6时需要禁止,并在第80个关闭。此时,update.rc将在runlevel 0 1 6 中分别映射一个指向/etc/init.d/apache2的符号链接,名称均为K80apache2。
sysvinit在进入对应runlevel时,根据/etc/init.d/rc可知,首先执行所有以K开头的脚本(传入脚本的参数为stop),然后执行所有以S开头的脚本(传入脚本的参数为start)。所有以K开头的脚本按照紧接着的数字,从小到大开始执行,相同数字的脚本按照创建顺序执行。同理,对于所有以S开头的脚本,其执行顺序遵循同样的规则。
busybox是众多linux命令的一个工具集,根目录下的bin,sbin,usr和linuxrc通常就是Busybox。使用busybox时,kernel cmdline的init一般指向linuxrc,但是linuxrc最终还是会调用到/sbin/init这个真正的init进程。init进程的执行流如下:
busybox中/etc/inittab文件具有与sysvinit类似的格式,只是各字段定义有所区别:
id:runlevel_ignored:action:command
由于busybox并不支持runlevel的概念,可以认为在busybox中只有一个runlevel,系统启动时自动跳转到该runlevel并执行启动脚本/etc/init.d/rcS。在yocto项目当中,通过配置/etc/inittab和/etc/init.d/rcS可以兼容sysvinit生成的脚本。
/etc/inittab文件
::sysinit:/bin/mount -t proc proc /proc
::sysinit:/bin/mount -o remount,rw /
::sysinit:/bin/mount -a
::sysinit:/etc/init.d/rcS
::ctrlaltdel:/sbin/reboot
::shutdown:/etc/init.d/rcK
init程序顺序执行含sysinit字段的进程,最后会调用etc/init.d/rcS(在没有inittab文件的情况下,init进程会默认查找rcS脚本文件并执行),通过设定/etc/init.d/rcS的内容如下,将自动启动所有在/etc/rcS.d和/etc/rc5.d中的脚本:
#!/bin/sh
for i in /etc/rcS.d/S??* /etc/rc5.d/S??* ;do //查找etc/rcS.d和etc/rc5.d下所有以S开头的文件并执行
case "$i" in
*.sh)
# Source shell script for speed.
(
trap - INT QUIT TSTP
set start
. $i
)
;;
*)
$i start
;;
esac
done
同理,可以设定/etc/init.d/rcK用于关闭所有的服务。
不同于sysvinit和busybox init的服务启动方式,systemd通过解析unit配置文件启动对应的服务,每一个unit中包含启动对应服务的方式、依赖项等信息。根据服务的类型,systemd将服务归为以下几类:
systemd通过unit配置文件指明应调用哪个程序开启/关闭该服务,并指定该服务与其他服务之间的依赖关系。不像sysvinit中通过数字大小指定程序的运行顺序,systemd中通过before/after、require、wants、RequiredBy/WantedBy关键词来指定服务间的依赖关系。几个常用的描述依赖的名词解释如下:
一个service unit的示例如下:
[UNIT]
Description=My custom service
Requires=network.target //在本服务启动之前,需先启动network.target
[SERVICE]
Type=simple
ExecStart=/usr/bin/sleep infinity //启动服务时执行的命令和参数
[INSTALL]
WantedBy=multi-user.target //定义该服务在multi-user.target中启动
UNIT字段描述该项服务的基本信息以及配置与其他服务之间的依赖关系;SERVICE字段只有service unit才有,用于提供执行服务的指令;INSTALL字段主要用于定义启动的时机,是否开机启动 。
systemd中的/sbin/init程序为指向/usr/lib/systemd/systemd的一个符号链接,运行该init程序将执行systemd的初始化流程,使能default.target包含的所有服务,default.target类似于sysVinit中的默认runlevel,为一个服务集合,并由unit单元中的依赖关系所定义。systemd中的target和sysvinit中的runlevel存在如下对应关系:
0 poweroff.target
1、s rescue.target
2、3、4 multi-usr.target
5 graphical.target
6 reboot.target
emergency emergency.target
与sysVinit不同的是,graphical.target依赖于multi-user.target,即graphical.target包含multi-user.target的服务集合。服务通过sysctrl enable命令,自动将该服务映射到WantBy=所指向的target服务集合中,而当target被设置为default target时,该服务将在systemd启动时便开启。target的服务集合保存在/etc/systemd/system/**.target/wants目录下,目录下为存储在/etc/systemd/system/和/usr/lib/systemd/system/目录下服务unit的符号链接。即此时提供的服务库有两个(etc和usr),当两个库中包含同一各服务的unit单元时,/etc/目录下的unit 具有更高的优先级,即lib路径下的unit不会被解析,systemd中target的映射如下:
systemd通过systemd-sysv-generator可以兼容sysvinit中的shell脚本。systemd-sysv-generator通过识别shell脚本的LSB头部信息,建立服务间的依赖关系并转化成unit单元。此外,systemd同样兼容/etc/fstab对文件系统的挂载方式,通过systemd-fstab-generator读取/etc/fstab配置文件,并将其中的每一条挂载项转换为systemd的本地unit,fstab中的auto标识会使systemd将其转换而成的service添加到local-fs.target或remote-fs.target的依赖中,使其在系统启动时自动挂载,而noauto则不会。对于同一挂载点,/etc/fstab的优先级低于/etc/目录下的.mount unit但是高于/usr/lib/目录下.mount unit。
systemd不支持sysV的服务在early boot阶段运行,都在basic.target之后运行。如果同一个服务在多个源中都有配置,如/etc/systemd/system/avahi.service和/etc/init.d/avahi,此时则忽略sysVinit中的服务,采用systemd本地的服务。
systemd支持模板机制,例如提供一个[email protected]的模板,根据该模板可以被实例化为[email protected]等。这个@后面的参数可以被service中的内容所继承。
sysvinit和systemd的服务生成过程对应如下,其添加服务的过程类似:在服务库中添加文件;通过工具/配置文件指定依赖关系,并添加到指定用户模式的服务集合中。
根据core-image.bbclass,保证发行版正常启动的根文件系统包括如下几个packagegroup的内容:
CORE_IMAGE_BASE_INSTALL = '\
packagegroup-core-boot \
packagegroup-base-extended \
\
${CORE_IMAGE_EXTRA_INSTALL} \
'
其中,packagegroup-core-boot.bb的部分内容如下:
VIRTUAL-RUNTIME_dev_manager ?= "udev"
VIRTUAL-RUNTIME_login_manager ?= "busybox"
VIRTUAL-RUNTIME_init_manager ?= "sysvinit"
VIRTUAL-RUNTIME_initscripts ?= "initscripts"
VIRTUAL-RUNTIME_keymaps ?= "keymaps"
EFI_PROVIDER ??= "grub-efi"
SYSVINIT_SCRIPTS = "${@bb.utils.contains('MACHINE_FEATURES', 'rtc', '${VIRTUAL-RUNTIME_base-utils-hwclock}', '', d)} \
modutils-initscripts \
init-ifupdown \
${VIRTUAL-RUNTIME_initscripts} \
"
RDEPENDS_${PN} = "\
base-files \
base-passwd \
${VIRTUAL-RUNTIME_base-utils} \
${@bb.utils.contains("DISTRO_FEATURES", "sysvinit", "${SYSVINIT_SCRIPTS}", "", d)} \
${@bb.utils.contains("MACHINE_FEATURES", "keyboard", "${VIRTUAL-RUNTIME_keymaps}", "", d)} \
${@bb.utils.contains("MACHINE_FEATURES", "efi", "${EFI_PROVIDER} kernel", "", d)} \
netbase \
${VIRTUAL-RUNTIME_login_manager} \
${VIRTUAL-RUNTIME_init_manager} \
${VIRTUAL-RUNTIME_dev_manager} \
${VIRTUAL-RUNTIME_update-alternatives} \
${MACHINE_ESSENTIAL_EXTRA_RDEPENDS}"
RRECOMMENDS_${PN} = "\
${MACHINE_ESSENTIAL_EXTRA_RRECOMMENDS}"
由上可知,packagegroup至少包括base-files、base-passwd、VIRTUAL-RUNTIME_base-utils、VIRTUAL-RUNTIME_login_manager、VIRTUAL-RUNTIME_init_manager、VIRTUAL-RUNTIME_dev_manager、VIRTUAL-RUNTIME_update-alternatives几个package的内容。其中,具有RUNTIME_RUNTIME前缀的package可通过重写指定。如用户可以根据不同的发行版需求同的init manger:
VIRTUAL-RUNTIME_init_manager =
当init_manager选择sysvinit时,还需要安装initscripts package,该package包括init.d目录下shell脚本的安装:
VIRTUAL-RUNTIME_initscripts =
当需要往开机自启动中添加自己的服务时,无论对于sysvinit还是systemd,首先需要向服务库中添加自己的服务(init.d中的shell脚本,systemd/system中的unit单元),然后将服务添加到默认启动的服务集合当中并指定执行的顺序/依赖(sysvinit 通过update.rc,systemd中通过uint配置和systemdctrl 实现),yocto中存在update.rc和systemd的类来实现对应的功能。
对于sysVinit而言,需要在.bb文件中inherit update.rc:
inherit update.rc
INITSCRIPT_PACKAGES ?= "${PN}" #服务存在的package,默认为bb文件的name
INITSCRIPT_NAME = “first” #服务的名称
INITSCRIPT_PARAMS = "defaults 97" #服务的启动/关闭的顺序和所属runlevel
当需要在同一个.bb文件中安装多个服务时,可以将服务置于不同的package中,并利用关键词分别对这些服务指定运行顺序,分别在${PN}和${PN}-second两个package中存在一个服务时,如下配置:
FILES_${PN}-second = “ \
${sysconfdir}/init.d/second.sh
”
INITSCRIPT_PACKAGES = “${PN} ${PN}-second”
INITSCRIPT_NAME_${PN} = “first.sh”
INITSCRIPT_PARAMS = "defaults 97" //对first进行映射
INITSCRIPT_NAME_${PN}-second = “second.sh”
INITSCRIPT_PARAMS${PN}-second = "start 20 2 3 4 5 . stop 80 0 1 6 ." //对second进行映射
.bb文件生成package时通过do_install过程实现的,默认package的名称为${PN},内容为本.bb文件do_install任务所有安装到根文件系统目录下的文件,当将do_install中的文件指定到特定的package中时,${PN}中将不再包含对应的内容,即先打包其他的package,最后剩余的统一打包到${PN}。如下,${PN}中的内容就是除了${PN}-second中的first文件。
do_install () {
install -d ${D}${sysconfdir}/init.d
install -m 0755 ${THISDIR}/files/${MACHINE}/script/first ${D}/${sysconfdir}/init.d/first
install -m 0755 ${THISDIR}/files/${MACHINE}/script/second ${D}/${sysconfdir}/init.d/second
install -d ${D}${systemd_unitdir}/system/
install -m 0644 ${THISDIR}/files/${MACHINE}/script/first.service ${D}${systemd_unitdir}/system
install -m 0644 ${THISDIR}/files/${MACHINE}/script/second.service ${D}${systemd_unitdir}/system
}
FILES_${PN}-second ="\
${D}/${sysconfdir}/init.d/second
${D}${systemd_unitdir}/system/second.service
"
INITSCRIPT_PACKAGES += "${PN}-second"
对于systemd而言,则需要inherit systemd:
inherit systemd
SYSTEMD_PACKAGES ?= "${PN}"
SYSTEMD_SERVICE = “first.service” //提供服务unit配置文件
当需要在同一个.bb文件中提供多个服务时,可以采用与sysvinit中同样的方式,即将不同的service分别安装到不同的package中。但是systemd的依赖关系存在于各自的unit单元中,并不一定需要为每个service单独一个package,而是可以按如下方式指定:
inherit systemd
SYSTEMD_PACKAGES ?= "${PN}"
SYSTEMD_SERVICE_${PN} = “first.service second.service”
由于在systemd中包含如下默认配置,因此所有通过上述关键词声明的服务都将在开机时自动启动:
SYSTEMD_AUTO_ENABLE ??= “enable”
上述语句相当于调用systemctl enable xx.service。当不想让服务开机自启动时,可以在.bb文件中显示的失能该功能,在需要的时候通过手动调用systmctl命令启动:
SYSTEMD_AUTO_ENABLE = “disable”
当然,无论是sysvinit还是systemd,开机自启动能够成立的前提是:在do_install task中已将对应的服务放置到的/etc/init.d目录下或者在etc/systemd/system目录下了
yocto中可以根据需要建立多个发行版,并根据DISTRO变量自动切换使用不同的initmanager,为了在同一个.bb文件中兼容两种init manager的格式,可以将sysvinit和systemd需要包含的文件同时包含到一个package,当在distro配置文件中选择使用其中一个而失能另一个init manager时,bitbake会自动删除package中不需要的文件(失能的init manager安装到package中的文件):
inherit update.rc systemd
FILES_${PN}-second = “ \
${sysconfdir}/init.d/second.sh
${D}${systemd_unitdir}/system/second.service
”
INITSCRIPT_PACKAGES = “${PN} ${PN}-second”
INITSCRIPT_NAME_${PN} = “first.sh”
INITSCRIPT_PARAMS = "defaults 97"
INITSCRIPT_NAME_${PN}-second = “second.sh”
INITSCRIPT_PARAMS${PN}-second = "start 20 2 3 4 5 . stop 80 0 1 6 ."
SYSTEMD_PACKAGES = “${PN} ${PN}-second”
SYSTEMD_SERVICE_${PN} = “${PN} ${PN}-second”
SYSTEMD_SERVICE_${PN}-second ="second.service"
distro配置文件中一般默认的init manager为sysvinit,当需要选用systemd时,应该添加systemd到DISTRO_FEATURE项中:
DISTRO_FEATURES_append = " systemd"
当使能systemd并且删除sysvinit中包含的文件时,通过如下关键词进行配置:
DISTRO_FEATURES_BACKFILL_CONSIDERED += “sysvinit”
删除失能的init manager包含的文件由systemd.bbclass实现:
python rm_systemd_unitdir (){
import shutil
if not bb.utils.contains('DISTRO_FEATURES', 'systemd', True, False, d): #没有使能systemd,使能了sysvinit时
systemd_unitdir = oe.path.join(d.getVar("D"), d.getVar('systemd_unitdir'))
if os.path.exists(systemd_unitdir):
shutil.rmtree(systemd_unitdir)
systemd_libdir = os.path.dirname(systemd_unitdir)
if (os.path.exists(systemd_libdir) and not os.listdir(systemd_libdir)):
os.rmdir(systemd_libdir)
}
do_install[postfuncs] += "rm_systemd_unitdir " #安装之后立即执行该函数
python rm_sysvinit_initddir (){
import shutil
sysv_initddir = oe.path.join(d.getVar("D"), (d.getVar('INIT_D_DIR') or "/etc/init.d"))
if bb.utils.contains('DISTRO_FEATURES', 'systemd', True, False, d) and \ #使能了systemd并失能了sysvinit时
not bb.utils.contains('DISTRO_FEATURES', 'sysvinit', True, False, d) and \
os.path.exists(sysv_initddir):
systemd_system_unitdir = oe.path.join(d.getVar("D"), d.getVar('systemd_system_unitdir'))
# If systemd_system_unitdir contains anything, delete sysv_initddir
if (os.path.exists(systemd_system_unitdir) and os.listdir(systemd_system_unitdir)):
shutil.rmtree(sysv_initddir)
}
do_install[postfuncs] += "rm_sysvinit_initddir "
systemd各task的执行顺序如下,添加服务时可以根据该执行顺序指定添加服务的依赖关系:
local-fs-pre.target
|
v
(various mounts and (various swap (various cryptsetup
fsck services...) devices...) devices...) (various low-level (various low-level
| | | services: udevd, API VFS mounts:
v v v tmpfiles, random mqueue, configfs,
local-fs.target swap.target cryptsetup.target seed, sysctl, ...) debugfs, ...)
| | | | |
\__________________|_________________ | ___________________|____________________/
\|/
v
sysinit.target
|
____________________________________/|\________________________________________
/ | | | \
| | | | |
v v | v v
(various (various | (various rescue.service
timers...) paths...) | sockets...) |
| | | | v
v v | v rescue.target
timers.target paths.target | sockets.target
| | | |
v \_________________ | ___________________/
\|/
v
basic.target
|
____________________________________/| emergency.service
/ | | |
| | | v
v v v emergency.target
display- (various system (various system
manager.service services services)
| required for |
| graphical UIs) v
| | multi-user.target
| | |
\_________________ | _________________/
\|/
v
graphical.target
sysvinit:
https://blog.csdn.net/ranran0224/article/details/61196466
https://blog.csdn.net/wince_lover/article/details/52503405
https://www.ibm.com/developerworks/cn/linux/l-lpic1-v3-101-3/index.html
sysvinit源码分析
update-rc.d更新linux系统启动项
https://manpages.debian.org/testing/sysvinit-core/init.8.en.html
busybox init
https://blog.csdn.net/shanzhizi/article/details/39082495
http://blog.chinaaet.com/weiqi7777/p/5100052805
https://blog.csdn.net/s651665496/article/details/50773073
systemd
http://0pointer.de/blog/projects/systemd.html
http://www.wowotech.net/linux_application/why-systemd.html
https://www.ibm.com/developerworks/cn/linux/1407_liuming_init3/index.html
https://fedoraproject.org/wiki/Systemd/zh-cn
http://www.jinbuguo.com/systemd/systemd.service.html
https://unix.stackexchange.com/questions/233468/how-does-systemd-use-etc-init-d-scripts/233581#233581
https://www.cnblogs.com/sparkdev/p/8472711.html
http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html
https://www.linuxidc.com/Linux/2015-05/117640.htm