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;
}
posted @ 2022-03-05 15:57  豪崽_ZH  阅读(157)  评论(0)    收藏  举报