nginx中父子進程工作的主體函數
阿新 • • 發佈:2017-07-02
listening 定義 div -s mutex soc 屬性信息 基於 清空
依據Nginx(0.7.67版本號)的代碼。對Nginx主要的進程創建,進程主體以及事件處理進行了簡要的分析。
基本上,父進程(即主進程)一開始會初始化及讀取配置。並載入各模塊的功能,然後fork()出N個子進程(即工作進程),具有同樣的工作邏輯和功能。 /*...*/
//nginx 啟動的時候會嘗試從環境變量中讀取前次運行時候的監聽套接口的id, //並會創建相應數量的ngx_listening_t結構變量(存於 cycle->listening數組中)。 //然後調用這個接口通過getsockname,getsockopt等系統調用把原來套接口的屬性信息和 //設置參數讀取出來去設置那些新創建的ngx_listening_t結構變量。這樣就繼承了前次運行 //時候的監聽套接口了。這個接口是在 ngx_init_cycle之前調用的
if (ngx_add_inherited_sockets(&init_cycle) != NGX_OK) {
return 1; }
/*...*/
//ngx_modules數組在objs/ngx_modules.c定義ngx_max_module = 0;for (i = 0; ngx_modules[i]; i++) { ngx_modules[i]->index = ngx_max_module++; //將每一個模塊編號 }
//ngx_init_cycle()函數,這是個比較重要的函數。被main, ngx_master_process_cycle //和ngx_single_process_cycle 調用, 當中後兩者是在reconfigure的時候被調用的 。
//它主要工作是。初始化cycle是基於舊有的cycle進行的;
會繼承old cycle的非常多屬性。
//比方log等, 可是同一時候會對非常多資源又一次分配,
比方pool, shared mem, file handler,
//listening socket等,同一時候清除舊有的cycle的資源
//讀取配置文件也是在這裏完畢的
cycle = ngx_init_cycle(&init_cycle);
if (ngx_process == NGX_PROCESS_SINGLE) { ngx_single_process_cycle(cycle); } else { //一般採用多進程處理請求 ngx_master_process_cycle(cycle); } }
//主進程(即父進程)的主體 //這個函數會啟動工作進程幹活。而且會處理信號量。處理的過程中會殺死或者創建新的進程 void ngx_master_process_cycle(ngx_cycle_t *cycle) { //把信號添加到監聽集合set sigemptyset(&set);//sigemptyset()用來將參數set信號集初始化並清空 sigaddset(&set, SIGCHLD);//sigaddset() 添加一個信號至信號集 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));
/*...*/
//依照ngx_core_conf_t中worker_processes數,啟動若幹個work進程 ngx_start_worker_processes(cycle, ccf->worker_processes, NGX_PROCESS_RESPAWN);
//後面一個循環對不同的狀態進行不同處理。而那些狀態多數是進程收到的不同信號//ngx_signal_t signals[]數組定義信號以及處理方法:ngx_signal_handler()//信號在ngx_init_signals()裏面初始化//在ngx_signal_handler()裏,依據ngx_process來決定是master process還是 //worker process的處理流程(即是說master和worker都調用ngx_signal_handler //來處理信號)
for ( ;; ) { //掛起當前進程,等到有信號。就會從掛起狀態退出。繼續運行//set記錄當前監聽的信號 sigsuspend(&set); /*各種信號的處理,如NGX_SHUTDOWN_SIGNAL,NGX_RECONFIGURE_SIGNAL等*/ /*省略*/ } }
//ngx_start_worker_processes()函數。這個函數按指定數目n,以ngx_worker_process_cycle()函 //數為參數調用ngx_spawn_process()創建work進程並初始化相關資源和屬性。 //運行子進程的運行函數ngx_worker_process_cycle;向之前已經創建的全部worker廣播當前創建的 //worker進程的信息。每一個進程打開一個通道(ngx_pass_open_channel()) //n是worker process的數目 //type 即創建新進程式的方式,如NGX_PROCESS_RESPAWN, NGX_PROCESS_JUST_RESPAWN 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;
ch.command = NGX_CMD_OPEN_CHANNEL;
for (i = 0; i < n; i++) { //從config讀取CPU的分配 cpu_affinity = ngx_get_cpu_affinity(i); //ngx_spawn_process中設置ngx_process_slot為被分配到子進程的 //空暇slot 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 把這個worker的channel[0]和進程id //在進程表中的偏移slot廣播(ngx_write_channel())給全部其它已經 //創建的worker。 //這樣,創建全然部進程之後,每一個worker就獲得了全部其它worker的 //channel[0]了 ngx_pass_open_channel(cycle, &ch); } }
ngx_pid_t ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data, char *name, ngx_int_t respawn) { /*...*/ //假設類型為NGX_PROCESS_DETACHED。則說明是熱代碼替換(熱代碼替換也是通過這 //個函數進行處理的),因此不須要新建socketpair。 if (respawn != NGX_PROCESS_DETACHED) {//建立socketpair //創建socketpair用於進程間通信。master進程為每一個worker創建一對 //socket, master進程空間打開全部socketpair的channel[0], //channel[1]兩端句柄。
/*...*/ //創建子進程 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(); //調用ngx_worker_process_cycle() //註意:這個函數中定義了子進程處理事件的循環,即子進程不會 //運行這個函數之後的語句 proc(cycle, data);break;default:break; }
}
//worker進程的主體 static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data) { //設置一些環境變量 //調用全部模塊的init_process鉤子函數 //將其它進程的channel[1]關閉。自己的除外//子進程繼承了父進程的ngx_processes數組。但子進程僅僅監聽自己的channel[1] //將自己的channel[0]關閉//由於自己的channel[0]是給其它子進程。用來發送消息的sendmsg //調用ngx_add_channel_event()函數,給ngx_channel註冊一個讀事件處理函數。 ngx_worker_process_init(cycle, 1); //主循環。處理事件 for ( ;; ) { /*...*/
//調用epoll,周期性監聽事件 //先接收連接(並不處理事件)。以及處理進程間信號(如有) //處理accept queue和event queue裏面的事件 ngx_process_events_and_timers(cycle);
/*...*/ }
/*...*/ }
基本上,父進程(即主進程)一開始會初始化及讀取配置。並載入各模塊的功能,然後fork()出N個子進程(即工作進程),具有同樣的工作邏輯和功能。
父進程負責監聽信號(如HUP,QUIT等),通過socket pair把信號傳遞給子進程(子進程間一般不通信)。子進程通過事件來處理父進程傳遞的信號。由於每一個子進程都共享服務監聽port(如http 80),當用戶發送請求時,會觸發子進程的事件調用函數。因此在accept()請求的時候,須要用到mutex,保證僅僅有一個工作進程接受並處理請求。
Nginx主進程的入口是跟一般的程序一樣的main()函數。它的代碼是這種:
int ngx_cdecl main(int argc, char *const *argv)
{
//nginx 啟動的時候會嘗試從環境變量中讀取前次運行時候的監聽套接口的id, //並會創建相應數量的ngx_listening_t結構變量(存於 cycle->listening數組中)。 //然後調用這個接口通過getsockname,getsockopt等系統調用把原來套接口的屬性信息和 //設置參數讀取出來去設置那些新創建的ngx_listening_t結構變量。這樣就繼承了前次運行 //時候的監聽套接口了。這個接口是在 ngx_init_cycle之前調用的
if (ngx_add_inherited_sockets(&init_cycle) != NGX_OK) {
/*...*/
//ngx_modules數組在objs/ngx_modules.c定義ngx_max_module = 0;for (i = 0; ngx_modules[i]; i++) { ngx_modules[i]->index = ngx_max_module++; //將每一個模塊編號 }
//ngx_init_cycle()函數,這是個比較重要的函數。被main, ngx_master_process_cycle //和ngx_single_process_cycle 調用, 當中後兩者是在reconfigure的時候被調用的
if (ngx_process == NGX_PROCESS_SINGLE) { ngx_single_process_cycle(cycle); } else { //一般採用多進程處理請求 ngx_master_process_cycle(cycle); } }
//主進程(即父進程)的主體 //這個函數會啟動工作進程幹活。而且會處理信號量。處理的過程中會殺死或者創建新的進程 void ngx_master_process_cycle(ngx_cycle_t *cycle) { //把信號添加到監聽集合set sigemptyset(&set);//sigemptyset()用來將參數set信號集初始化並清空 sigaddset(&set, SIGCHLD);//sigaddset() 添加一個信號至信號集 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));
/*...*/
//依照ngx_core_conf_t中worker_processes數,啟動若幹個work進程 ngx_start_worker_processes(cycle, ccf->worker_processes, NGX_PROCESS_RESPAWN);
//後面一個循環對不同的狀態進行不同處理。而那些狀態多數是進程收到的不同信號//ngx_signal_t signals[]數組定義信號以及處理方法:ngx_signal_handler()//信號在ngx_init_signals()裏面初始化//在ngx_signal_handler()裏,依據ngx_process來決定是master process還是 //worker process的處理流程(即是說master和worker都調用ngx_signal_handler //來處理信號)
for ( ;; ) { //掛起當前進程,等到有信號。就會從掛起狀態退出。繼續運行//set記錄當前監聽的信號 sigsuspend(&set); /*各種信號的處理,如NGX_SHUTDOWN_SIGNAL,NGX_RECONFIGURE_SIGNAL等*/ /*省略*/ } }
//ngx_start_worker_processes()函數。這個函數按指定數目n,以ngx_worker_process_cycle()函 //數為參數調用ngx_spawn_process()創建work進程並初始化相關資源和屬性。 //運行子進程的運行函數ngx_worker_process_cycle;向之前已經創建的全部worker廣播當前創建的 //worker進程的信息。每一個進程打開一個通道(ngx_pass_open_channel()) //n是worker process的數目 //type 即創建新進程式的方式,如NGX_PROCESS_RESPAWN, NGX_PROCESS_JUST_RESPAWN 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;
ch.command = NGX_CMD_OPEN_CHANNEL;
for (i = 0; i < n; i++) { //從config讀取CPU的分配 cpu_affinity = ngx_get_cpu_affinity(i); //ngx_spawn_process中設置ngx_process_slot為被分配到子進程的 //空暇slot 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 把這個worker的channel[0]和進程id //在進程表中的偏移slot廣播(ngx_write_channel())給全部其它已經 //創建的worker。 //這樣,創建全然部進程之後,每一個worker就獲得了全部其它worker的 //channel[0]了 ngx_pass_open_channel(cycle, &ch); } }
ngx_pid_t ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data, char *name, ngx_int_t respawn) { /*...*/ //假設類型為NGX_PROCESS_DETACHED。則說明是熱代碼替換(熱代碼替換也是通過這 //個函數進行處理的),因此不須要新建socketpair。 if (respawn != NGX_PROCESS_DETACHED) {//建立socketpair //創建socketpair用於進程間通信。master進程為每一個worker創建一對 //socket, master進程空間打開全部socketpair的channel[0], //channel[1]兩端句柄。
//當創建一個worker的時候,這個worker會繼承master當前已經創建並
//打開的全部socketpair。//這個worker初始化的時候(調用ngx_worker_process_init)會關閉 //本進程相應socketpair的channel[0]和其它worker相應的 //channel[1],保持打開本進程相應socketpair的channel[1]和其它 //worker相應的channel[0],並監聽本進程相應socketpair的 //channel[1]的可讀事件。這樣,每一個worker就擁有了其它worker的 //channel[0],能夠sendmsg(channel[0], ...)向其它worker發送消息 /*...*/ }/*...*/ //創建子進程 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(); //調用ngx_worker_process_cycle() //註意:這個函數中定義了子進程處理事件的循環,即子進程不會 //運行這個函數之後的語句 proc(cycle, data);break;default:break; }
}
//worker進程的主體 static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data) { //設置一些環境變量 //調用全部模塊的init_process鉤子函數 //將其它進程的channel[1]關閉。自己的除外//子進程繼承了父進程的ngx_processes數組。但子進程僅僅監聽自己的channel[1] //將自己的channel[0]關閉//由於自己的channel[0]是給其它子進程。用來發送消息的sendmsg //調用ngx_add_channel_event()函數,給ngx_channel註冊一個讀事件處理函數。 ngx_worker_process_init(cycle, 1); //主循環。處理事件 for ( ;; ) { /*...*/
//調用epoll,周期性監聽事件 //先接收連接(並不處理事件)。以及處理進程間信號(如有) //處理accept queue和event queue裏面的事件 ngx_process_events_and_timers(cycle);
/*...*/ }
/*...*/ }
nginx中父子進程工作的主體函數