Linux守护进程、sighup与nohup详解

strace 查看系统调用之后,发现了原因所在

socket(PF_LOCAL, SOCK_DGRAM|SOCK_CLOEXEC, 0) = 3
connect(3, {sa_family=AF_LOCAL, sun_path="/dev/log"}, 110) = -1 ENOENT (No such file or directory)
close(3)                                = 0
open("/var/run/keepalived.pid", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=1, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe85ab1b000
read(3, "\n", 4096)                    = 1
read(3, "", 4096)                      = 0
close(3)                                = 0
munmap(0x7fe85ab1b000, 4096)            = 0
kill(0, SIG_0)                          = 0
socket(PF_LOCAL, SOCK_DGRAM|SOCK_CLOEXEC, 0) = 3
connect(3, {sa_family=AF_LOCAL, sun_path="/dev/log"}, 110) = -1 ENOENT (No such file or directory)
close(3)                                = 0
exit_group(0)                          = ?
+++ exited with 0 +++

这就是一个典型的Linux单例守护进程启动做的事情:检测进程是否已经存在(判断记录文件是否存在以及对应pid进程是否还在执行),并通过syslog套接字文件向syslog服务端发送日志。

很显然,Keepalived无法正常启动是故障宕机时,相应的pid文件没有清理干净,如果仅仅如此,Keepalived应该可以启动,一般守护进程启动都会覆盖残留的锁文件,问题关键在read(3, "\n", 4096) : 锁文件Keepalived.pid是空的!! 而kil 向进程0 发送信号0,执行成功,则Keepalived认为已经有Keepalived进程正在运行。所以问题出在锁文件存在且内容为"\n",故依次清理 keepalived.pid vrrp.pid checkers.pid文件后,Keepalived正常启动。至于定位为何锁文件内容为"\n",那是后话了。

经此一事,笔者想写一写Linux 守护进程

守护进程特点与相关概念

并非运行时间长的程序即是守护进程,笔者并未找到守护进程最标准的定义,但守护进程都有下面几个特点:

1、没有控制终端,终端名设置为?号:也就意味着没有 stdin 0 、stdout 1、stderr 2

2、父进程不是用户创建的进程,init进程或者systemd(pid=1)以及用户人为启动的用户层进程一般以pid=1的进程为父进程,而以kthreadd内核进程创建的守护进程以kthreadd为父进程

3、守护进程一般是会话首进程、组长进程。

4、工作目录为 \ (根),主要是为了防止占用磁盘导致无法卸载磁盘

这里涉及到一些概念,是unix为了更好管理进程间的关系提出的概念和方法,稍做说明下

控制终端

通过网络登录或者终端登录建立的会话,会分配唯一一个tty终端或者pts伪终端(网络登录),实际上它们都是虚拟的,以文件的形式建立在/dev目录,而并非实际的物理终端。

在终端中按下的特殊按键:中断键(ctrl+c)、退出键(ctrl+\)、终端挂起键(ctrl + z)会发送给当前终端连接的会话中的前台进程组中的所有进程

在网络登录程序中,登录认证守护程序 fork 一个进程处理连接,并以ptys_open 函数打开一个伪终端设备(文件)获得文件句柄,并将此句柄复制到子进程中作为标准输入、标准输出、标准错误,所以位于此控制终端进程下的所有子进程将可以持有终端

与控制终端相连的会话首进程也叫控制进程

进程组

进程组是一个或者多个进程的集合。一般由某个程序fork出一个家族来构成进程组,或者由管道命令建立作业构成进程组。

同一个进程组中的所有进程接收来自同一终端的信号。

进程组中的第一个进程作为进程组的首长,进程组id取首长进程的id。在各个进程中,通过函数getpgrp获取其所属进程组id

孤儿进程组

一个进程的父进程终止后,进程变成了孤儿进程,将被pid为1的进程(init进程或者systemd)收养。

而对孤儿进程组的定义是:进程组中每个进程的父进程要么在组中,也么不在该组所在会话中。

换言之,如果一个进程组中进程的父进程如果是组中成员,或者是init、systemd进程的话,这个进程组就一定是孤儿进程组。这样的进程组是很常见的,下图就是一个简单且典型的孤儿进程组

Linux守护进程、sighup与nohup详解

很显然,只有一个进程的进程组,并且是孤儿进程的话,进程组将变成孤儿进程组(哪怕它只有一个进程)。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/f1a3a6f773e083b9c97fa8acf011a20e.html