四、nginx启动过程中的进程创建(参考《深入剖析Nginx》)

  1. 通过前文(《nginx的函数调用》),已知nginx启动时有如下顺序:

main --> ngx_master_process_cycle --> ngx_start_worker_processes

大致是先启动主进程,再通过ngx_start_worker_processes启动子进程。在main函数末尾,有如下代码:

 if (ngx_process == NGX_PROCESS_SINGLE) {
    ngx_single_process_cycle(cycle);
} else {
    ngx_master_process_cycle(cycle);
}

从本段代码看,如果用户没有配置单进程运行的话,就会进入ngx_master_process_cycle()函数。该函数的参数cycle是一个ngx_cycle_s结构体,存储着许多nginx运行需要的全局信息,但在本节不是重点专注的范围。

2.下面看一看ngx_master_process_cycle()函数的内容。该函数位于 ngx_process_cycle.c中,其中有如下片段:

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);

第一行代码估计是获取配置信息,第二行代码便是上文nginx启动顺序中的ngx_start_worker_processes函数。该函数有三个参数,第一个参数是ngx_cycle_s结构体,第二个参数用于指示要创建的工作进程的个数,第三个参数被定义为:
“#define NGX_PROCESS_RESPAWN -3”
这个参数大概是用来定义进程意外终止后是否重启的一个常量,此处不属于重点关注的内容。

3.关于ngx_start_worker_processes()函数。该函数也位于ngx_process_cycle.c中。其定义很简洁:

static void
ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type)
{
ngx_int_t      i;
ngx_channel_t  ch;
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start worker processes");
ch.command = NGX_CMD_OPEN_CHANNEL;
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_int_t被定义为:”typedef intptr_t ngx_int_t”。也就是说,ngx_int_t也就是intptr_t类型,而intptr_t存在的意义跨平台,其长度总是所在平台的位数,作用是用来存放地址。

  • ngx_channel_t 定义为

    typedef struct {
     ngx_uint_t  command;
     ngx_pid_t   pid;
     ngx_int_t   slot;
     ngx_fd_t    fd;
    } ngx_channel_t;
    

    这是nginx用于进程间通信准备的结构体。

  • for循环n次,创建n个子进程,也就是参数ccf->worker_processes个子进程。而具体fork产生子进程的代码,位于循环中的ngx_spawn_process方法中。

  1. 接下来就是子进程的创建了。ngx_spawn_process函数也位于ngx_process_cycle.c中,共五个参数。其函数头定义为:

    ngx_pid_t
    ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,
    char *name, ngx_int_t respawn)
    

    其中第二个参数是函数指针,这里传入了一个用于处理子进程的函数ngx_worker_process_cycle。现在先看看ngx_spawn_process函数中创建子进程的代码:

     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;
    

    定义很清晰,使用fork产生子进程:

    • 若出错则处理错误。
    • 若为父进程则直接退出switch语句,继续往下运行,直到ngx_spawn_process末尾,将子函数的pid返回。
    • 若为子进程,则先获取pid,然后执行 proc(cycle, data),而proc就是上文中传入ngx_spawn_process的函数指针ngx_worker_process_cycle。

5.至此,父子进程开始同时运行。

  • 下面先看看ngx_worker_process_cycle函数中子进程的运行模式,下面ngx_worker_process_cycle函数的大致结构:

     ngx_worker_process_init(cycle, 1);
     #if (NGX_THREADS){
     ...
     }
    #endif
     for ( ;; ) {
      ...//事件处理
     }  
    

    在ngx_worker_process_cycle函数中,先执行了子进程的初始化函数,之后会进入无限for循环,在for循环中进行工作。

  • 而在父进程中,将会结束ngx_spawn_process,返回子进程的pid,回到ngx_start_worker_processes函数的for循环中,也就是

    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);
    }
    

    父进程在对子进程的信息做一下记录和处理后,又会进行下一次循环,产生新的子进程,直到产生的子进程数量达到参数ccf->worker_processes个。

6.当所有子进程创建完毕后,父进程将结束ngx_start_worker_processes,回到ngx_master_process_cycle函数中,也就是如下代码段中继续运行:

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);
...
for ( ;; ) {
...//信号处理
}

显然,父进程也进入了无限for循环,在循环中工作。

7.总结一下nginx启动时的进程创建过程。从调用函数的顺序上看,若配置为只产生一个工作进程,则大致如下:


四、nginx启动过程中的进程创建(参考《深入剖析Nginx》)_第1张图片
一个主进程,一个工作进程

你可能感兴趣的:(四、nginx启动过程中的进程创建(参考《深入剖析Nginx》))