yocto中的init manager

发布时间 2023-04-23 17:54:47作者: 我家有只江小白

yocto中的init manager

 https://blog.csdn.net/qq_35018427/article/details/105487675

前言
根据前文Linux根文件系统挂载流程中的分析,内核通过调用根文件系统中的init程序跳转到用户空间,并对用户空间所需的基础框架进行初始化。类比于内核启动时,需要对各个功能模块进行初始化,当进入用户空间时,也需要启动各项服务来搭建基础的应用环境。对于不同的init系统管理器,服务启动的方式有所区别,下文主要对yocto中使用的sysVinit、busybox init和systemd进行介绍。

sysVinit
在sysvinit中,服务由shell脚本提供,并且不同的runlevel包含不同的shell脚本集合,可将系统启动到具有不同服务的用户模式。其中,所有能够提供服务的shell脚本都包含在/etc/init.d目录下,对目录下的shell脚本进行不同的组合便可以组成具有不同服务的用户模式,即runlevel,其结构映射如下所示:

如上图,可以将包含所有shell脚本的/etc/init.d看作一个服务库,通过提取库中的不同服务并组合便可以定制不同的系统模式,而系统启动到哪种模式,由配置文件/etc/inittab指定,/etc/inittab文件的内容由多个指定格式的项所组成,每一项的格式规定如下:

label:runlevel:action:process

label:标识符,与各项唯一对应,限于1-4个字节。其中,包括如下几个默认标识符的定义:
id:缺省的init运行级别。无实际执行内容,只是向init程序指示系统需要启动到哪个runlevel对应的模式。
si:系统初始化时启动的进程,在进入任何一个runlevel之前都需执行。
ln:指定进程在哪个/哪些runlevel下运行,n为runlevel的值。
ca:当检测到Ctrl+Alt+Del时启动的进程。
runlevel:运行级别,进入指定运行级别时启动对应的进程,各运行级别定义如下:
0:Halt,关闭系统
1:单用户模式,root权限,需输入root口令,进入该模式之前关闭所有应用和服务。
2:无网络多用户模式
3:有网络多用户模式(控制台命令行)
4:保留,可供用户自行定义
5:xwindow图形界面模式(图形GUI)
6:重启系统
S/s:单用户模式(初始化),当系统初次启动或者从多用户模式切换到单用户模式时,该进程首先被执行。
action:如何运行该进程,有效的运行方式定义如下:
boot:系统引导期间执行。
bootwait:系统引导期间执行,并且等待该进程运行结束,该参数用于多用户模式。
sysinit:初始化时执行(boot、bootwait之前),runlevel域被忽略。
initdefault:特定用于指定系统引导进入的runlevel级别,若该项不存在,将通过控制台向用户询问。
respawn:自启动进程,当进程被终止时自动重启。
once:指定进程只启动一次。
off:终止进程,当启动到指定运行级别中发现指定进程处于运行状态,则终止该进程。
wait:等待进程运行结束才启动下一进程。
ctrlaltdel:当检测到当检测到Ctrl+Alt+Del时启动的进程。
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系统启动流程:

init程序首先解析/etc/inittab文件,获取需要进入的runlevel模式
执行初始化进程(action=sysinit),进入单用户模式(s)?;
进入runlevel模式(执行shell脚本集合)。
根据以上示例可以看出:系统默认启动到图形化多用户模式(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 init
busybox是众多linux命令的一个工具集,根目录下的bin,sbin,usr和linuxrc通常就是Busybox。使用busybox时,kernel cmdline的init一般指向linuxrc,但是linuxrc最终还是会调用到/sbin/init这个真正的init进程。init进程的执行流如下:

为init进程设置信号处理进程
对控制台进行初始化
解析inittab文件,即etc/inittab,若无inittab文件,则采用默认的配置
执行/etc/init.d/rcS
busybox中/etc/inittab文件具有与sysvinit类似的格式,只是各字段定义有所区别:

id:runlevel_ignored:action:command

id:启动进程的控制终端,如shell终端
runlevel_ignored:该字段在busybox中被忽略,保持为空
action:进程启动方式,支持sysinit、wait、once、respawn、askfirst、ctratldel、shutdown和restart参数。系统启动期间,init进程首先启动sysinit、wait、once三类子进程,init进程会等待sysinit和wait进程结束,而不会等待once的进程结束,在系统正常运行期间,init程序启动respawn和askfirst两类子进程,并监视它们,在某个子进程退出时重新启动它。系统退出时,执行shutdown、restart和ctraltdel的三类子进程之一或全部。
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用于关闭所有的服务。

systemd
不同于sysvinit和busybox init的服务启动方式,systemd通过解析unit配置文件启动对应的服务,每一个unit中包含启动对应服务的方式、依赖项等信息。根据服务的类型,systemd将服务归为以下几类:

service:后台服务进程。
socker:封装文件系统或网络中的一个套接字,支持AF_INET,AF_INET6,AF_UNIX,还支持FIFIO。每一个socket unit需对应一个service unit。
device:封装linux设备树中的一个设备。如果该设备遵循udev rules,则该设备将在systemd中被抽象为一个device unit,并且在udev中设置的属性可以作为配置该unit依赖项的根据。
mount:挂载点(挂载目录)。systemd监控所有挂载点的状态,并且指示其挂载和卸载过程。此外,systemd兼容/etc/fstab对文件系统进行挂载。
automount:自动挂载点(挂载目录)。每一个automount unit对应一个mount unit,当挂载目录可用时,该目录即被按照automount unit中所设定的格式自动挂载。
target:unit集合,本身并不实现具体服务,只是封装对其他unit的索引,类似于sysV中runlevel对应的脚本集合,用于进入特定的系统模式。
snapshot:用于保存服务的状态或回滚,主要用于两种情形:暂停当前服务切换到emergency shell之后,恢复到service被暂停时的状态;服务被挂起之前关闭服务,之后重启到关闭前的状态。
systemd通过unit配置文件指明应调用哪个程序开启/关闭该服务,并指定该服务与其他服务之间的依赖关系。不像sysvinit中通过数字大小指定程序的运行顺序,systemd中通过before/after、require、wants、RequiredBy/WantedBy关键词来指定服务间的依赖关系。几个常用的描述依赖的名词解释如下:

before/after:表示在某个服务的之前/之后运行。
requires:表示在启动该项服务之前必须启动的服务列表,当需求的服务没有全部被成功启动时,该项服务便会启动失败。
wants:与requires功能一样,只是此时为弱依赖,即使其依赖的服务没有被成功启动,本服务也可以正常启动。
RequiredBy/WantedBy:与requires和wants对应,表示本服务被某项其他服务所依赖。
一个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支持模板机制,例如提供一个getty@.service的模板,根据该模板可以被实例化为getty@tty2.service等。这个@后面的参数可以被service中的内容所继承。

sysvinit和systemd的服务生成过程对应如下,其添加服务的过程类似:在服务库中添加文件;通过工具/配置文件指定依赖关系,并添加到指定用户模式的服务集合中。

 

yocto的init manager
根据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添加服务
对于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
对于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目录下了

sysVinit和systemd兼容
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