- 通過前文(《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啟動時的進程創建過程。從調用函數的順序上看,若配置為只產生一個工作進程,則大致如下: