菜鳥學習Nginx之啟動流程(1)
對於C語言編寫的程式來說,main函式就是入口函式,把main函式研究清楚對於理解軟體架構、功能會有事半功倍的效果。好在Nginx的main函式並不是很複雜,這裡會把啟動流程分成兩篇來介紹,希望能夠描述清楚。
我把啟動流程劃分成兩部分:cycle核心結構體初始化、master/worker程序啟動。本篇介紹cycle核心結構體初始化。
一、初始化流程
在Nginx中有一個結構體伴隨Nginx程序整個生命週期,那就是ngx_cycle。在Nginx中有且只有一個物件ngx_cycle_t。先來看一下Nginx啟動流程圖,有一些無關緊要的功能並沒有在圖中體現:
特別說明:
- 繼承socket,此部分功能主要用於平滑升級功能並且繼承的是監聽socket。為了保證服務部中斷。
- 初始化cycle,核心結構體初始化是Nginx原始碼中最長的函式,裡面涉及的內容非常多,本篇就是詳細分析該方法。
- 啟動後臺程序,一般情況下我們是通過終端,直接執行Nginx程序啟動服務。這種場景屬於前臺程序,為了脫離終端程序,必須要以後臺方式執行Nginx方式。
- Nginx生存模式,一般由兩種,單機模式和master/woker模式。在真正服務部署時,均採用master/worker模式。
二、結構體
typedef struct ngx_cycle_s ngx_cycle_t; struct ngx_cycle_s { /** * 指向一個指標陣列,該陣列元素又指向了一個指標陣列,因此時4級指標. * 第一層陣列下標,是ngx_module_t.index * 第二層陣列下標,是ngx_module_t.ctx_index * 返回配置結構體,每個模組都一個配置模組結構體(自定義). * 由create_conf回撥創建出來的配置結構體 * 注: conf_ctx是一個數組,大小與ngx_modules一樣 初始化在函式ngx_init_cycle */ void ****conf_ctx; ngx_pool_t *pool; /* 程序級記憶體池 */ ngx_log_t *log; ngx_log_t new_log; ngx_uint_t log_use_stderr; /* unsigned log_use_stderr:1; */ ngx_connection_t **files; ngx_connection_t *free_connections; /* 空閒連線 */ ngx_uint_t free_connection_n; /* 空閒連線數 */ ngx_module_t **modules; /* 實際指向ngx_modules.c中ngx_modules */ ngx_uint_t modules_n; ngx_uint_t modules_used; /* unsigned modules_used:1; */ ngx_queue_t reusable_connections_queue; /* 雙向連結串列 空閒connections 可重複使用 */ ngx_uint_t reusable_connections_n; ngx_array_t listening; /* 動態陣列 監聽socket */ ngx_array_t paths; ngx_array_t config_dump; ngx_rbtree_t config_dump_rbtree; ngx_rbtree_node_t config_dump_sentinel; ngx_list_t open_files; ngx_list_t shared_memory; ngx_uint_t connection_n; /* 當前活躍連線數 */ ngx_uint_t files_n; /* 三者對應關係是 按照陣列下標對應 */ ngx_connection_t *connections; /* 連線池 陣列 預設1024 */ ngx_event_t *read_events; /* 陣列 每一個連線至少對應一個讀事件 預設1024 */ ngx_event_t *write_events;/* 陣列 每一個連線至少對應一個寫事件 預設1024 */ ngx_cycle_t *old_cycle; ngx_str_t conf_file; /* 預設/usr/local/nginx/conf/nginx.conf */ ngx_str_t conf_param; ngx_str_t conf_prefix; /* 預設/usr/local/nginx/conf/ */ ngx_str_t prefix; /* /usr/local/nginx/ */ ngx_str_t lock_file; ngx_str_t hostname; };
我相信所有人第一次看到該結構體,第一反應絕對是:我靠,4級指標,什麼鬼!不錯,我就是這個反應,當初看到了這個成員突然有種不想繼續想法。這裡我想說的是:當遇到奇葩的資料結構或者定義的時候,我們需要冷靜下來,慢慢分析,一定能分析出個所以然。如果還不行就是百度/谷歌,我們相信自己絕對不是第一個吃螃蟹的人。
這個4級指標conf_ctx,在註釋中已經介紹的詳細了,如果還是比較模糊,在下面還會具體介紹。
三、詳細說明
函式ngx_init_cycle主要功能就是初始化ngx_cycle_t資料結構中各個成員,該函式大概有900行程式碼,應該是Nginx中最長的程式碼。具體如下:
/** * 建立ngx_cycle_t核心結構 * @param old_cycle 舊的核心結構 * @return 返回新的ngx_cycle_t結構 */ ngx_cycle_t * ngx_init_cycle(ngx_cycle_t *old_cycle) { void *rv; char **senv; ngx_uint_t i, n; ngx_log_t *log; ngx_time_t *tp; ngx_conf_t conf; ngx_pool_t *pool; ngx_cycle_t *cycle, **old; ngx_shm_zone_t *shm_zone, *oshm_zone; ngx_list_part_t *part, *opart; ngx_open_file_t *file; ngx_listening_t *ls, *nls; ngx_core_conf_t *ccf, *old_ccf; ngx_core_module_t *module; char hostname[NGX_MAXHOSTNAMELEN]; ngx_timezone_update(); /* force localtime update with a new timezone */ tp = ngx_timeofday(); tp->sec = 0; ngx_time_update(); log = old_cycle->log; /* 建立記憶體池 */ pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log); if (pool == NULL) { return NULL; } pool->log = log; /* 在記憶體池中為ngx_cycle_t分配記憶體 */ cycle = ngx_pcalloc(pool, sizeof(ngx_cycle_t)); if (cycle == NULL) { ngx_destroy_pool(pool); return NULL; } cycle->pool = pool; cycle->log = log; cycle->old_cycle = old_cycle; /* 配置檔案相關 */ cycle->conf_prefix.len = old_cycle->conf_prefix.len; cycle->conf_prefix.data = ngx_pstrdup(pool, &old_cycle->conf_prefix); if (cycle->conf_prefix.data == NULL) { ngx_destroy_pool(pool); return NULL; } 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_destroy_pool(pool); return 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) { ngx_destroy_pool(pool); return NULL; } n = old_cycle->paths.nelts ? old_cycle->paths.nelts : 10; if (ngx_array_init(&cycle->paths, pool, n, sizeof(ngx_path_t *)) != NGX_OK) { ngx_destroy_pool(pool); return NULL; } ngx_memzero(cycle->paths.elts, n * sizeof(ngx_path_t *)); if (ngx_array_init(&cycle->config_dump, pool, 1, sizeof(ngx_conf_dump_t)) != NGX_OK) { ngx_destroy_pool(pool); return NULL; } ngx_rbtree_init(&cycle->config_dump_rbtree, &cycle->config_dump_sentinel, ngx_str_rbtree_insert_value); 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; } } else { n = 20; } if (ngx_list_init(&cycle->open_files, pool, n, sizeof(ngx_open_file_t)) != NGX_OK) { ngx_destroy_pool(pool); return NULL; } 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 += part->nelts; } } else { n = 1; } if (ngx_list_init(&cycle->shared_memory, pool, n, sizeof(ngx_shm_zone_t)) != NGX_OK) { ngx_destroy_pool(pool); return NULL; }
這段程式碼邏輯比較簡單,只是單純的呼叫內部api對各個成員 進行初始化,例如記憶體池,共享記憶體,配置檔案等。這裡需要特別說明ngx_cycle_t中記憶體池pool。在開篇的時候就已經說了ngx_cycle_t生命週期是和程序一樣的,那麼ngx_cycle_t中的pool也是一樣,在這裡我稱呼它為程序級記憶體池。後續所有記憶體的申請以及子記憶體池(連線級記憶體池、請求級記憶體池)均來自此池。
/* 分配listening陣列動態陣列 如果第一次啟動old_cycle->listening為0 */
n = old_cycle->listening.nelts ? old_cycle->listening.nelts : 10;
if (ngx_array_init(&cycle->listening, pool, n, sizeof(ngx_listening_t)) != NGX_OK)
{
ngx_destroy_pool(pool);
return NULL;
}
ngx_memzero(cycle->listening.elts, n * sizeof(ngx_listening_t));
初始化listening結構,該結構主要用於儲存監聽socket相關資訊,例如:監聽80埠的socket。為什麼listening是動態陣列呢?對於Tcp監聽來說,我們可以指定多個埠同時提供服務,例如:http預設埠80,https預設埠是443。這個時候就需要有多個listening儲存。
ngx_queue_init(&cycle->reusable_connections_queue);
/* 建立大小為ngx_max_module,陣列元素型別為void* 其實建立的指標陣列 */
cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *));
if (cycle->conf_ctx == NULL)
{
ngx_destroy_pool(pool);
return NULL;
}
注意conf_ctx初始化工作,通過記憶體池申請記憶體,其大小為ngx_max_module * sizeof(void *)。實質內容是,建立了一個指標陣列,等價於void* conf_ctx[ngx_max_module],陣列每一個項儲存的是一個指標。個人認為這樣解釋應該比較清晰。陣列與ngx_modules定義順序是保持一致的。例如:conf_ctx[0]是模組ngx_core_module上下文,conf_ctx[4]是模組ngx_events_module上下文,下面程式碼可驗證
/* 從全域性變數ngx_modules拷貝到cycle->modules*/
if (ngx_cycle_modules(cycle) != NGX_OK)
{
ngx_destroy_pool(pool);
return NULL;
}
/* 初始化核心模組即型別為NGX_CORE_MODULE */
for (i = 0; cycle->modules[i]; i++)
{
if (cycle->modules[i]->type != NGX_CORE_MODULE)
{
continue;
}
module = cycle->modules[i]->ctx;/* 定義模組時賦值 */
if (module->create_conf)
{
/**
* 目前定義create_conf回撥方法 只有ngx_core_module和ngx_regex_module
* ngx_event_module沒有定義create_conf,只定義了init_conf,可知
* ngx_event_module對應的conf_ctx是NULL, 但是在經過ngx_conf_parse後
* conf_ctx不為NULL.
*/
rv = module->create_conf(cycle);
if (rv == NULL)
{
ngx_destroy_pool(pool);
return NULL;
}
cycle->conf_ctx[cycle->modules[i]->index] = rv;//給指標陣列賦值
}
}
呼叫核心模組(型別為NGX_CORE_MODULE)中定義的create_conf回撥函式。該函式主要用於建立配置檔案上下文,用於賦值給conf_ctx。在模組宣告時只有ngx_core_module和ngx_regex_module定義了create_conf,看到這裡不知道是否和我有一樣的疑問:那其他模組是在什麼時候生成conf_ctx呢?繼續往下看
senv = environ;//儲存環境變數
ngx_memzero(&conf, sizeof(ngx_conf_t));
/* STUB: init array ? */
conf.args = ngx_array_create(pool, 10, sizeof(ngx_str_t));
if (conf.args == NULL)
{
ngx_destroy_pool(pool);
return NULL;
}
conf.temp_pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log);
if (conf.temp_pool == NULL)
{
ngx_destroy_pool(pool);
return NULL;
}
conf.ctx = cycle->conf_ctx;
conf.cycle = cycle;
conf.pool = pool;
conf.log = log;
conf.module_type = NGX_CORE_MODULE;
conf.cmd_type = NGX_MAIN_CONF;
#if 0
log->log_level = NGX_LOG_DEBUG_ALL;
#endif
if (ngx_conf_param(&conf) != NGX_CONF_OK)
{
environ = senv;
ngx_destroy_cycle_pools(&conf);
return NULL;
}
/**
* 解析配置檔案
* 注1: 經過此方法之後 核心模組ngx_event_module對應的conf_ctx有資料了
* 在執行ngx_conf_parse函式時,會解析nginx.conf配置檔案,當遇到event標籤,會呼叫
* ngx_events_block回撥方法 該方法會設定conf_ctx
* 注2: 經過此方法cycle->listening中會儲存真正資料
*/
if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK)
{
environ = senv;
ngx_destroy_cycle_pools(&conf);
return NULL;
}
通過執行ngx_conf_parse函式,解析nginx.conf配置檔案,在解析過程中遇到標籤就會呼叫對應的解析函式,在解析函式中會對conf_ctx進行賦值。例如:events標籤,會呼叫ngx_events_block。對於其他modules都這樣操作的。
for (i = 0; cycle->modules[i]; i++)
{
if (cycle->modules[i]->type != NGX_CORE_MODULE)
{
continue;
}
module = cycle->modules[i]->ctx;
if (module->init_conf)
{
if (module->init_conf(cycle,
cycle->conf_ctx[cycle->modules[i]->index]) == NGX_CONF_ERROR)
{
environ = senv;
ngx_destroy_cycle_pools(&conf);
return NULL;
}
}
}
呼叫init_conf回撥函式,大部分模組都沒有定義該回調方法。
在接下來,為了節約篇幅,這裡忽略一些邏輯簡單的初始化流程。
/* handle the listening sockets */
if (old_cycle->listening.nelts)
{
/* 只有在平滑升級才會進入此分支,第一次啟動服務不會進入。 針對平滑升級的會有獨立一篇,介時會詳細說明 */
}
else
{
/**
* listening賦值是在執行ngx_conf_parse 即解析配置檔案時,
* 入口ngx_http_block,最終會ngx_create_listening
*/
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++)
{
ls[i].open = 1;
#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
if (ls[i].accept_filter)
{
ls[i].add_deferred = 1;
}
#endif
#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
if (ls[i].deferred_accept)
{
ls[i].add_deferred = 1;
}
#endif
}
}
/* open listening socket */
if (ngx_open_listening_sockets(cycle) != NGX_OK)
{
goto failed;
}
if (!ngx_test_config)
{//對監聽套接字進行配置 主要是socket選項
ngx_configure_listening_sockets(cycle);
}
/* commit the new cycle configuration */
if (!ngx_use_stderr)
{
(void)ngx_log_redirect_stderr(cycle);
}
pool->log = cycle->log;
/* initialize all modules 呼叫所有模組init_module方法*/
if (ngx_init_modules(cycle) != NGX_OK)
{
/* fatal */
exit(1);
}
這部分功能是建立監聽socket並且對socket進行基本設定。 那麼cycle->listening.elts是在什麼地方設定的呢?其實仔細思考一下,我們監聽埠配置是寫在nginx.conf配置檔案,那麼肯定是在解析配置檔案時對其賦值,所以應該ngx_conf_parse。至此,針對ngx_cycle_t初始化基本主要內容介紹完畢。
我們回過頭在來看一下conf_ctx賦值這部分程式碼,上面介紹過conf_ctx有兩種賦值方式,一個是呼叫回撥函式create_conf,另外一個是解析配置檔案標籤資訊。通過ngx_core_module和ngx_event_module來舉例說明這兩種方式:
/**
* create_conf回撥函式 用於生成conf_ctx上下文
*/
static void *
ngx_core_module_create_conf(ngx_cycle_t *cycle)
{
ngx_core_conf_t *ccf;
ccf = ngx_pcalloc(cycle->pool, sizeof(ngx_core_conf_t));
if (ccf == NULL) {
return NULL;
}
/*
* set by ngx_pcalloc()
*
* ccf->pid = NULL;
* ccf->oldpid = NULL;
* ccf->priority = 0;
* ccf->cpu_affinity_auto = 0;
* ccf->cpu_affinity_n = 0;
* ccf->cpu_affinity = NULL;
*/
ccf->daemon = NGX_CONF_UNSET;
ccf->master = NGX_CONF_UNSET;
ccf->timer_resolution = NGX_CONF_UNSET_MSEC;
ccf->shutdown_timeout = NGX_CONF_UNSET_MSEC;
ccf->worker_processes = NGX_CONF_UNSET;
ccf->debug_points = NGX_CONF_UNSET;
ccf->rlimit_nofile = NGX_CONF_UNSET;
ccf->rlimit_core = NGX_CONF_UNSET;
ccf->user = (ngx_uid_t) NGX_CONF_UNSET_UINT;
ccf->group = (ngx_gid_t) NGX_CONF_UNSET_UINT;
if (ngx_array_init(&ccf->env, cycle->pool, 1, sizeof(ngx_str_t))
!= NGX_OK)
{
return NULL;
}
return ccf;
}
/**
* 解析events標籤,呼叫此函式。用於建立conf_ctx上下文
* @param conf是就是cycle中conf_ctx元素地址
*/
static char *
ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
char *rv;
void ***ctx;
ngx_uint_t i;
ngx_conf_t pcf;
ngx_event_module_t *m;
if (*(void **)conf)
{//表示上下文已經存在 直接返回
return "is duplicate";
}
/* count the number of the event modules and set up their indices */
/* 獲取所有event模組數量並且設定他們的索引值ctx_index */
ngx_event_max_module = ngx_count_modules(cf->cycle, NGX_EVENT_MODULE);
ctx = ngx_pcalloc(cf->pool, sizeof(void *));
if (ctx == NULL)
{
return NGX_CONF_ERROR;
}
*ctx = ngx_pcalloc(cf->pool, ngx_event_max_module * sizeof(void *));
if (*ctx == NULL)
{
return NGX_CONF_ERROR;
}
*(void **)conf = ctx;
for (i = 0; cf->cycle->modules[i]; i++)
{
if (cf->cycle->modules[i]->type != NGX_EVENT_MODULE)
{
continue;
}
m = cf->cycle->modules[i]->ctx;
if (m->create_conf)
{//建立子模組conf_ctx上下文
(*ctx)[cf->cycle->modules[i]->ctx_index] =
m->create_conf(cf->cycle);//建立子型別上下文
if ((*ctx)[cf->cycle->modules[i]->ctx_index] == NULL)
{
return NGX_CONF_ERROR;
}
}
}
pcf = *cf;
cf->ctx = ctx;
cf->module_type = NGX_EVENT_MODULE;
cf->cmd_type = NGX_EVENT_CONF;
rv = ngx_conf_parse(cf, NULL);
*cf = pcf;
if (rv != NGX_CONF_OK)
{
return rv;
}
//初始化NGX_EVENT_MODULE模組
for (i = 0; cf->cycle->modules[i]; i++)
{
if (cf->cycle->modules[i]->type != NGX_EVENT_MODULE)
{
continue;
}
m = cf->cycle->modules[i]->ctx;
if (m->init_conf)
{
rv = m->init_conf(cf->cycle,
(*ctx)[cf->cycle->modules[i]->ctx_index]);
if (rv != NGX_CONF_OK)
{
return rv;
}
}
}
return NGX_CONF_OK;
}
四、總結
至此Nginx啟動流程,關於ngx_cycle_t初始化介紹完畢,有些地方可能介紹不到位,請大家多多指點。後面介紹Nginx啟動流程中關於master/worker模式。