1. 程式人生 > >菜鳥學習Nginx之啟動流程(1)

菜鳥學習Nginx之啟動流程(1)

對於C語言編寫的程式來說,main函式就是入口函式,把main函式研究清楚對於理解軟體架構、功能會有事半功倍的效果。好在Nginx的main函式並不是很複雜,這裡會把啟動流程分成兩篇來介紹,希望能夠描述清楚。

我把啟動流程劃分成兩部分:cycle核心結構體初始化、master/worker程序啟動。本篇介紹cycle核心結構體初始化。

一、初始化流程

在Nginx中有一個結構體伴隨Nginx程序整個生命週期,那就是ngx_cycle。在Nginx中有且只有一個物件ngx_cycle_t。先來看一下Nginx啟動流程圖,有一些無關緊要的功能並沒有在圖中體現:

 

特別說明:

  1. 繼承socket,此部分功能主要用於平滑升級功能並且繼承的是監聽socket。為了保證服務部中斷。
  2. 初始化cycle,核心結構體初始化是Nginx原始碼中最長的函式,裡面涉及的內容非常多,本篇就是詳細分析該方法。
  3. 啟動後臺程序,一般情況下我們是通過終端,直接執行Nginx程序啟動服務。這種場景屬於前臺程序,為了脫離終端程序,必須要以後臺方式執行Nginx方式。
  4. 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模式