- 通过前文(《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方法中。
-
接下来就是子进程的创建了。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启动时的进程创建过程。从调用函数的顺序上看,若配置为只产生一个工作进程,则大致如下: