前言
守护进程可以运行在后台,不受某个特定终端的限制,安全稳定地持续运行,一般会作为程序中的“监管进程存在”。
守护进程有以下特点:
1.不属于任何终端的会话分组,自己拥有会话分组。
2.没有显示终端,一般不与用户交互。
3.一般持续运行在后台。
源代码
//描述:守护进程初始化
//执行失败:返回-1, 子进程:返回0,父进程:返回1
int ngx_daemon()
{
//(1)创建守护进程的第一步,fork()一个子进程出来
switch (fork()) //fork()出来这个子进程才会成为咱们这里讲解的master进程;
{
case -1:
//创建子进程失败
return -1;
case 0:
//子进程,走到这里直接break;
break;
default:
//父进程以往 直接退出exit(0);现在希望回到主流程去释放一些资源
return 1; //父进程直接返回1;
} //end switch
//只有fork()出来的子进程才能走到这个流程
ngx_parent = ngx_pid; //ngx_pid是原来父进程的id,因为这里是子进程,所以子进程的ngx_parent设置为原来父进程的pid
ngx_pid = getpid(); //当前子进程的id要重新取得
//(2)脱离终端,终端关闭,将跟此子进程无关
if (setsid() == -1)
{
return -1;
}
//(3)设置为0,不要让它来限制文件权限,以免引起混乱
umask(0);
//(4)打开黑洞设备,以读写方式打开
int fd = open("/dev/null", O_RDWR);
if (fd == -1)
{
return -1;
}
if (dup2(fd, STDIN_FILENO) == -1) //先关闭STDIN_FILENO[这是规矩,已经打开的描述符,动他之前,先close],类似于指针指向null,让/dev/null成为标准输入;
{
return -1;
}
if (dup2(fd, STDOUT_FILENO) == -1) //再关闭STDIN_FILENO,类似于指针指向null,让/dev/null成为标准输出;
{
return -1;
}
if (fd > STDERR_FILENO) //fd应该是3,这个应该成立
{
if (close(fd) == -1) //释放资源这样这个文件描述符就可以被复用;不然这个数字【文件描述符】会被一直占着;
{
return -1;
}
}
return 0; //子进程返回0
}
分析
创建一个守护进程的方式比较巧妙。首先,在一个终端运行的程序中,无论它fork()
多少次,创建了多少子进程,最原始的父进程是不能离开终端的会话组的。
也就是说,一开始在终端运行的进程,无法成为守护进程。
因此,成为守护进程的愿望只能由它的子进程实现。
创建子进程
//(1)创建守护进程的第一步,fork()一个子进程出来
switch (fork()) //fork()出来这个子进程才会成为咱们这里讲解的master进程;
{
case -1:
//创建子进程失败
return -1;
case 0:
//子进程,走到这里直接break;
break;
default:
//父进程以往 直接退出exit(0);现在希望回到主流程去释放一些资源
return 1; //父进程直接返回1;
} //end switch
首先应该明白fork()函数有三种返回值:
1.返回值为-1:说明子进程创建失败了,这种情况很罕见,基本是因为计算机内存不够继续分配导致的,这就是硬件资源和总体设计的问题了。
返回值为0:说明是子进程。
返回值是个正数:说明是父进程,更确切的说,返回值是子进程的pid号。pid号肯定是一个正数。
通过返回值的区分,我们可以实现在一个程序文件中对子进程和父进程进行分流。
在上面的代码中,子进程使用break
关键字退出switch
循环,继续向函数的下面运行;父进程则直接return
退出该函数,继续回到调用该函数的区域工作。
//只有fork()出来的子进程才能走到这个流程
ngx_parent = ngx_pid; //ngx_pid是原来父进程的id,因为这里是子进程,所以子进程的ngx_parent设置为原来父进程的pid
ngx_pid = getpid(); //当前子进程的id要重新取得
随后要更新该进程的pid,因为调用该函数的是父进程,所以原先的pid一定是父进程的。
子进程分离会话
//(2)脱离终端,终端关闭,将跟此子进程无关
if (setsid() == -1)
{
return -1;
}
setsid()
函数实现了子进程与原先父进程所在终端会话的脱离,这样子进程就已经成为了一个守护进程,在后台运行。
设置权限掩码
//(3)设置为0,不要让它来限制文件权限,以免引起混乱
umask(0);
在创建守护进程之后,需要为其设置一次权限掩码,不要限制文件的权限。
输出与输出的重定向
这里需要补充一个概念:空设备。
空设备的路径是:/dev/null
。
空设备是一个特殊的设备文件,它会丢弃一起写入其中的内容。
一般称其为“黑洞设备”,专门吸收你不想要的内容。
如果我们将守护进程的标准输入与标准输出重定向到空设备,就能实现守护进程与用户的零交互,不受影响地在后台运行。
//(4)打开黑洞设备,以读写方式打开
int fd = open("/dev/null", O_RDWR);
if (fd == -1)
{
return -1;
}
if (dup2(fd, STDIN_FILENO) == -1) //先关闭STDIN_FILENO[这是规矩,已经打开的描述符,动他之前,先close],类似于指针指向null,让/dev/null成为标准输入;
{
return -1;
}
if (dup2(fd, STDOUT_FILENO) == -1) //再关闭STDIN_FILENO,类似于指针指向null,让/dev/null成为标准输出;
{
return -1;
}
if (fd > STDERR_FILENO) //fd应该是3,这个应该成立
{
if (close(fd) == -1) //释放资源这样这个文件描述符就可以被复用;不然这个数字【文件描述符】会被一直占着;
{
return -1;
}
}
return 0; //子进程返回0
最后注意一下,打开文件的句柄需要在函数的最后进行回收,不然文件描述符会被一直占用。