yocto系列讲解 (实战篇) 58 - 程序开机自启动(sysvinit/init)

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

程序开机自启动

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

今天讲下如何实现某个程序开机自启动, 这对于某些盆友来说很有必要.

1号进程有哪些

首先我们要弄清楚一点就是自己的目标机器中运行的首进程是什么?所谓的首进程是指进程号为1的进程,是内核启动后运行的第一个用户进程,即下图中的 1号用户进程

0号进程
1号内核进程
1号用户进程
getty进程
shell进程

通常1号用户进程有两种:

  • 一种是比较传统的init(或者sysvinit)程序
  • 另一种是现在比较流行的systemd程序

他们有着相互独立的配置,二者选其一。

init和Systemd的区别

下面是比较init和systemd进程的区别(内容摘自: Linux启动流程和服务管理(init和systemd)
init:

  • 一是启动时间长,init是串行启动,只有前一个进程启动完,才会启动下一个进程
  • 二是启动脚本复杂,Init进程只是执行启动脚本,不管其他事情,脚本需要自己处理各种情况,这往往使得脚本变得很长
  • 由Linux内核加载运行,位于 /sbin/init ,是系统中第一个进程,PID永远为1

对于支持 service 的程序,安装的时候,会自动的在 /etc/init.d 目录添加一个配置文件。当我们使用 service 控制程序时,比如执行开启httpd的服务:service httpd start 。那么我们的 service 就会开启 /etc/init.d/httpd 配置文件里面指向的 /usr/sbin/httpd 可执行文件

systemd:

  • 按需启动服务,减少系统资源消耗。
  • 尽可能并行启动进程,减少系统启动等待时间
  • 由Linx内核加载运行,位于 /usr/lib/systemd/systemd ,是系统中第一个进程,PID永远为1

对于支持 systemd 的程序,安装的时候,会自动的在 /usr/lib/systemd/system 目录添加一个配置文件。当我们使用 systemctl 控制该程序时,比如执行开启httpd服务:systemctl start httpd.service 。那么我们的 systemctl 就会开启 httpd.service 配置里面指向的 /usr/sbin/httpd 可执行文件

下面我会逐一讲解如何在yocto中实现不同启动~

搞清楚自己的是哪个

这里我们继续以qemux86-64虚拟机为例,如何查询当前的1号进程是哪个呢?

  1. 方法一
    在运行的机器中通过ps命令查看:
root@qemux86-64:~# ps -axjf
PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
0     1     1     1 ?           -1 Ss       0   0:03 init [5] 
  • 1
  • 2
  • 3

找到对于的PID进程号为1的进程,上面显示是init进程。
再看下systemd的进程情况:

# ps -a
PID   USER     TIME   COMMAND
    1 root       0:08 {systemd} /sbin/init
  • 1
  • 2
  • 3

有些时候在不同的系统中ps命令带的参数不同,我列举几个以供尝试,那个能用就用:

# ps
# ps -a
# ps -ax
# ps -axjf
  • 1
  • 2
  • 3
  • 4
  1. 方法2
    在运行的机器中通过ls命令查看:
# ls -l /sbin/init
lrwxrwxrwx 1 root root 19 Mar  9  2018 /sbin/init -> /sbin/init.sysvinit
  • 1
  • 2

可见,上面是init(或者sysvinit)程序

再看一个systemd的程序:

# ls -l /sbin/init
lrwxrwxrwx. 1 root root 20 Mar  2 14:18 /sbin/init -> /lib/systemd/systemd
  • 1
  • 2
  1. 方法3
    在poky目录中查看:
poky]$ source oe-init-build-env
build]$ bitbake -e <your image target> | grep ^DISTRO_FEATURES=
  • 1
  • 2

比如我们的core-image-sato:

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 pulseaudio sysvinit gobject-introspection-data ldconfig"
  • 1
  • 2
  • 3

在上面找有没有sysvinit或者systemd,上面有sysvinit。

  1. 方法4:
    在poky中创建的文件系统中查看:
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

首先准备一个service程序

看过 yocto系列讲解 (理论篇) 56 - poky下目录结构 文章的盆友应该记得我提到在下面的目录中存在一个service服务的实例程序:

poky]$ tree meta-skeleton/recipes-skeleton/service/
meta-skeleton/recipes-skeleton/service/
├── service
│   ├── COPYRIGHT
│   ├── skeleton
│   └── skeleton_test.c
└── service_0.1.bb

1 directory, 4 files
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

这里我们将其作为我们本篇文章用来实现开机自启动的示例。
首先我们将其复制到自己的 meta-mylayer 目录中:

poky]$ cp -rf meta-skeleton/recipes-skeleton/service/ meta-mylayer/recipes-myapps/
  • 1

关于这个service的内容很简单,自己看下~ (我在想要不要给它改个名字比如myservice)
这里要提一点,默认情况下 meta-skeleton 是没有加入 bblayers.conf 文件中的,所以不参与项目构建,但是我们meta-mylayer是的,所以将其拷贝到 meta-mylayer 目录中。然后就可以用bitbake编译了:

poky]$ source oe-init-build-env 
build]$ bitbake service
  • 1
  • 2

一次成功没有多余的动作,so easy~
然后,我们看下编译的结果:

build]$ tree tmp-qemux86-64/work/core2-64-poky-linux/service/0.1-r0/image/
tmp-qemux86-64/work/core2-64-poky-linux/service/0.1-r0/image/
├── etc
│   └── init.d
│       └── skeleton
└── usr
    └── sbin
        └── skeleton-test
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

如上所示,生成了两个文件,一个是启动脚本位于 /etc/init.d/skeleton ,另一个是启动脚本要启动的服务程序位于 /usr/sbin/skeleton-test

紧接着将其添加到目标镜像文件系统中,有些步骤中出现的目录和文件是前面某些章节中出现的内容,我就不再复述了哈。参考如下:

meta-mylayer]$ vim recipes-sato/images/core-image-sato.bbappend
...
IMAGE_INSTALL += "learnyocto"
IMAGE_INSTALL += "service" #添加这个
  • 1
  • 2
  • 3
  • 4

开始构建我们的目标镜像:

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

构建完成后,运行我们的qemux86-64虚拟机,我们确认下成果:

root@qemux86-64:~# ls -l /usr/sbin/skeleton-test 
-rwxr-xr-x 1 root root 14296 Mar  9  2018 /usr/sbin/skeleton-test
root@qemux86-64:~# ls -l /etc/init.d/skeleton 
-rwxr-xr-x 1 root root 4940 Mar  9  2018 /etc/init.d/skeleton
root@qemux86-64:~# ps -ax | grep skeleton | grep -v grep
  • 1
  • 2
  • 3
  • 4
  • 5

可见,我们的服务和脚本都有安装到文件系统中,但是,通过 ps 命令也可以看到我们的 skeleton-test 服务并没有启动。

在讲述如何实现开机自启动任务前,先我们手动验证下启动脚本是否OK:

root@qemux86-64:~# skeleton-test  
root@qemux86-64:~# ps -ax | grep skeleton | grep -v grep
  830 ?        Ss     0:00 skeleton-test
root@qemux86-64:~# killall skeleton-test 
root@qemux86-64:~# ps -ax | grep skeleton | grep -v grep
root@qemux86-64:~# /etc/init.d/skeleton start
Starting skeleton ...
root@qemux86-64:~# ps -ax | grep skeleton | grep -v grep
  849 ?        Ss     0:00 /usr/sbin/skeleton-test
root@qemux86-64:~# /etc/init.d/skeleton stop            
Stopped skeleton (849).
root@qemux86-64:~# ps -ax | grep skeleton | grep -v grep
root@qemux86-64:~# 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

上面的过程我验证了服务程序能够正常启动,然后验证了启动脚本能够正常启动和停止服务程序。
而且这个过程通常在开发阶段在目标机器中编写启动脚本进行验证,而不要等生成文件系统再运行机器慢慢的验证。

在init进程环境下的方法

话说回来,我们前面的服务程序有了,启动脚本有了,为什么没有实现开机自启动呢?
这是因为,实际需要自启动的程序会在一个对应系统运行级别的 /etc/rc*.d 目录中创建一个符号连接文件指向启动脚本,而且符号连接文件名开头通常带有 S## ,再加上原始文件名,其中 # 是数字,数字表示启动顺序,一般取99以内的数字,数字可以重复,数字越小启动越早,反之靠后启动。
我们如何看系统的运行级别呢?又如何知道是对应那个目录呢?
可以通过 runlevel 命令查看:

root@qemux86-64:~# runlevel 
N 5
  • 1
  • 2

当前 系统运行级别 是5,对应的符号连接文件应该放到 /etc/rc5.d 目录中。

手动快速验证和演示

为了验证这个过程,下面我先手动教大家如何实现:

root@qemux86-64:/etc/rc5.d# ln -sf ../init.d/skeleton S99skeleton   
root@qemux86-64:/etc/rc5.d# ls -l                                   
total 0
lrwxrwxrwx 1 root root 20 Mar  9  2018 S01networking -> ../init.d/networking
lrwxrwxrwx 1 root root 16 Mar  9  2018 S02dbus-1 -> ../init.d/dbus-1
lrwxrwxrwx 1 root root 17 Mar  9  2018 S05connman -> ../init.d/connman
lrwxrwxrwx 1 root root 22 Mar  9  2018 S09xserver-nodm -> ../init.d/xserver-nodm
lrwxrwxrwx 1 root root 18 Mar  9  2018 S10dropbear -> ../init.d/dropbear
lrwxrwxrwx 1 root root 17 Mar  9  2018 S12rpcbind -> ../init.d/rpcbind
lrwxrwxrwx 1 root root 21 Mar  9  2018 S15mountnfs.sh -> ../init.d/mountnfs.sh
lrwxrwxrwx 1 root root 19 Mar  9  2018 S20bluetooth -> ../init.d/bluetooth
lrwxrwxrwx 1 root root 20 Mar  9  2018 S20hwclock.sh -> ../init.d/hwclock.sh
lrwxrwxrwx 1 root root 16 Mar  9  2018 S20syslog -> ../init.d/syslog
lrwxrwxrwx 1 root root 22 Mar  9  2018 S21avahi-daemon -> ../init.d/avahi-daemon
lrwxrwxrwx 1 root root 15 Mar  9  2018 S22ofono -> ../init.d/ofono
lrwxrwxrwx 1 root root 22 Mar  9  2018 S99rmnologin.sh -> ../init.d/rmnologin.sh
lrwxrwxrwx 1 root root 18 Jun  8 11:34 S99skeleton -> ../init.d/skeleton #看这里
lrwxrwxrwx 1 root root 23 Mar  9  2018 S99stop-bootlogd -> ../init.d/stop-bootlogd
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

OK,我们重启系统试试看吧:

root@qemux86-64:/etc/rc5.d# reboot
  • 1

再次启动后,我们看下:

[peeta@peeta-OptiPlex-7050 ~]$ ssh root@192.168.7.2
root@qemux86-64:~# ps -ax | grep skeleton | grep -v grep   
  524 ?        Ss     0:00 /usr/sbin/skeleton-test
  • 1
  • 2
  • 3

喏,看上面,我们的服务是不是真正实现了自启动!

INITSCRIPT_*变量知识点

好了,到这里有盆友就说了, 我们该如何在yocto构建过程中实现创建这个符号连接文件呢?

莫非是在bb文件中的 do_install() 函数中创建吗?

NO, 不是的,它有一套自己的标准做法。使用INITSCRIPT相关的变量,下面我就来讲~

我们在对应的bb文件中添加如下内容:

meta-mylayer]$ vim recipes-myapps/service/service_0.1.bb
... #省略部分内容
inherit update-rc.d #首先让他继承这个update-rc.d,rc.d就是我们上面提及的rc5.d目录的意思

INITSCRIPT_NAME = "skeleton" #对应/etc/init.d/skeleton启动脚本名
INITSCRIPT_PARAMS = "defaults 99" #默认的会在rc2.d、rc3.d、rc4.d和rc5.d中创建启动脚本,在rc0.d、rc1.d和rc6.d中创建停止脚本,启动编号是99
#或者是这样:
#INITSCRIPT_PARAMS = "start 99 2 3 4 5 . stop 09 0 5 6 1 ."
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

最后一项内容有些复杂,我简单讲解下:
start和stop关键词表示启动和停止服务对应的脚本,99是启动顺序,而09是数值小说明停止的顺序很早(原则就是后启动的早关闭),
然后就是99和09后面的 2 3 4 5 0 6 1 ,意思是启动脚本在 rc2.d rc3.d rc4.d rc5.d 目录中都创建 S99skeleton 启动脚本符号连接文件,而停止脚本在 rc0.d rc6.d rc1.d 目录中会创建 K09skeleton 脚本符号连接文件。至于你需要在那些rc#.d目录中创建符号连接文件,完全是根据程序的特性或者说需求来定。比如有些机器还带有 recovery 模式,这个时候系统就运行在不同的 运行级别 上面,启动的应用也不同。我这里面的 2 3 4 5 0 6 1 完全是为了举例子,与上面的 defaults 关键词功能相同。
另外讲下就是你再下面的目录中是看不到 rc*.d 目录的:

build]$ ls -l tmp-qemux86-64/work/core2-64-poky-linux/service/0.1-r0/image/etc/
total 4
drwxr-xr-x 2 peeta peeta 4096 68 20:04 init.d
  • 1
  • 2
  • 3

最终只会在制作文件系统的时候生成。

这里我们采用第一行的做法,后面一行大家可以自行验证~
然后就是开始编译我们的目标镜像,然后重新运行我们的系统看下结果:

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

运行结果看下:

root@qemux86-64:~# ps -ax | grep skeleton-test | grep -v grep
  629 ?        Ss     0:00 /usr/sbin/skeleton-test
root@qemux86-64:~# for c in /etc/rc*.d;do echo $c/;echo `ls -l $c/ | grep skeleton`;done     
/etc/rc0.d/
lrwxrwxrwx 1 root root 18 Mar 9 2018 K99skeleton -> ../init.d/skeleton
/etc/rc1.d/
lrwxrwxrwx 1 root root 18 Mar 9 2018 K99skeleton -> ../init.d/skeleton
/etc/rc2.d/
lrwxrwxrwx 1 root root 18 Mar 9 2018 S99skeleton -> ../init.d/skeleton
/etc/rc3.d/
lrwxrwxrwx 1 root root 18 Mar 9 2018 S99skeleton -> ../init.d/skeleton
/etc/rc4.d/
lrwxrwxrwx 1 root root 18 Mar 9 2018 S99skeleton -> ../init.d/skeleton
/etc/rc5.d/
lrwxrwxrwx 1 root root 18 Mar 9 2018 S99skeleton -> ../init.d/skeleton
/etc/rc6.d/
lrwxrwxrwx 1 root root 18 Mar 9 2018 K99skeleton -> ../init.d/skeleton
/etc/rcS.d/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

验证的结果达到了我们预期目标~

后面再讲systemd如何实现自启动功能哈

谢谢阅读!希望帮我点个赞加关注,你的喜欢就是我持续更新的动力!