By: fu linux
E-mail: fulinux@sina.com
Blog: https://blog.csdn.net/fulinus
喜欢的盆友欢迎订阅!
你的喜欢就是我写作的动力!
程序开机自启动
返回总目录 : Yocto开发讲解系列 - 总目录
今天讲下如何实现某个程序开机自启动, 这对于某些盆友来说很有必要.
1号进程有哪些
首先我们要弄清楚一点就是自己的目标机器中运行的首进程是什么?所谓的首进程是指进程号为1的进程,是内核启动后运行的第一个用户进程,即下图中的
1号用户进程
通常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号进程是哪个呢?
-
方法一
在运行的机器中通过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
-
方法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
-
方法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。
-
方法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 3月 9 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 6月 8 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如何实现自启动功能哈
谢谢阅读!希望帮我点个赞加关注,你的喜欢就是我持续更新的动力!