本节我们实现一个守护进程,使得我们的程序可以脱离终端,运行在后台。
- 配置
我们希望通过配置文件,可以选择程序是否以守护进程的方式启动,因此首先在配置文件 nginx_sim.conf 中进行如下配置
# 注释行
# 每个有效配置项用 = 处理,= 前不超过40字符,= 后不超过400字符
# 以 [ 开头表示组信息,也等价于注释行
Log = logs/error2.log
LogLevel = 5
#表示程序是否以守护进程启动
Daemon = 1
- 实现一个守护进程函数
在文件 ngx_func.h 中声明函数 int ngx_deamon();在我们的目录结构中已经创建了专门存放进程相关的目录 proc,在此目录下新建 ngx_deamon.cxx 文件,在此文件我们实现这个函数。
/**
* @brief 守护进程的初始化
* @return -1: 执行失败; 子进程:0;父进程:1
*
*/
int ngx_deamon()
{
switch(fork())
{
case -1:
ngx_log_error_core(NGX_LOG_EMERG,errno,"ngx_deamon()中的fork()失败!");
return -1;
case 0:
break;
default:
return 1;
}
//记录进程的ID
ngx_parent = ngx_pid;
ngx_pid = getpid();
//脱离终端
if(-1 == setsid())
{
ngx_log_error_core(NGX_LOG_EMERG,errno,"进程脱离终端失败!");
}
//取消文件权限的限制
umask(0);
int fd = open("/dev/null", O_RDWR);
if (fd == -1)
{
ngx_log_error_core(NGX_LOG_EMERG,errno,"ngx_daemon()中open(\"/dev/null\")失败!");
return -1;
}
//先关闭STDIN_FILENO,类似于指针指向null,让/dev/null成为标准输入;
if (dup2(fd, STDIN_FILENO) == -1)
{
ngx_log_error_core(NGX_LOG_EMERG,errno,"ngx_daemon()中dup2(STDIN)失败!");
return -1;
}
//再关闭STDIN_FILENO,类似于指针指向null,让/dev/null成为标准输出;
if (dup2(fd, STDOUT_FILENO) == -1)
{
ngx_log_error_core(NGX_LOG_EMERG,errno,"ngx_daemon()中dup2(STDOUT)失败!");
return -1;
}
if (fd > STDERR_FILENO)
{
//释放资源这样这个文件描述符就可以被复用;不然这个数字【文件描述符】会被一直占着;
if (close(fd) == -1)
{
ngx_log_error_core(NGX_LOG_EMERG,errno, "ngx_daemon()中close(fd)失败!");
return -1;
}
}
return 0; //子进程返回0
}
- 测试
我们在 main 函数中,设置进程名之后加入以下代码测试
//守护进程
if(CConfig::GetInstance()->GetIntDefault("Daemon",0) == 1)
{
int cdaemonresult = ngx_deamon();
if(cdaemonresult == -1) //fork()失败
{
goto lblexit;
}
if(cdaemonresult == 1)
{
//这是原始的父进程
freereSource(); //只有进程退出了才goto到 lblexit,用于提醒用户进程退出了
//而我现在这个情况属于正常fork()守护进程后的正常退出,不应该跑到lblexit()去执行,因为那里有一条打印语句标记整个进程的退出,这里不该限制该条打印语句;
return 0; //整个进程直接在这里退出
}
//走到这里,成功创建了守护进程并且这里已经是fork()出来的进程,现在这个进程做了master进程
g_daemonized = 1; //守护进程标记,标记是否启用了守护进程模式,0:未启用,1:启用了
}
运行我们的程序,可以看到已经脱离了终端
查看一下后台进程可以看到我们的进程正在运行
Note: 我们的程序中 master 进程其实并非最原始的进程,最原始的进程其实退出了,master 进程也是由最原始的进程 fork 出来的子进程。至于为何这样做,主要是这个原始的主进程是进程组的组长,调用 setsid 函数脱离终端会失败。
至此,我们的程序已经可以以守护进程的方式启动了,下一节将继续我们的进程话题,为我们的程序创建多个 worker 子进程。