目录
一、概念
守护进程(Daemon)也被翻译为精灵进程、后台进程,是一种旨在运行于相对干净环境、不受终端影响的、常驻内存的进程,就像神话中的精灵拥有不死的特性,长期稳定提供某种功能或服务。
在Unix/Linux系统中,使用 ps 命令可以看到许多以 -d 结尾的进程,它们大多都是守护进程。
有许多程序或服务理应成为这种“不死”的守护进程,比如提供系统网络服务的核心程序systemd-networkd,只要系统需要基于TCP/IP协议栈进行网络通信,它就应该一直常驻内存,永不退出。
二、守护进程编写步骤
2.1 忽略SIGHUP
由于终端的关闭会触发SIGHUP并发送给终端所关联的会话的所有进程,而一开始进程尚未脱离原会话,因此应尽早忽略该信号,避免被挂断信号误杀。
// 1,忽略挂断信号SIGHUP,防止被终端误杀
signal(SIGHUP, SIG_IGN);
2.2 产生子进程
从终端(不管是远程登录窗口还是本地伪终端)启动的进程所在的会话都关联了控制终端,而控制终端会有各种数据或信号的输入干扰,为了避开这些干扰,需要脱离控制终端,而脱离控制终端的简单做法就是新建一个新的、没有控制终端的会话,但创建一个新会话的进程必须是非进程组组长,但Linux系统中,从终端启动的进程默认就是其所在进程组的组长,因此摆脱这一困境的简单做法就是让其产生一个子进程,退出原进程(即父进程)并让子进程继续下面的步骤即可。
// 2,退出父进程(原进程组组长),为能成功创建新会话做准备
if(fork() > 0)
exit(0);
2.3 创建新会话
创建新会话,脱离原会话,脱离控制终端。
// 3,创建新会话,脱离原会话,脱离控制终端。
setsid();
2.4 产生孙子进程
此时的进程是其所在的会话的创始进程,而创始进程拥有可以再次关联的控制终端的权限,为避免此种情况的发生,最简单的做法就是退出当前创始进程,改由其子进程(非创始进程)继续完成成为守护进程的使命。
// 4,断绝重新关联控制终端的可能性
if(fork() > 0)
exit(0);
2.5 进入新进程组
虽然此时进程的父进程、祖父进程已经退出,但进程组是一直都在的,且处于新会话中的孙子进程一直都在其祖父进程的进程组之中,而进程组是可以传递信号的,因此为了与任何方面脱离关系,应“自立门户”创建新进程组,并将自身置入其中。
// 5,脱离原进程组,创建并进入只包含自身的进程组
setgpid(0, 0)
2.6 关闭文件资源
文件资源是可以在父子进程之间代际相传的,这其中也包括了标准输入输出文件,而作为守护进程,是一种在后台运行的程序,运行过程中一般无需交互,若有消息需要输出一般会以系统日志的方式输出到指定日志文件中。因此,为了节约系统资源,也为了避免不必要的逻辑谬误,守护进程一般都需要将所有从父辈进程继承下来的文件全部关闭。
// 6,关闭父辈继承下来的所有文件
for(int i=0; i<sysconf(_SC_OPEN_MAX); i++)
close(i);
2.7 关闭文件权限掩码
在Linux系统中创建一个新文件时,可以通过相关的函数参数指定文件的权限。
// 试图在file.txt不存在的情况下,创建一个权限为0777的文件
int fd = open("file.txt", O_CREAT|O_RDWR, 0777);
但其实被创建出来的文件的权限并非代码中指定的权限,该权限与系统当前的文件权限掩码做位与操作之后的值才是文件真正的权限,我们可以通过命令 umask 来查看当前系统默认的文件权限掩码的值:
gec@ubuntu:~$ umask
0022
gec@ubuntu:~$
因此,上述创建的文件的权限最后不是0777,而是0755:
gec@ubuntu:~$ ls -l
-rwxr-xr-x 1 gec gec 0 6月 17 00:18 file.txt
gec@ubuntu:~$
为了让守护进程在后续工作过程创建文件时指定的权限不受系统文件权限掩码干扰,可以将umask设置为0。
实现代码如下:
// 7,避开系统文件权限掩码的干扰
umask(0);
2.8 切换进程工作路径
任何一个进程都有一个当前工作路径,从终端启动的进程的工作路径就是启动时终端所在的系统路径。以下代码可以输出进程当前所在路径:
int main(void)
{
printf("%s\n", getcwd(NULL, 0));
}
注意:
当一个进程的工作路径被卸载时,进程也会随时消亡。守护进程为了避免此种情况发生,最简单的做法就是将自身的工作路径切换到一个无法被卸载的路径下,比如根目录。
// 8,避免所在路径被卸载
chdir("/");
例子:
#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(void)
{
pid_t a;
int max_fd, i;
//忽略信号
signal(SIGHUP, SIG_IGN);
//创建子进程, 父进程退出
a = fork();
if(a > 0)
{
exit(0);
}
//创建新会话,脱离原会话,脱离控制终端。
setsid();
//子进程再生孙子进程 断绝重新关联控制终端的可能性
a = fork();
if(a > 0)
{
exit(0);
}
//脱离原进程组,创建并进入只包含自身的进程组
setpgrp();
//关闭父辈继承下来的所有文件
max_fd = sysconf(_SC_OPEN_MAX);
for(i=0; i<max_fd; i++)
close(i);
//设置掩码位
umask(0);
//切换工作路径
chdir("/");
//暂停
pause();
return 0;
}