典型的例子是一个父进程fork子进程之后,父进程立即退出,这样子进程所在的进程组将变为孤儿进程组。这样的孤儿进程组中的每个停止(Stopped)状态的每个进程都将收到挂断信号(SIGHUP),然后又立即收到继续信号(SIGCONT)。所以fork子进程之后,退出父进程,如果子进程还需要继续运行,则需要处理挂断信号,否则进程对挂断信号的默认处理将是退出。
此时的孤儿进程组并没有变为后台进程,一些博客将后台进程说成是孤儿进程组的一个特点,笔者认为是不正确的,在他们的示例中,孤儿进程组变为后台进程的原因是:父进程退出后,子进程在运行时向自身发送了SIGTSTP信号,这就像在终端按下终端挂起键(ctrl+z)一样,暂时断开了进程与控制终端的连接,自然变成了后台进程。
所以这是将进程转到后台运行的一个手段,但并不能创建守护进程,后面会将怎么创建守护进程。
会话
表示一个或多个进程组的集合,在有控制终端的会话中,可以被分为一个前台进程组和多个后台进程组。
取首进程id为会话id。
函数getsid用来获取会话id,而函数setsid用来新建一个会话,只有非首长进程(非进程组的组长)才能调用setsid新建会话。实际上setsid做了三件事
•设置当前进程的会话id为该进程id,此进程成为会话首进程。
•将调用setsid的进程设置为一个新进程组的首长进程。
•断开已连接的控制终端
这三步是创建守护进程的重要步骤。
下图结合了笔者对这些概念的理解,做出的判断
守护进程的创建
创建守护进程有标准的步骤:
1.如果是单例守护进程,结合锁文件和kill函数检测是否有进程已经运行
2.umask取消进程本身的文件掩码设置,也就是设置Linux文件权限,一般设置为000,这是为了防止子进程创建创建一个不能访问的文件(没有正确分配权限)。此过程并非必须,如果守护进程不会创建文件,也可以不修改
3.fork出子进程,父进程退出。这样子进程一定不是组长进程(进程id不等于进程组id)
4.子进程调用setsid新建会话(使子进程变为会话首进程、组长进程,并断开终端)
5.如果是单例守护进程,将pid写入到记录锁文件,一般为/var/run/xxx.pid
6.切换工作目录到根目录,这是为了防止占用磁盘造成磁盘不能卸载。所以也可以改到别的目录,只要保证目录所在磁盘不会中途卸载
7.重定向输入输入错误文件句柄,将其指向/dev/null。
前面提到,守护进程一般借助记录锁文件来(文件存在并且文件内记录的pid对应的进程依然活跃)判断是否已经有进程存在。
多数守护进程并不自己维护日志文件,而是统一将日志输出给遵循syslog协议的日志进程(如:rsyslogd)处理,统一将日志输出至 /var/log/messages,当然这些日志进程也是可以配置的。
而且守护进程因为是没有终端的后台进程,所以系统不会发送一些跟终端相关的信号给守护进程,程序可以通过捕捉这些只有可能人为发送的信号,来处理一些事情,比如处理SIGHUP来动态更新程序配置就是典型例子。下面的代码演示了如何创建一个守护进程。
#include <stdio.h>
#include <syslog.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/resource.h>
#define PID_FILE "/var/run/sampled.pid"
int sampled_running(){
FILE * pidfile = fopen(PID_FILE,"r");
pid_t pid;
int ret ;
if (! pidfile) {
return 0;
}
ret = fscanf(pidfile,"%d",&pid);
if (ret == EOF && ferror(pidfile) != 0){
syslog(LOG_INFO,"Error open pid file %s",PID_FILE);
}
fclose(pidfile);
// 检测进程是否存在
if ( kill(pid , 0 ) ){
syslog(LOG_INFO,"Remove a zombie pid file %s", PID_FILE);
unlink(PID_FILE);
return 0;
}
return pid;
}
pid_t sampled(){
pid_t pid;
struct rlimit rl;
int fd,i;