nginx的啟動過程代碼主要分布在src/core以及src/os/unix目錄下。啟動流程的函數調用序列:main(src/core/nginx.c)→ngx_init_cycle(src/core/ngx_cycle.c)→ngx_master_process_cycle(src/os/)。nginx的啟動過程就是圍繞着這三個函數進行的。
main函數的處理過程總體上可以概括如下:
簡單初始化一些資料接結構和子產品:ngx_debug_init、ngx_strerror_init、ngx_time_init、ngx_regex_init、ngx_log_init、ngx_ssl_init等
擷取并處理指令參數:ngx_get_options。
初始化ngx_cycle_t結構體變量init_cycle,設定其log、pool字段等。
init_cycle.pool = ngx_create_pool(1024, log);
if (init_cycle.pool == NULL) {
return 1;
}
儲存參數,設定全局變量:ngx_argc ngx_os_argv ngx_argv ngx_environ
處理控制台指令行參數,設定init_cycle的字段。這些字段包括:conf_prefix、prefix(-p prefix)、conf_file(-c filename)、conf_param(-g directives)。此外,還設定init_cyle.log.log_level=NGX_LOG_INFO。
調用ngx_os_init來設定一些和作業系統相關的全局變量:ngx_page_size、ngx_cacheline_size、ngx_ncpu、ngx_max_sockets、ngx_inherited_nonblocking。
調用ngx_crc32_table_init初始化ngx_crc32_table_short。用于後續做crc校驗。
調用ngx_add_inherited_sockets(&init_cycle)→ngx_set_inherited_sockets,初始化init_cycle的listening字段(一個ngx_listening_t的數組)。
對所有子產品進行計數
調用ngx_init_cycle進行子產品的初始化,當解析配置檔案錯誤時,退出程式。這個函數傳入init_cycle然後傳回一個新的ngx_cycle_t。
調用ngx_signal_process、ngx_init_signals處理信号。
在daemon模式下,調用ngx_daemon以守護程序的方式運作。這裡可以在./configure的時候加入參數–with-debug,并在nginx.conf中配置:
可以取消守護程序模式以及master線程模型。
調用ngx_create_pidfile建立pid檔案,把master程序的pid儲存在裡面。
根據程序模式來分别調用相應的函數
} else {
ngx_master_process_cycle(cycle);
多程序的情況下,調用ngx_master_process_cycle。單程序的情況下調用ngx_single_process_cycle完成最後的啟動工作。
整個啟動過程中一個關鍵的變量init_cycle,其資料結構ngx_cycle_t如下所示:
它儲存了一次啟動過程需要的一些資源。
ngx_init_cycle函數的處理過程如下:
調用ngx_timezone_update()、ngx_timeofday、ngx_time_update()來更新時區、時間等,做時間校準,用來建立定時器等。
建立pool,賦給一個新的cycle(ngx_cycle_t)。這個新的cycle的一些字段從舊的cycle傳遞過來,比如:log,conf_prefix,prefix,conf_file,conf_param。
cycle->prefix.len = old_cycle->prefix.len;
cycle->prefix.data = ngx_pstrdup(pool, &old_cycle->prefix);
if (cycle->prefix.data == NULL) {
ngx_destroy_pool(pool);
return NULL;
cycle->conf_file.len = old_cycle->conf_file.len;
cycle->conf_file.data = ngx_pnalloc(pool, old_cycle->conf_file.len + 1);
if (cycle->conf_file.data == NULL) {
ngx_cpystrn(cycle->conf_file.data, old_cycle->conf_file.data,
old_cycle->conf_file.len + 1);
cycle->conf_param.len = old_cycle->conf_param.len;
cycle->conf_param.data = ngx_pstrdup(pool, &old_cycle->conf_param);
if (cycle->conf_param.data == NULL) {
還有一些字段會首先判斷old_cycle中是否存在,如果存在,則申請同樣大小的空間,并初始化。這些字段如下:
//paths
cycle->paths.elts = ngx_pcalloc(pool, n * sizeof(ngx_path_t *));
if (cycle->paths.elts == NULL) {
cycle->paths.nelts = 0;
cycle->paths.size = sizeof(ngx_path_t *);
cycle->paths.nalloc = n;
cycle->paths.pool = pool;
//open_files
if (old_cycle->open_files.part.nelts) {
n = old_cycle->open_files.part.nelts;
for (part = old_cycle->open_files.part.next; part; part = part->next) {
n += part->nelts;
n = 20;
//shared_memory
if (old_cycle->shared_memory.part.nelts) {
n = old_cycle->shared_memory.part.nelts;
for (part = old_cycle->shared_memory.part.next; part; part = part->next)
{
n = 1;
//listening
n = old_cycle->listening.nelts ? old_cycle->listening.nelts : 10;
cycle->listening.elts = ngx_pcalloc(pool, n * sizeof(ngx_listening_t));
if (cycle->listening.elts == NULL) {
cycle->listening.nelts = 0;
cycle->listening.size = sizeof(ngx_listening_t);
cycle->listening.nalloc = n;
cycle->listening.pool = pool;
此外,new_log.log_level重新指派的為NGX_LOG_ERR;old_cycle為傳遞進來的cycle;hostname為gethostname;初始化resuable_connection_queue。
這裡有一個關鍵變量的初始化:conf_ctx。初始化為ngx_max_module個void *指針。說明其實所有子產品的配置結構的指針。
調用所有子產品的create_conf,傳回的配置結構指針放到conf_ctx數組中,索引為ngx_modules[i]->index。
從指令行和配置檔案讀取配置更新到conf_ctx中。ngx_conf_param是讀取指令行中的指令,ngx_conf_parse是把讀取配置檔案。ngx_conf_param最後也是通過調用ngx_cong_parse來讀取配置的。ngx_conf_parse函數中有一個for循環,每次都調用ngx_conf_read_token取得一個配置指令,然後調用ngx_conf_handler來處理這條指令。ngx_conf_handler每次會周遊所有子產品的指令集,查找這條配置指令并分析其合法性,如果正确則建立配置結構并把指針加入到cycle.conf_ctx中。 周遊指令集的過程首先是周遊所有的核心類子產品,若是 event類的指令,則會周遊到ngx_events_module,這個子產品是屬于核心類的,其鈎子set又會嵌套調用ngx_conf_parse去周遊所有的event類子產品,同樣的,若是http類指令,則會周遊到ngx_http_module,該子產品的鈎子set進一步周遊所有的http類子產品,mail類指令會周遊到ngx_mail_module,該子產品的鈎子進一步周遊到所有的mail類子產品。要特别注意的是:這三個周遊過程中會在适當的時機調用event類子產品、http類子產品和mail類子產品的建立配置和初始化配置的鈎子。從這裡可以看出,event、http、mail三類子產品的鈎子是配置中的指令驅動的。
調用core module的init_conf。
讀取核心子產品ngx_core_module的配置結構,調用ngx_create_pidfile建立pid檔案。
這裡代碼中有一句注釋:
當不是第一次初始化cycles時才會調用ngx_create_pidfile寫入pid。
調用ngx_test_lockfile,ngx_create_paths并打開error_log檔案複制給cycle->new_log.file。
周遊cycle的open_files.part.elts,打開每一個檔案。open_files填充的檔案資料是讀取配置檔案時寫入的。
建立共享記憶體。這裡和對open_files類似。先預配置設定空間,再填充資料。
處理listening sockets,周遊cycle->listening數組與old_cycle->listenning進行比較,設定cycle->listening的一些狀态資訊,調用ngx_open_listening_sockets啟動所有監聽socket,循環調用socket、bind、listen完成服務端監聽監聽socket的啟動。并調用ngx_configure_listening_sockets配置監聽socket,根據ngx_listening_t中的狀态資訊設定socket的讀寫緩存和TCP_DEFER_ACCEPT。
調用每個module的init_module。
關閉或者删除一些殘留在old_cycle中的資源,首先釋放不用的共性記憶體,關閉不使用的監聽socket,再關閉不适用的打開檔案。最後把old_cycle放入ngx_old_cycles。最後再設定一個定時器,定期回調ngx_cleaner_event清理ngx_old_cycles。周期設定為30000ms。
接下來是程序的啟動,包括master和worker程序。main函數最後調用ngx_master_process_cycle來啟動master程序模式(這裡對單程序模式不做講述)。
設定一些信号,如下:
調用ngx_setproctitle設定程序标題:”master process” + ngx_argv[0…]
啟動worker程序,數量為ccf->worker_processes。
啟動檔案cache管理程序。
這裡的cahche在一些子產品中是需要的,如fastcgi子產品等,這些子產品會把檔案cache路徑添加到cycle->paths中,檔案cache管理程序會定期調用這些子產品的檔案cache處理鈎子處理一下檔案cache。
master主循環,主要是循環處理信号量。在循環過程中,判斷相應的條件然後進入相應的處理。這裡的相關标志位基本都是在信号處理函數中指派的。
信号處理函數是在main函數中進行的初始化:ngx_init_signals(cycle->log)。其中的signal handler代碼如下所示:
建立worker子程序的函數是ngx_start_worker_processes。
這裡ngx_pass_open_channel,即周遊所有worker程序,跳過自己和異常的worker,把消息發送給各個worker程序。worker程序的管道可讀事件捕捉函數是ngx_channel_handler(ngx_event_t *ev),在這個函數中,會讀取message,然後解析,并根據不同給的指令做不同的處理。
這裡有一個關鍵的函數是ngx_pid_t ngx_spawn_process(ngx_cycle_t cycle, ngx_spawn_proc_pt proc, void data,char *name, ngx_int_t respawn)。proc是子程序的執行函數,data是其參數,name是程序名。
這個函數的任務:
有一個ngx_processes全局數組,包含了所有的子程序,這裡會fork出子程序并放入相應的位置,并設定這個程序的相關屬性。
建立socketpair,并設定相關屬性
子啊子程序中執行傳遞進來的函數。
接下來建立一對socketpair句柄,然後初始化相關屬性。
接下來就是fork子程序,并設定程序相關參數。
最後看一下,worker程序執行的函數static void ngx_worker_process_cycle(ngx_cycle_t cycle, void data)。
調用ngx_worker_process_init初始化;
設定ngx_process=NGX_PROCESS_WORKER
全局性的設定,包括執行環境、優先級、限制、setgid、setuid、信号初始化
調用所有子產品的init_process鈎子
關閉不使用的管道句柄,關閉目前的worker子程序的channel[0]句柄和繼承來的其他程序的channel[1]句柄。使用其他程序的channel[0]句柄發送消息,使用本程序的channel[1]句柄監聽事件。
進行線程相關的操作。(如果有線程模式)
主循環處理各種狀态,類似master程序的主循環。
總結一下,nginx的啟動過程可以劃分為兩個部分,
讀取配置檔案并設定全局的配置結構資訊以及每個子產品的配置結構資訊,調用子產品的 create_conf鈎子和init_conf鈎子。
建立程序和程序間通信機制,master程序負責管理各個worker子程序,通過 socketpair向子程序發送消息,各個worker子程序服務利用事件機制處理請求,通過socketpair與其他子程序通信(發送消息或者接收消息),程序啟動的各個适當時機會調用子產品的init_module鈎子、init_process鈎子、exit_process鈎子和 exit_master鈎子,init_master鈎子沒有被調用過。nginx的worker子程序繼承了父程序的全局變量之後,子程序和父程序就會獨立處理這些全局變量,有些全局量需要在父子程序之間同步就要通過通信方式了,比如 ngx_processes(程序表)的同步。
原文出處:後端技術雜談
<a href="http://www.rowkey.me/blog/2014/09/24/nginx-bootstrap/" target="_blank">原文連結</a>
轉載請與作者聯系,同時請務必标明文章原始出處和原文連結及本聲明。