守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程。
1、守护进程最重要的特性是后台运行。
2、守护进程必须与其运行前的环境隔离开来。这些环境包括未关闭的文件描述符,控制终端,会话和进程组,工作目录以及文件创建掩模等。这些环境通常是守护进程从执行它的父进程(特别是shell)中继承下来的。
3、守护进程的启动方式有其特殊之处。它可以在Linux系统启动时从启动脚本/etc/rc.d中启动,可以由作业规划进程crond启动,还可以由用户终端(shell)执行。
总之,除开这些特殊性以外,守护进程与普通进程基本上没有什么区别。因此,编写守护进程实际上是把一个普通进程按照上述的守护进程的特性改造成为守护进程。如果对进程有比较深入的认识就更容易理解和编程了。
守护进程之编程规则
(1)首先要做的是调用umask将文件模式创建屏蔽字设置为0。
文件权限掩码:是指屏蔽掉文件权限中的对应位。例如,有个文件权限掩码是050,它就屏蔽了文件组拥有者的可读与可执行权限(对应二进制为,rwx, 101)。由于fork函数创建的子进程继承了父进程的文件权限掩码,这就给子进程使用文件带来了诸多的麻烦。因此,把文件权限掩码设置为0(即,不屏蔽任何权限),可以增强该守护进程的灵活性。设置文件权限掩码的函数是umask。通常的使用方法为umask(0)。
(2)调用fork,然后使父进程退出(exit)。if(pid=fork()) exit(0);
(3)调用setsid以创建一个新会话,脱离控制终端和进程组。setsid函数作用:用于创建一个新的会话,并担任该会话组的组长。
调用setsid有3个作用:(a) 让进程摆脱原会话的控制;(b) 让进程摆脱原进程组的控制;(c) 让进程摆脱原控制终端的控制; setsid()
使用setsid函数的目的:由于创建守护进程的第一步调用了fork函数来创建子进程再将父进程退出。由于在调用fork函数时,子进程拷贝了父进程的会话期、进程组、控制终端等,虽然父进程退出了,但会话期、进程组、控制终端等并没有改变,因此,这还不是真正意义上的独立开了。使用setsid函数后,能够使进程完全独立出来,从而摆脱其他进程的控制。
(4)将当前工作目录更改为根目录。#define NOFILE 256 for(i=0;i<NOFILE;i++) close(i);
(5)关闭不再需要的文件描述符。这使守护进程不再持有从其父进程继承来的某些文件描述符(父进程可能是shell进程,或某个其他进程)。
(6)某些守护进程打开/dev/null使其具有文件描述符0、1和2,这样,任何一个试图读标准输入、写标准输出和标准出错的库例程都不会产生任何效果。因为守护进程并不与终端设备相关联,所以不能在终端设备上显示其输出,也无处从交互式用户那里接受输入。
#include <sys/types.h>
#include<sys/stat.h>
#include<sys/time.h>
#include<sys/resource.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
#include<syslog.h>
void daemonize(const char *cmd)
{
int i, fd0, fd1, fd2;
pid_t pid;
struct rlimit rl;
struct sigaction sa;
umask(0); // Clear file creation mask.
if (getrlimit(RLIMIT_NOFILE, &rl) < 0) { // Get maximum number of file descriptors.
err_quit("%s: can‘t get file limit", cmd);
}
if ((pid = fork()) < 0) { //这一步fork保证进程不是进程组组长进程
err_quit("%s: can‘t fork", cmd);
}
else if (pid != 0) { /* parent */
exit(0);
}
setsid(); // 创建一个回话,会话只包含子进程,且子进程是会话首进程
/*
会话首进程的退出会出发SIGHUP信号
默认此信号的操作会终止进程
*/
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGHUP, &sa, NULL) < 0) {
err_quit("%s: can‘t ignore SIGHUP", cmd);
}
/*
再次创建子进程,退出父进程,保证守护进程不是会话首进程,这样open的时候就不会被分配终端
*/
if ((pid = fork()) < 0) {
err_quit("%s: can‘t fork", cmd);
}
else if (pid != 0) { /* parent */
exit(0);
}
if (chdir("/") < 0) { // 改变当前工作路径为根目录
err_quit("%s: can‘t change directory to /", cmd);
}
if (rl.rlim_max == RLIM_INFINITY) { //关闭所有打开的文件描述符
rl.rlim_max = 1024;
}
for (i = 0; i < rl.rlim_max; i++) {
close(i);
}
/*
因为前面关闭了所有的文件描述符,此时open返回的必定是最小的0,后面两次dup返回的依次是1、2,
也就完成了对标准输入、标准输出、标准错误重定向至/dev/null的操作
*/
fd0 = open("/dev/null", O_RDWR);
fd1 = dup(0);
fd2 = dup(0);
/*
* Initialize the log file.
*/
openlog(cmd, LOG_CONS, LOG_DAEMON);
if (fd0 != 0 || fd1 != 1 || fd2 != 2)
{
syslog(LOG_ERR, "unexpected file descriptors %d %d %d",fd0, fd1, fd2);
exit(1);
}
}
http://www.frankyang.cn/2017/05/25/daemon/