Nginx源码分析--master进程

来源:互联网 发布:晨曦软件 编辑:程序博客网 时间:2024/04/30 15:40

Nginx分为Single和Master两种进程模型,Single模型即为单进程方式工作,具有较差的容错能力,不适合生产之用。Master模型即为一个master进程+N个worker进程的工作方式。生产环境都是用master-worker模型来工作。本文着重分析Nginx的master进程做了哪些事情,它是如何管理好各个worker进程的。在具体分析代码之前,先附上一张master进程的全貌图:

我们知道在main函数中完成了Nginx启动初始化过程,启动初始化过程中的一个重要环节就是解析配置文件,回调各个配置指令的回调函数,因此完成了各个模块的配置及相互关联。在所有的这些重要及不重要的初始化完成后,main函数就开始为我们打开进程的“大门”——调用ngx_master_process_cycle(cycle); 接下来的文字里面,我们就重点来看看这个函数里做了一些什么事情。

    sigemptyset(&set);    sigaddset(&set, SIGCHLD);    sigaddset(&set, SIGALRM);    sigaddset(&set, SIGIO);    sigaddset(&set, SIGINT);    sigaddset(&set, ngx_signal_value(NGX_RECONFIGURE_SIGNAL));    sigaddset(&set, ngx_signal_value(NGX_REOPEN_SIGNAL));    sigaddset(&set, ngx_signal_value(NGX_NOACCEPT_SIGNAL));    sigaddset(&set, ngx_signal_value(NGX_TERMINATE_SIGNAL));    sigaddset(&set, ngx_signal_value(NGX_SHUTDOWN_SIGNAL));    sigaddset(&set, ngx_signal_value(NGX_CHANGEBIN_SIGNAL));    if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,                      "sigprocmask() failed");    }    sigemptyset(&set);迈入这扇大门之后,迎面而来的就是屏蔽一系列的信号,以防干事的时候,被打扰嘛。你懂的。
    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);    ngx_start_worker_processes(cycle, ccf->worker_processes, NGX_PROCESS_RESPAWN);    ngx_start_cache_manager_processes(cycle, 0);
这里好像要开始创建子进程了哦,没错,master进程就是通过依次调用这两个函数来创建子进程。第一个调用的函数创建的子进程我们称为worker进程,第二调用的函数创建的是有关cache的子进程。接收请求,完成响应的就是worker进程。光光是调用这个函数好像没什么看头,我们深入“虎穴”窥探一下究竟。
    for (i = 0; i < n; i++) {        cpu_affinity = ngx_get_cpu_affinity(i);        ngx_spawn_process(cycle, ngx_worker_process_cycle, NULL,                          "worker process", type);        ch.pid = ngx_processes[ngx_process_slot].pid;        ch.slot = ngx_process_slot;        ch.fd = ngx_processes[ngx_process_slot].channel[0];        ngx_pass_open_channel(cycle, &ch);    }
其中的ngx_spawn_process函数如下:
ngx_pid_tngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,    char *name, ngx_int_t respawn){    u_long     on;    ngx_pid_t  pid;    ngx_int_t  s;    if (respawn >= 0) {        s = respawn;    } else {        for (s = 0; s < ngx_last_process; s++) {            if (ngx_processes[s].pid == -1) {                break;            }        }
        if (s == NGX_MAX_PROCESSES) {            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,                          "no more than %d processes can be spawned",                          NGX_MAX_PROCESSES);            return NGX_INVALID_PID;        }    }    if (respawn != NGX_PROCESS_DETACHED) {        /* Solaris 9 still has no AF_LOCAL */        if (socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1)        {            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,                          "socketpair() failed while spawning \"%s\"", name);            return NGX_INVALID_PID;        }        ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0,                       "channel %d:%d",                       ngx_processes[s].channel[0],                       ngx_processes[s].channel[1]);        if (ngx_nonblocking(ngx_processes[s].channel[0]) == -1) {            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,                          ngx_nonblocking_n " failed while spawning \"%s\"",                          name);            ngx_close_channel(ngx_processes[s].channel, cycle->log);            return NGX_INVALID_PID;        }        if (ngx_nonblocking(ngx_processes[s].channel[1]) == -1) {            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,                          ngx_nonblocking_n " failed while spawning \"%s\"",                          name);            ngx_close_channel(ngx_processes[s].channel, cycle->log);            return NGX_INVALID_PID;        }        on = 1;        if (ioctl(ngx_processes[s].channel[0], FIOASYNC, &on) == -1) {            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,                          "ioctl(FIOASYNC) failed while spawning \"%s\"", name);            ngx_close_channel(ngx_processes[s].channel, cycle->log);            return NGX_INVALID_PID;        }        if (fcntl(ngx_processes[s].channel[0], F_SETOWN, ngx_pid) == -1) {            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,                          "fcntl(F_SETOWN) failed while spawning \"%s\"", name);            ngx_close_channel(ngx_processes[s].channel, cycle->log);            return NGX_INVALID_PID;        }        if (fcntl(ngx_processes[s].channel[0], F_SETFD, FD_CLOEXEC) == -1) {            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,                          "fcntl(FD_CLOEXEC) failed while spawning \"%s\"",                           name);            ngx_close_channel(ngx_processes[s].channel, cycle->log);            return NGX_INVALID_PID;        }        if (fcntl(ngx_processes[s].channel[1], F_SETFD, FD_CLOEXEC) == -1) {            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,                          "fcntl(FD_CLOEXEC) failed while spawning \"%s\"",                           name);            ngx_close_channel(ngx_processes[s].channel, cycle->log);            return NGX_INVALID_PID;        }        ngx_channel = ngx_processes[s].channel[1];    } else {        ngx_processes[s].channel[0] = -1;        ngx_processes[s].channel[1] = -1;    }    ngx_process_slot = s;    pid = fork();  /*创建子进程*/    switch (pid) {    case -1:  /*进程创建失败*/        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,                      "fork() failed while spawning \"%s\"", name);        ngx_close_channel(ngx_processes[s].channel, cycle->log);        return NGX_INVALID_PID;    case 0:  /*子进程*/        ngx_pid = ngx_getpid();        proc(cycle, data);        break;    default:        break;    }    ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start %s %P", name, pid);    ngx_processes[s].pid = pid;    ngx_processes[s].exited = 0;    if (respawn >= 0) {        return pid;    }    ngx_processes[s].proc = proc;    ngx_processes[s].data = data;    ngx_processes[s].name = name;    ngx_processes[s].exiting = 0;    switch (respawn) {    case NGX_PROCESS_NORESPAWN:        ngx_processes[s].respawn = 0;        ngx_processes[s].just_spawn = 0;        ngx_processes[s].detached = 0;        break;    case NGX_PROCESS_JUST_SPAWN:        ngx_processes[s].respawn = 0;        ngx_processes[s].just_spawn = 1;        ngx_processes[s].detached = 0;        break;    case NGX_PROCESS_RESPAWN:        ngx_processes[s].respawn = 1;        ngx_processes[s].just_spawn = 0;        ngx_processes[s].detached = 0;        break;    case NGX_PROCESS_JUST_RESPAWN:        ngx_processes[s].respawn = 1;        ngx_processes[s].just_spawn = 1;        ngx_processes[s].detached = 0;        break;    case NGX_PROCESS_DETACHED:        ngx_processes[s].respawn = 0;        ngx_processes[s].just_spawn = 0;        ngx_processes[s].detached = 1;        break;    }    if (s == ngx_last_process) {        ngx_last_process++;    }    return pid;}
其实吧,ngx_start_worker_processes函数挺短小精干的,再截取主体就剩下这么一个for循环了。此处就是循环创建起n个worker进程,fork新进程的具体工作在ngx_spawn_process函数中完成。这里涉及到了一个全局数组ngx_processes(定义在src/os/unix/ngx_process.c文件中),这个数组的长度为NGX_MAX_PROCESSES(默认1024),存储的元素类型是ngx_process_t(定义在src/os/unix/ngx_process.h文件中)。全局数组ngx_processes就是用来存储每个子进程的相关信息,如:pid,channel,进程做具体事情的接口指针等等,这些信息就是用结构体ngx_process_t来描述的。在ngx_spawn_process创建好一个worker进程返回后,master进程就将worker进程的pid、worker进程在ngx_processes数组中的位置及channel[0]传递给前面已经创建好的worker进程,然后继续循环开始创建下一个worker进程。刚提到一个channel[0],这里简单说明一下:channel就是一个能够存储2个整型元素的数组而已,这个channel数组就是用于socketpair函数创建一个进程间通道之用的。master和worker进程以及worker进程之间都可以通过这样的一个通道进行通信,这个通道就是在ngx_spawn_process函数中fork之前调用socketpair创建的。有兴趣的自己读读ngx_spawn_process吧。
至于ngx_start_cache_manager_processes函数,和start_worker的工作相差无几,这里暂时就不纠结了。至此,master进程就完成了worker进程的创建工作了,此时此刻系统中就有一个master进程+N个worker进程在工作了哦,接下来master进程将“陷入”死循环中守护着worker进程,担当起伟大的幕后工作。在master cycle中调用了sigsuspend(),因而将master进程挂起,等待信号的产生。master cycle所做的事情虽然不算复杂,但却比较多;主要过程就是:【收到信号】,【调用信号处理函数(在初始化过程中注册了)】,【设置对应的全局变量】,【sigsuspend函数返回,判断各个全局变量的值并采取相应的动作】。在这里,我们不对每个信号的处理情况进行分析,随便看看两个信号就好了。
        if (ngx_quit) {            ngx_signal_worker_processes(cycle,                                        ngx_signal_value(NGX_SHUTDOWN_SIGNAL));            ls = cycle->listening.elts;            for (n = 0; n < cycle->listening.nelts; n++) {                if (ngx_close_socket(ls[n].fd) == -1) {                    ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,                                  ngx_close_socket_n " %V failed",                                  &ls[n].addr_text);                }            }            cycle->listening.nelts = 0;            continue;        }
这段位于master cycle中的代码是对SIGQUIT信号进行的处理动作。ngx_quit就那个全局变量之一,当master进程收到这个信号的时候,就调用ngx_signal_handler(定义在src/os/unix/ngx_process.c文件中)设置ngx_quit为1。因此master从sigsuspend返回后,检测到ngx_quit为1,就调用ngx_signal_worker_processes函数向每个worker进程递送SIGQUIT信号,通知worker进程们开始退出工作。然后就关闭所有的监听套接字。最后居然来了一个continue就又回到了cycle中,不是退出吗?为什么是continue而不是exit呢。前面已经提过了,master进程是幕后者,需要守护着worker进程们,既然是守护哪能worker进程没撤退,自己就先撤退了呢。由于,worker进程是master的子进程,所以worker退出后,将发送SIGCHLD信号给master进程,好让master进程为其善后(否则将出现“僵尸”进程)。在master进程收到SIGCHLD信号,就会设置全局变量ngx_reap为1了。
        if (ngx_reap) {            ngx_reap = 0;            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "reap children");            live = ngx_reap_children(cycle);        }

此时,ngx_reap为1了,master进程调用ngx_reap_children处理所有的worker子进程。这个ngx_reap_children函数不光担任起为worker进程善后的工作(子进程的收尸处理是在信号处理函数直接完成的),还担任了重启worker进程的任务。当然,这个重启worker进程是在一些异常情况下导致worker进程退出后的重启,并不是在“君要臣死、臣不得不死”的时候的顽强抵抗。Nginx具有高度的模块化优势,每个人都可以开发自己需要的模块程序,难免会出现一些bug引起worker进程的崩溃,因此master进程就肩负起了容错任务,这样才能够保证24小时的提供服务。

至此,master进程也就差不多了,剩下的一些信号处理动作,有兴趣的自行研究吧,其实master进程在处理热代码替换方面也是值得一读的。下一篇博文理所当然的应该是worker进程了。

原创粉丝点击