郝健:Linux下服务程序启动管理方式的分析与总结
作者:郝健
目前就职于瑞星咖啡,负责4层负载均衡的研究与开发。曾就职于天融信,赛尔网络,云杉网络几家公司。主要感兴趣的方向:linux内核网络子系统,dpdk。
目前,Linux平台下主流的服务程序启动管理方式有以下几种:
daemon
sysvinit
systemd
nohup
1. daemon
守护进程是在后台运行不受控端控制的进程,通常情况下守护进程在系统启动时自动运行。
1.1 概念说明
进程组
每个进程除了有一进程ID( PID )之外,还属于一个进程组。进程组是一个或多个进程的集合。每个进程组有一个唯一的进程组ID( PGID )。进程组ID类似于进程ID, 它是一个正整数,并可存放在pid_t数据类型中。函数getpgrp返回调用进程的进程组ID。
会话期(session)
会话期是一个或多个进程组的集合。通常是由shell的管道将几个进程编成一组的。比如,下图中的安排 可能是由下列形式的shell命令形成的:
procl | proc2 &
proc3 | proc4 | proc5
一般,登陆一个shell时就会创建一个会话期。进程调用setsid函数就可建立一个新会话期。
如果调用此函数的进程不是一个进程组的组长,则此函数创建一个新会话期,结果为:
此进程变成该新会话期的会话期首进程(session leader,会话期首进程是创建该会话期的进 程)。此进程是该新会话期中的唯一进程。
此进程成为一个新进程组的组长进程。新进程组ID是此调用进程的进程ID。
此进程没有控制终端。如果在调用setsid之前此进程有一个控制终端,那么这种联系也被解除。如果此调用进程已经是一个进程组的组长,则此函数返回出错。为了保证不处于这种情况,通常先 调用fork,然后使其父进程终止,而子进程则继续。因为子进程继承了父进程的进程组ID,而其进程ID则是新分配的,两者不可能相等,所以这就保证了子进程不是一个进程组的组长。
控制终端(terminal)
会话期和进程组有一些其他特性:
一个会话期可以有一个单独的控制终端(controlling terminal)。这通常是我们在其上登录的终端设备(终端登录情况)或伪终端设备(网络登录情况)。
建立与控制终端连接的会话期首进程,被称为控制进程(controlling process)。
一个会话期中的几个进程组可被分成一个前台进程组(foreground process group)以及一个或 几个后台进程组(background process group)。
如果一个会话期有一个控制终端,则它有一个前台进程组,其他进程组则为后台进程组。
无论何时键入中断键(常常是DELETE或Ctrl -C)或退出键(常常是Ctrl -\),就会造成将中断信号或退出信号送至前台进程组的所有进程。
如果终端界面检测到网络已经断开连接,则将挂断信号送至控制进程(会话期首进程)。通常,我们不必关心控制终端,登录时,将自动建立控制终端。
1.2 创建方法
创建守护进程,应该创建一个进程然后将其放到一个新的会话期中。
首先要做的是调用umask将文件模式创建屏蔽字设置为0。
调用fork(子进程会是将来的守护进程),然后使父进程退出(exit),保证子进程不是进程组组长(这 是setsid调用的必要前提条件)。
调用setsid创建新的会话期。
将当前目录改为根目录(如果不改为根目录可能导致不能umount某个目录)。
关闭不再需要的文件描述符。
某些守护进程打开/dev/null使其具有描述符0、1和2(将标准输入、标准输出、标准错误重定向 到/dev/null)。
或者直接调用daemon函数
2. sysvinit
sysvinit主要依赖于Shell脚本,在/etc/rc.d/rc*.d建立软连接。在RedHat系列发行版中,还提供了 service,chkconfig 等命令行工具,来方便来管理 init 系统。sysvinit的主要问题是启动慢,已经逐渐 被淘汰。
目前Ubuntu和Centos等主流发行版都以移步到systemd,但仍然兼容老的写法,这里简单举个的例 子,编写一个simple脚本,添加执行权限后,保存到/etc/init.d/目录下。
测试:
3. systemd
systemd是目前主流Linux发型版采用的init项目,systemd起初饱受争议,可以搜索“the software that currently breaks your audio”查看。但随着越来越稳定和使用c开发,高效启动,已经逐渐成为主角。Linus也表示:Torvalds says he has no strong opinions on systemd
https://www.itwire.com/business-it-news/open-source/65402-torvalds-says-he-has-no-strong-opinions-on-systemd
我们简单实现一个simple-server来感受一下systemd,主要步骤如下:
1. 编写一个需要作为守护的程序,如:simple-server.c
2. 编写service文件
具体的说明详见;
https://www.freedesktop.org/software/systemd/man/systemd.service.html
其中Restart=always代表在任何情况下service被杀死后,都需要自动重启,其他说明详见如上链接中的 Table 2。
3. sudo cp simple-server.service /lib/systemd/system
4. sudo systemctl enable simple-server 设置为开机启动,会自动创建软连接 /etc/systemd/system/multi-user.target.wants/simpleserver.service -> /lib/systemd/system/simple-server.service
5. sudo systemctl start simple-server 启动
6. sudo systemctl status simple-server 查看状态
7. cat /var/log/syslog 查看日志输出
8. pstree | grep simple-server
9. sudo killall simple-server 杀死服务,验证是否自动重启。
几点说明:
修改simple-server.service后,需要执行systemctl daemon-reload重新加载。
在自daemon的方式中,是无法决定服务程序信息输出的位置的,只能重定向到/dev/null,而 systemd的方式可以灵活配置重定向的位置,/var/log/syslog为默认日志路径。
service文件中Restart选项体现了"子死父知因"的重要性。
4. nohup
一个terminal一旦关闭,其绑定的shell会收到SIGHUP信号,且shell在退出之前会将SIGHUP信号广播 给其上的进程组,即其上所有进程都会退出。这也是自daemon需要调用setsid脱离terminal的原因。nohup命令并不是真正脱离terminal,而是忽略SIGHUP。将标准输入重定向到/dev/null,标准输出和标准出错重定向到nohup.out,且不会随着terminal的关闭而退出。验证:
1)正常启动一个死循环程序
2)nohup启动同样的死循环程序
对比总结
daemon 通过自实现daemon,可以清楚的看出一个守护进程实现的原理,方便理清概念。但缺点是在实际工程中默认将输出重定向/dev/null,无法保存服务日志,排查服务问题,即使在程序中重定向到 某个日志目录,也只能写死,修改位置需要重新编译,不够灵活。
sysvinit sysvinit的优点是简单,只需要编写脚本。将服务添加到某个runlevel时,只需要创建软链接文件 即可,DevOPs兴起之前,传统运维人员可以很快上手。顺序执行,方便排查问题。但顺序执行既 是优点也是缺点,带来的弊端是运行效率慢,这也是逐渐被淘汰的主要原因。毕竟现在Linux不只 用于服务器系统,终端用户更追求fast boot的用户体验。
systemd systemd是目前主流Linux发行版中最新的初始化系统,主要的设计目标就是提高系统的启动速 度。.service文件中包含很多选项,配置灵活,比如日志位置和重启方式等。当然,最主要的卖点 还是并行启动速度快。
nohup 在实际工程中,nohup也挺常用。比如一个类服务程序的调试,简单执行即可在一段时间通过 nohup.out文件观察问题,但其并不能算正真的守护程序。
综上,systemd看起来可以满足一个产品级别服务程序的两个最重要的需求:根据不同情况重启;记录 日志灵活可查,而这是daemon和nohup方式无法快速方便满足的。sysvinit简单清晰,但启动慢,就作为传统运维人员的美好记忆保留吧。
点一点右下角”在看”,为阅码场打Call~