05如何创建守护进程,防止僵尸子进程的出现
一、如何将一个终端进程更改守护进程
问:为什么要有这个功能呢?
答:解放终端,实现项目中。特别是在后期,程序还需要设置开机自启动的功能。
//创建守护进程
if(p_config->GetIntDefault("Daemon",0) == 1) //读配置文件,拿到配置文件中是否按守护进程方式启动的选项
{
//1:按守护进程方式运行
int cdaemonresult = ngx_daemon(); //---创建守护进程的入口
if(cdaemonresult == -1) //fork()失败
{
exitcode = 1; //标记失败
goto lblexit;
}
if(cdaemonresult == 1)
{
//这是原始的父进程
freeresource(); //只有进程退出了才goto到 lblexit,用于提醒用户进程退出了
//而我现在这个情况属于正常fork()守护进程后的正常退出,不应该跑到lblexit()去执行,因为那里有一条打印语句标记整个进程的退出,这里不该限制该条打印语句;
exitcode = 0;
return exitcode; //整个进程直接在这里退出
}
//走到这里,成功创建了守护进程并且这里已经是fork()出来的进程,现在这个进程做了master进程
g_daemonized = 1; //守护进程标记,标记是否启用了守护进程模式,0:未启用,1:启用了
}
ngx_daemon()
//描述:守护进程初始化
//执行失败:返回-1, 子进程:返回0,父进程:返回1
int ngx_daemon()
{
//(1)创建守护进程的第一步,fork()一个子进程出来
switch (fork()) //fork()出来这个子进程才会成为咱们这里讲解的master进程;
{
case -1:
//创建子进程失败
ngx_log_error_core(NGX_LOG_EMERG,errno, "ngx_daemon()中fork()失败!");
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)
{
ngx_log_error_core(NGX_LOG_EMERG, errno,"ngx_daemon()中setsid()失败!");
return -1;
}
//(3)设置为0,不要让它来限制文件权限,以免引起混乱
umask(0);
//(4)打开黑洞设备,以读写方式打开
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;
}
if (dup2(fd, STDIN_FILENO) == -1) //先关闭STDIN_FILENO[这是规矩,已经打开的描述符,动他之前,先close],类似于指针指向null,让/dev/null成为标准输入;
{
ngx_log_error_core(NGX_LOG_EMERG,errno,"ngx_daemon()中dup2(STDIN)失败!");
return -1;
}
if (dup2(fd, STDOUT_FILENO) == -1) //再关闭STDIN_FILENO,类似于指针指向null,让/dev/null成为标准输出;
{
ngx_log_error_core(NGX_LOG_EMERG,errno,"ngx_daemon()中dup2(STDOUT)失败!");
return -1;
}
if (fd > STDERR_FILENO) //fd应该是3,这个应该成立
{
if (close(fd) == -1) //释放资源这样这个文件描述符就可以被复用;不然这个数字【文件描述符】会被一直占着;
{
ngx_log_error_core(NGX_LOG_EMERG,errno, "ngx_daemon()中close(fd)失败!");
return -1;
}
}
return 0; //子进程返回0
}
解释重要的函数:
setsid():
pid_t setsid(void);
如果调用此函数的进程不是一个进程组的组长,则此函数创建一个新会话。
参考《Unix环境高级编程-第3版》 P235
dup2():
int dup2(int fd,int fd2);
用fd2参数指定新描述符的值。
参考《Unix环境高级编程-第3版》 P63
二、防止僵尸子进程的出现
僵尸进程的产生:在Unix系统中,一个子进程结束了,但是他的父进程还活着,但该父进程没有调用(wait/waitpid)函数来进行额外的处置,那么这个子进程就会变成一个"僵尸进程";
僵尸进程:已经被终止,不干活了,但是依旧没有被内核丢弃掉,因为内核认为父进程可能还需要该子进程的一些信息;
waitpid():说白了获取子进程的终止状态,这样,子进程就不会成为僵尸进程了;
pid_t pid = waitpid(-1,&status,WNOHANG);
//第一个参数为-1,表示等待任何子进程,
//第二个参数:保存子进程的状态信息(大家如果想详细了解,可以百度一下)。
//第三个参数:提供额外选项,WNOHANG表示不要阻塞,让这个waitpid()立即返回
//信号处理函数
//siginfo:这个系统定义的结构中包含了信号产生原因的有关信息
static void ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext)
{
//printf("来信号了\n");
ngx_signal_t *sig; //自定义结构
char *action; //一个字符串,用于记录一个动作字符串以往日志文件中写
for (sig = signals; sig->signo != 0; sig++) //遍历信号数组
{
//找到对应信号,即可处理
if (sig->signo == signo)
{
break;
}
} //end for
action = (char *)""; //目前还没有什么动作;
if(ngx_process == NGX_PROCESS_MASTER) //master进程,管理进程,处理的信号一般会比较多
{
//master进程的往这里走
switch (signo)
{
case SIGCHLD: //一般子进程退出会收到该信号
ngx_reap = 1; //标记子进程状态变化,日后master主进程的for(;;)循环中可能会用到这个变量【比如重新产生一个子进程】
break;
//.....其他信号处理以后待增加
default:
break;
} //end switch
}
else if(ngx_process == NGX_PROCESS_WORKER) //worker进程,具体干活的进程,处理的信号相对比较少
{
//worker进程的往这里走
//......以后再增加
//....
}
else
{
//非master非worker进程,先啥也不干
//do nothing
} //end if(ngx_process == NGX_PROCESS_MASTER)
//这里记录一些日志信息
//siginfo这个
if(siginfo && siginfo->si_pid) //si_pid = sending process ID【发送该信号的进程id】
{
ngx_log_error_core(NGX_LOG_NOTICE,0,"signal %d (%s) received from %P%s", signo, sig->signame, siginfo->si_pid, action);
}
else
{
ngx_log_error_core(NGX_LOG_NOTICE,0,"signal %d (%s) received %s",signo, sig->signame, action);//没有发送该信号的进程id,所以不显示发送该信号的进程id
}
//.......其他需要扩展的将来再处理;
//子进程状态有变化,通常是意外退出【既然官方是在这里处理,我们也学习官方在这里处理】
if (signo == SIGCHLD)
{
ngx_process_get_status(); //获取子进程的结束状态
} //end if
return;
}
//获取子进程的结束状态,防止单独kill子进程时子进程变成僵尸进程
static void ngx_process_get_status(void)
{
pid_t pid;
int status;
int err;
int one=0; //抄自官方nginx,应该是标记信号正常处理过一次
//当你杀死一个子进程时,父进程会收到这个SIGCHLD信号。
for ( ;; )
{
//waitpid,有人也用wait,但老师要求大家掌握和使用waitpid即可;这个waitpid说白了获取子进程的终止状态,这样,子进程就不会成为僵尸进程了;
//第一次waitpid返回一个> 0值,表示成功,后边显示 2019/01/14 21:43:38 [alert] 3375: pid = 3377 exited on signal 9【SIGKILL】
//第二次再循环回来,再次调用waitpid会返回一个0,表示子进程还没结束,然后这里有return来退出;
pid = waitpid(-1, &status, WNOHANG); //第一个参数为-1,表示等待任何子进程,
//第二个参数:保存子进程的状态信息(大家如果想详细了解,可以百度一下)。
//第三个参数:提供额外选项,WNOHANG表示不要阻塞,让这个waitpid()立即返回
if(pid == 0) //子进程没结束,会立即返回这个数字,但这里应该不是这个数字【因为一般是子进程退出时会执行到这个函数】
{
return;
} //end if(pid == 0)
//-------------------------------
if(pid == -1)//这表示这个waitpid调用有错误,有错误也理解返回出去,我们管不了这么多
{
//这里处理代码抄自官方nginx,主要目的是打印一些日志。考虑到这些代码也许比较成熟,所以,就基本保持原样照抄吧;
err = errno;
if(err == EINTR) //调用被某个信号中断
{
continue;
}
if(err == ECHILD && one) //没有子进程
{
return;
}
if (err == ECHILD) //没有子进程
{
ngx_log_error_core(NGX_LOG_INFO,err,"waitpid() failed!");
return;
}
ngx_log_error_core(NGX_LOG_ALERT,err,"waitpid() failed!");
return;
} //end if(pid == -1)
//-------------------------------
//走到这里,表示 成功【返回进程id】 ,这里根据官方写法,打印一些日志来记录子进程的退出
one = 1; //标记waitpid()返回了正常的返回值
if(WTERMSIG(status)) //获取使子进程终止的信号编号
{
ngx_log_error_core(NGX_LOG_ALERT,0,"pid = %P exited on signal %d!",pid,WTERMSIG(status)); //获取使子进程终止的信号编号
}
else
{
ngx_log_error_core(NGX_LOG_NOTICE,0,"pid = %P exited with code %d!",pid,WEXITSTATUS(status)); //WEXITSTATUS()获取子进程传递给exit或者_exit参数的低八位
}
} //end for
return;
}

浙公网安备 33010602011771号