Linux守护进程
守护进程(daemon)是在后台运行且不与任何控制终端关联的进程。
与终端脱离有两个目的:
- 避免进程运行过程中将信息输出到终端
- 避免进程被终端产生的信息中断
创建守护进程前首先需要理解几个概念:进程组、会话、控制终端。
每个进程除了有一进程ID(PID)之外,还属于一个进程组。
进程组是一个或多个进程的集合,每个进程组有一个进程组ID。
每个进程组有一个组长进程(process group leader),组长进程的PID等于它的进程组ID。
会话(session)是一个或多个进程组的集合。
会话中的多个进程组可被分为一个前台进程组以及多个后台进程组。
一个会话有一个控制终端。
进程可以通过调用setsid函数创建一个新会话。
pid_t setsid(void);
有一点需要注意:调用此函数的进程不能是一个进程组的组长。调用该函数会发生以下3件事:
(1) 该进程变成新会话的会话首进程(session leader)
(2) 该进程成为一个新进程组的组长进程
(3) 该进程没有控制终端
创建守护进程的步骤如下:
(1) 创建子进程,父进程退出
调用fork,然后父进程exit,这么做实现下面两点:第一、如果本进程是从前台作为一个shell命令启动的,当父进程终止时,shell就认为该命令已经执行完毕,这样子进程就自动在后台运行。第二、子进程继承了父进程的进程组ID,而且它有自己的进程ID,这就保证子进程不是一个进程组的组长进程,这是接下去调用setsid函数的必要条件。
(2) 调用setsid创建一个新会话
步骤1中的子进程继承了父进程的进程组、会话、控制终端,调用setsid函数会创建新的会话、新的进程组,而且没有控制终端。
(3) 忽略SIGHUP信号并再次fork
步骤2中的子进程是一个会话首进程,当没有控制终端的会话首进程打开一个终端设备时,该终端自动成为这个会话首进程的控制终端。再次fork可以确保新的子进程不再是一个会话首进程,从而不能自动获得一个控制终端。这里必须忽略SIGHUP信号,因为当会话首进程终止时,其会话中的所有进程(包括再次fork产生的子进程)都会受到SIGHUP信号。
(4) 将当前工作目录更改为根目录
(5) 重设文件权限掩码
(6) 关闭所有打开的文件描述符
(7) 重定向stdin、stdout、stderr
下面给一个具体的例子:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <signal.h> #include <time.h> #define MAXFD 64 void daemon_init() { pid_t pid; if ( (pid = fork()) < 0) { perror("fork error"); exit(1); } else if (pid){ exit(0); } // child 1 if (setsid() < 0) { perror("setsid error"); exit(1); } signal(SIGHUP, SIG_IGN); if ( (pid = fork()) < 0) { exit(1); } else if (pid){ exit(0); } // child 2 chdir("/"); umask(0); for (int i = 0; i < MAXFD; i++) { close(i); } open("/dev/null", O_RDONLY); open("/dev/null", O_RDWR); open("/dev/null", O_RDWR); } int main(int argc, char **argv) { daemon_init(); FILE *fp; time_t t; while (true) { if ( fp = fopen("/tmp/daemon.log", "a") ) { t = time(0); fprintf(fp, "current time is: %s", asctime(localtime(&t))); fclose(fp); } sleep(1); } }
参考文章: