yocto系列讲解 (实战篇) 59 - 程序开机自启动(systemd)

By: fu linux
E-mail: fulinux@sina.com
Blog: https://blog.csdn.net/fulinus
喜欢的盆友欢迎订阅!
你的喜欢就是我写作的动力!


在这里插入图片描述

返回总目录 Yocto开发讲解系列 - 总目录

概述

默认情况下, yocto 项目(例如我们的 qemux86-64 机器)使用 sysvinit 作为系统 初始化管理器 ,即系统首进程init。可是我们的 yocto 也可以支持你换成 systemd systemd 可以完全替代 init ,它具有并行启动服务、减少 shell 开销等特性,而且也可以想sysvinit那样启动一个脚本,

相比之下,systemd 将组件视为 units 单元。与sysvinit管理和启动程序相比,使用 units 单元是一个更广泛的概念。一个单元包括几种不同类型的实体(比如服务或者程序),服务或程序是实体类型之一。 sysvinit 中的 运行级别 概念对应于 systemd target目标 的概念,其中 target目标 也是一种支持的单元,即你启动一个目标可能就启动了很多的组件(程序或者服务)。

而且吧,这个 sysvinit 管理器还有很多的缺陷,比如不能实现程序并行启动,只能一个个顺序启动,不支持并行启动,还要写脚本,而且有的时候程序死掉了都不能让他重启。

我这两个系统初始化管理程序都用过,我推荐你使用 systemd 哈,现在 systemd 也流行起来了,现在 Ubuntu 系统都是 systemd ,而且它调试启动问题的工具也丰富,今后可能都是systemd的环境了,~

小技巧分享

本专栏开篇的时候,我就讲到qemux86-64虚拟机对于没有Ubuntu桌面环境的玩家而言也是可以运行的,为啥没有桌面环境? 因为我们某些小伙伴把Ubuntu桌面程序给停掉了,因为我们的电脑性能不足编译慢、或者是一台公司的服务器,又或者单纯的最求最强性能,不喜欢多余的开销0.0 怎么做呢?分享一下:
在这里插入图片描述

如何替换成systemd呢

前面我们说了默认都是使用sysvinit管理器,如果你是想继续使用sysvinit,就不用看本章了,如果想更换成systemd,如何替换呢?

meta-mylayer]$ vim recipes-sato/images/core-image-sato.bbappend
...
DISTRO_FEATURES_append = " systemd"
VIRTUAL-RUNTIME_init_manager = "systemd"
  • 1
  • 2
  • 3
  • 4

还有,我们需要停了 sysvinit ,可以这样避免启用了 sysvinit 的部分功能,停止启用的方法:

#在上面的文件中追加这个
DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"
  • 1
  • 2

可以删除任意冗余的 sysvinit 脚本。

如果你想要从目标镜像文件系统中完全删除 initscripts 脚本文件,同时设置下面的语句:

VIRTUAL-RUNTIME_initscripts = ""
  • 1

但是对于某些迷你型的(或者某些救急或者恢复模式的)镜像,我们可能还需要用到sysvinit的内容,因此,根据自己的需求,上面添加的最后两项也可以不使用,可以屏蔽,就只保留开始的两项语句。

编译验证systemd

编译前我们看下效果:

poky]$ source oe-init-build-env 
build]$ bitbake -e core-image-sato | grep ^DISTRO_FEATURES=
DISTRO_FEATURES="acl alsa argp bluetooth ext2 ipv4 ipv6 largefile pcmcia usbgadget usbhost wifi xattr nfs zeroconf pci 3g nfc x11 vfat largefile opengl ptest multiarch wayland vulkan systemd pulseaudio gobject-introspection-data ldconfig"
  • 1
  • 2
  • 3

是不是就少了 sysvinit , 而多了 systemd 了,然后我们开始构建我们的目标镜像吧!

build]$ bitbake core-image-sato
  • 1

但是很不好意思,并没有达到预期的目标。 init 进程依旧是 sysvinit 实例程序。

build]$ ls -l tmp/work/qemux86_64-poky-linux/core-image-sato/1.0-r0/rootfs/sbin/init
lrwxrwxrwx 1 peeta peeta 19 39  2018 tmp/work/qemux86_64-poky-linux/core-image-sato/1.0-r0/rootfs/sbin/init -> /sbin/init.sysvinit
  • 1
  • 2

问题在哪里呢?通过单独编译 systemd 发现如下错误:

build]$ bitbake systemd
...
ERROR: Nothing PROVIDES 'systemd'
systemd was skipped: missing required distro feature 'systemd' (not in DISTRO_FEATURES)
  • 1
  • 2
  • 3
  • 4

但是我前面确实是在 DISTRO_FEATURES 中有添加了systemd的feature,但是先让没有生效,莫非要在conf等的配置文件中添加吗?
我们先尝试在local.conf中进行添加:

poky]$ vim build/conf/local.conf
...
# Use systemd init manager.
DISTRO_FEATURES_append = " systemd"
VIRTUAL-RUNTIME_init_manager = "systemd"
DISTRO_FEATURES_BACKFILL_CONSIDERED += "sysvinit"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

然后重新编译:

poky]$ source oe-init-build-env 
build]$ bitbake core-image-sato -c cleanall
build]$ bitbake core-image-sato
  • 1
  • 2
  • 3

编译完成后可以看到:

build]$ ls -l tmp-qemux86-64/work/qemux86_64-poky-linux/core-image-sato/1.0-r0/rootfs/sbin/init
lrwxrwxrwx 1 peeta peeta 22 39  2018 tmp-qemux86-64/work/qemux86_64-poky-linux/core-image-sato/1.0-r0/rootfs/sbin/init -> ../lib/systemd/systemd #变成了systemd了
build]$ ls -l tmp-qemux86-64/work/qemux86_64-poky-linux/core-image-sato/1.0-r0/rootfs/etc/systemd/
total 48
-rw-r--r--  1 peeta peeta 1052 39  2018 journald.conf
-rw-r--r--  1 peeta peeta 1072 39  2018 logind.conf
drwxr-xr-x  2 peeta peeta 4096 39  2018 network
-rw-r--r--  1 peeta peeta  609 39  2018 networkd.conf
-rw-r--r--  1 peeta peeta  529 39  2018 pstore.conf
-rw-r--r--  1 peeta peeta 1035 39  2018 resolved.conf
-rw-r--r--  1 peeta peeta  790 39  2018 sleep.conf
drwxr-xr-x 11 peeta peeta 4096 39  2018 system
-rw-r--r--  1 peeta peeta 1774 39  2018 system.conf
-rw-r--r--  1 peeta peeta  657 39  2018 timesyncd.conf
drwxr-xr-x  2 peeta peeta 4096 39  2018 user
-rw-r--r--  1 peeta peeta 1197 39  2018 user.conf
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

此时,可以将qemux86-64虚拟机运行起来看看哈。

手动添加systemd的启动脚本

我们以上一篇skeleton服务为例,来讲解如何在线手动创建systemd的启动脚本,以及如何实现开机自启动的。
首先我们登录qemux86-64虚拟机,继续以之前的然后通过vi命令创建一个systemd的service文件,参考如下:

root@qemux86-64:~# cd /lib/systemd/system
root@qemux86-64:/lib/systemd/system# vi skeleton.service
[Unit]
Description=Skeleton service
DefaultDependencies=no

[Service]
Type=oneshot
ExecStart=/usr/bin/skeleton-test
RemainAfterExit=true

[Install]
WantedBy=multi-user.target
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

这里说明下 skeleton-test 程序很特别,他调用了 daemon() 函数,运行后脱离控制台,以守护进程形式在后台运行的程序,不像其他没有加’&'符号的进程那样,会一直占用控制台。因此,这里就需要告知 systemd :它的类型是 oneshot 执行一次,而且程序退出了依旧认为程序在运行 RemainAfterExit = true 。当然读者盆友也可以尝试修改 service 文件,熟悉熟悉不同类型的启动方式,比如怎么通过它其他一个由脚本启动的程序,这里我就不过多的讲解systemd的相关知识了,感兴趣的盆友可以自行研究。编辑完成 service 后,我们尝试启动这个服务看看:

root@qemux86-64:~# systemctl start skeleton.service
root@qemux86-64:~# ps -ax | grep skeleton
    271 ?        Ss     0:00 /usr/sbin/skeleton-test
    273 pts/0    S+     0:00 grep skeleton
  • 1
  • 2
  • 3
  • 4

如上所示,已经成功了。

手动创建开机自启动

上面我们在 /lib/systemd/system 目录中创建了 skeleton.service 文件,只是添加了启动 skeleton-test 程序的启动规则,并没有实现是否需要开机自启动,如何实现呢?其实很简单,我们只需要在一个特定的目录中创建一个符号连接文件指向 skeleton.service 文件即可,参考如下:

root@qemux86-64:/lib/systemd/system# cd multi-user.target.wants/
root@qemux86-64:/lib/systemd/system/multi-user.target.wants# ln -sf ../skeleton.service  
  • 1
  • 2

即在 multi-user.target.wants 目录中创建了一个指向它的符号连接文件即可实现开机自启动,大家可以reboot试试看,另外如果不想让他开机自启动了可以将该符号连接文件删除。

项目集成和验证

有了上面的操作经验之后我们就有了 systemd 实现自启动的理论基础了,说白了就是在特定目录中创建一个 service 文件和符号连接文件。
因此,将上面的工作整合到 yocto 的方法就很简单了,在bb文件中复制和连接即可。

  1. 首先创建service文件:
meta-mylayer]$ cd recipes-myapps/service/
service]$ vim service/skeleton.service.in #带个in表示模板文件的意思,其实也可以没有
[Unit]
Description=Skeleton service
DefaultDependencies=no

[Service]
Type=oneshot
ExecStart=/usr/sbin/skeleton-test
RemainAfterExit=true

[Install]
WantedBy=multi-user.target
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  1. 修改bb文件:
    这里我想讲下,在 yocto 中我们知道常见的两种启动方式就是 sysvinit systemd ,两者启动文件等方式都不同,在 yocto 中广泛的做法就是判断是否包含 systemd feature ,并据此来安装 sysvinit 或者 systemd 的启动脚本,见下 bb 文件:
meta-mylayer]$ cat recipes-myapps/service/service_0.1.bb    
SUMMARY = "The canonical example of init scripts"
SECTION = "base"
LICENSE = "GPLv2"
LIC_FILES_CHKSUM = "file://${WORKDIR}/COPYRIGHT;md5=349c872e0066155e1818b786938876a4"

SRC_URI = "file://skeleton \
           file://skeleton.service.in \ #看这里
           file://skeleton_test.c \
           file://COPYRIGHT \
           "

inherit update-rc.d systemd
INITSCRIPT_NAME = "skeleton"
INITSCRIPT_PARAMS = "defaults 99"
#INITSCRIPT_PARAMS = "start 99 2 3 4 5 . stop 09 0 5 6 1 ."

SYSTEMD_PACKAGES = "${PN}" #有了这三条语句,就不需要手动去创建符号连接文件了
SYSTEMD_SERVICE_${PN} = "skeleton.service"
SYSTEMD_AUTO_ENABLE = "enable"

do_compile () {
        ${CC} ${CFLAGS} ${LDFLAGS} ${WORKDIR}/skeleton_test.c -o ${WORKDIR}/skeleton-test
}

do_install () {
        if ${@bb.utils.contains('DISTRO_FEATURES', 'sysvinit', 'true', 'false', d)}; then #如果有sysvinit,进入这个逻辑
                install -d ${D}${sysconfdir}/init.d
                cat ${WORKDIR}/skeleton | \
                  sed -e 's,/etc,${sysconfdir},g' \
                          -e 's,/usr/sbin,${sbindir},g' \
                          -e 's,/var,${localstatedir},g' \
                          -e 's,/usr/bin,${bindir},g' \
                          -e 's,/usr,${prefix},g' > ${D}${sysconfdir}/init.d/skeleton
                chmod a+x ${D}${sysconfdir}/init.d/skeleton
        fi

        if ${@bb.utils.contains('DISTRO_FEATURES', 'systemd', 'true', 'false', d)}; then #如果有systemd,进入这个逻辑
                install -D -m 0644 ${WORKDIR}/skeleton.service.in ${D}${systemd_unitdir}/system/skeleton.service
                #以前这里可能会有ln -sf xxx命令哈
        fi

        install -d ${D}${sbindir}
        install -m 0755 ${WORKDIR}/skeleton-test ${D}${sbindir}/
}

RDEPENDS_${PN} = "initscripts"

CONFFILES_${PN} += "${sysconfdir}/init.d/skeleton"
FILES_${PN} += "${systemd_unitdir}" #看这里
FILES_${PN} += "${sbindir}" #看这里

PACKAGE_STRIP = "no"
INSANE_SKIP_${PN} := "installed-vs-shipped"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  1. 单独编译验证:
build]$ bitbake service
build]$ ls tmp/work/core2-64-poky-linux/service/0.1-r0/image/lib/systemd/system/skeleton.service 
tmp/work/core2-64-poky-linux/service/0.1-r0/image/lib/systemd/system/skeleton.service
  • 1
  • 2
  • 3
  1. 全编译验证:
    确定在 multi-user.target.wants 目录中释放存在 skeleton.service 的符号连接文件。
build]$ ls -l tmp/work/qemux86_64-poky-linux/core-image-sato/1.0-r0/rootfs/lib/systemd/system/multi-user.target.wants/
#不存在skeleton.service符号连接文件,还有一个地方,看下面
build]$ ls -l tmp/work/qemux86_64-poky-linux/core-image-sato/1.0-r0/rootfs/etc/systemd/system/multi-user.target.wants/
skeleton.service -> /lib/systemd/system/skeleton.service #存在
  • 1
  • 2
  • 3
  • 4
  1. 运行 qemux86-64 验证:
root@qemux86-64:~# ps -ax | grep skeleton
    115 ?        Ss     0:00 /usr/sbin/skeleton-test
  • 1
  • 2

运行成功!

至此我们的 systemd 的开机自启动完成!

谢谢阅读!您的点赞加收藏就是我持续更新的动力!