1. 程式人生 > >Nginx 模組自主開發七:原始碼剖析整個Nginx框架

Nginx 模組自主開發七:原始碼剖析整個Nginx框架

在部落格提到的Nginx 模組自主開發四: 模組資料結構一個很重要的資料結構ngx_module_s,所有的模組都是用這個結構體來表示。

ngx_module_t結構體

模組又分為ngx_core_module_t和ngx_xxx_module_t,而在ngx_module_t中會包含這兩個結構,只不過不同類的模組包含不同的結構,一般會 用ctx表示,ngx_module_t又用type這個域用來表示在模組的型別

struct ngx_module_s {
   /*ctx_index表示當前模組在這類模組中的序號。這個 成員常常是由管理模組的 一個Nginx核心模組設定的,對於所有的http模組而言,ctx_index是由核心模組ngx_http_module設定的,ctx_index非常重要,Nginx的模組化設計非常依賴各個模組的順序*/
ngx_uint_t ctx_index; /*index表示當前模組在ngx_modules陣列中 的序號。*/ ngx_uint_t index; char *name; // sqare系列的 保留變數,暫未使用 ngx_uint_t spare0; ngx_uint_t spare1; // 模組的版本 ngx_uint_t version; const char *signature; /**ctx用於指向一類的模組的上下文結構體,Nginx模組有許多種類,不同模組之間的功能差別很大。例如事件模組主要處理I/O事件相關的功能,每個模組都有自己的特性,ctx將會指向特定型別模組的公共介面,例如在Http 模組 中,ctx指向ngx_http_module_t結構體*/
void *ctx; // commands將處理nginx.conf中 的配置項 ngx_command_t *commands; /*type 表示該模組的型別,它與ctx指標是緊密相關的,NGX_HTTP_MODULE、NGX_CORE_MODULE、NGX_CONF_MODULE、NGX_EVENT_MODULE、NGX_EVENT_MODULE、NGX_STREAM_MODULE*/ ngx_uint_t type; /*在Nginx 啟動、停止過程中,以下7個函式指標表示有7個執行點分別呼叫這個7個方法,對於任一方法,如果不需要Nginx在某個時刻執行 它,那麼 簡單地把它設為NULL空指標 即可*/
/* 雖然字面上理解應當在master程序啟動時呼叫,但到目前為止,框架程式碼從來不會呼叫它,因此 要設為NULL*/ ngx_int_t (*init_master)(ngx_log_t *log); /*init module 回撥方法在初始化所有模組被呼叫,在master/worker模式下,這個階段將在worker程序前完成 */ ngx_int_t (*init_module)(ngx_cycle_t *cycle); /*init_process回撥方法在正常服務前被呼叫,在master/worker模式下,多個worker子程序已經產生,在每個worker程序初始化會呼叫所有模組的init_process函式*/ ngx_int_t (*init_process)(ngx_cycle_t *cycle); // Nginx目前還支援多執行緒,所以這個沒有被呼叫過 ngx_int_t (*init_thread)(ngx_cycle_t *cycle); void (*exit_thread)(ngx_cycle_t *cycle); /* exit_process回撥方法在服務停止前呼叫。在master/worker模式下,worker會在退出前呼叫它*/ void (*exit_process)(ngx_cycle_t *cycle); //該方法 會在master程序退出前呼叫它 void (*exit_master)(ngx_cycle_t *cycle); //目前還沒用 uintptr_t spare_hook0; uintptr_t spare_hook1; uintptr_t spare_hook2; uintptr_t spare_hook3; uintptr_t spare_hook4; uintptr_t spare_hook5; uintptr_t spare_hook6; uintptr_t spare_hook7; };

這裡看到有兩個index,分別是ctx_index和index,他們的區別是這樣子的(用http模組舉例),ctx_index儲存了每一個http module的config的索引,而所有的http module config是分別儲存在nginx_conf_t的ctx陣列中的.而index儲存了每一個core module的config的索引,而每個core module的config都是儲存在cycle的conf_ctx中的,下面的程式碼輕易看出

#define ngx_http_conf_get_module_main_conf(cf, module)                        
    ((ngx_http_conf_ctx_t *) cf->ctx)->main_conf[module.ctx_index]

#define ngx_get_conf(conf_ctx, module)  conf_ctx[module.index]

ngx_core_module_t都包括(log, event, event_openssl, http, mail,google perftools),可以看到http module本身也是一個core module。這裡要注意還有一個conf module,只不過它也是用core module這個資料結構。

typedef struct {
    ngx_str_t             name;
    void               *(*create_conf)(ngx_cycle_t *cycle);
    char               *(*init_conf)(ngx_cycle_t *cycle, void *conf);
} ngx_core_module_t;

ngx_http_module_t包括所有src/http/下面的模組,它就包含了所有的http module,它們都從屬於http core模組。

typedef struct {
    ngx_int_t   (*preconfiguration)(ngx_conf_t *cf);
    ngx_int_t   (*postconfiguration)(ngx_conf_t *cf);

    void       *(*create_main_conf)(ngx_conf_t *cf);
    char       *(*init_main_conf)(ngx_conf_t *cf, void *conf);

    void       *(*create_srv_conf)(ngx_conf_t *cf);
    char       *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);

    void       *(*create_loc_conf)(ngx_conf_t *cf);
    char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;

還有一個ngx_module_s還有一個很重要的成員就是ngx_command_t。這個域包含當前 模組 所有包含的指令

解析完上面的資料結構,下面將從整體剖析整個Nginx框架的走向,前面的一篇部落格Nginx模組自主開發六:原始碼剖析配置檔案解析過程詳細介紹了ngx_init_cycle的流程,這篇部落格將主要從ngx_init_cycle如何串通所有模組 的流程說起 。(有些部分會重複,但是為了理清整個Nginx框架的流程還是值得)

Nginx在ngx_init_cycle中通過下列程式碼建立所有的 configure,它 通過呼叫每個core module 的Create_conf的方法來建立對應的conf,每個物件都儲存在全域性的conf__ctx中。

/*
建立這些資料結構的工作都需要在這一步進行。Nginx框架只關心NGX_CORE_MODULE核
心模組,這也是為了降低框架的複雜度。這裡將會呼叫所有核心模組的create conf方法(也只有核心模組才有這個方法),這意味著需要所有的核心
模組開始構造用於儲存配置項的結構體。其他非核心模組怎麼辦呢?其實很簡單。這些模組大都從屬於一個核心模組,如每個HTTP模組都由ngx_http_module
管理(如圖8-2所示),這樣ngx_http_module在解析自己感興趣的“http”配置項時,將會呼叫所有HTTP模組約定的方法來建立儲存配置項的結構體
(xxx_create_main_conf、xxx_create_srv_conf、xxx_create_loc_conf方法)。
*/
    for (i = 0; cycle->modules[i]; i++) {
        if (cycle->modules[i]->type != NGX_CORE_MODULE) {
            continue;
        }
    //得到core module
        module = cycle->modules[i]->ctx;
     /如果create_conf存在,則直接建立config
        if (module->create_conf) {
            rv = module->create_conf(cycle);
            if (rv == NULL) {
                ngx_destroy_pool(pool);
                return NULL;
            }
            cycle->conf_ctx[cycle->modules[i]->index] = rv;  
        }
    }
       .......
    conf.ctx = cycle->conf_ctx;  //這樣下面的ngx_conf_param解析配置的時候,裡面對conf.ctx賦值操作,實際上就是對cycle->conf_ctx[i]
    conf.cycle = cycle;
    conf.pool = pool;
    conf.log = log;
    conf.module_type = NGX_CORE_MODULE;
    conf.cmd_type = NGX_MAIN_CONF;

當所有的core module的config都建立完畢後,就要開始解析配置檔案了,解析配置檔案它會一行行讀取,然後如果遇到指令,則會查詢到對應的ngx_command_t物件,然後執行對應的回撥set方法。這裡所有動作都在ngx_conf_parse這個函式中進行。然後在ngx_conf_parse會判斷cf是否有handler回撥,如果有的話,優先呼叫handler回撥,如果沒有,則會進入ngx_conf_handler進行一般處理。
一般來說,如果沒有自定義handler ,解析檔案的過程ngx_init_cycle()->ngx_conf_parse()->ngx_conf_handler()解析 ,最後通過cmd->set方法進行回撥。這時就進入我們ngx_command_t設定的 回撥函式 。比如,ngx_http.c中 的ngx_http_block函式。

// 檔案中的ngx_http.c的ngx_http_bock函式
// 這個函式在分配 ctx的空間之後,同時給main,serv和conf分配空間之後,初始化每個空間的配置
for (m = 0; cf->cycle->modules[m]; m++) {
        if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }

        module = cf->cycle->modules[m]->ctx; // 注意module的賦值情況是ngx_module_t的成員ctx
        mi = cf->cycle->modules[m]->ctx_index; //得到對應的索引
     //如果有對應的回撥,則呼叫回撥函式,然後將返回的模組config設定到ctx的對應的conf列表中
        if (module->create_main_conf) {  
            ctx->main_conf[mi] = module->create_main_conf(cf);
            if (ctx->main_conf[mi] == NULL) {
                return NGX_CONF_ERROR;
            }
        }

        if (module->create_srv_conf) { // 
            ctx->srv_conf[mi] = module->create_srv_conf(cf);
            if (ctx->srv_conf[mi] == NULL) {
                return NGX_CONF_ERROR;
            }
        }

        if (module->create_loc_conf) { //
            ctx->loc_conf[mi] = module->create_loc_conf(cf);
            if (ctx->loc_conf[mi] == NULL) {
                return NGX_CONF_ERROR;
            }
        }
    }
    ....
    // 遞迴解析
    rv = ngx_conf_parse(cf, NULL);

http module相關的config是在ngx_http_block中建立的,在ngx_http_block中會建立,初始化,合併config,以及整個http handler phase的初始化等等.
先是初始化所有的http module的ctx_index.
當http block完全parse完畢之後,就需要merge(main和srv或者srv和loc)相關的config了。不過在每次merge之前都會首先初始化main conf。

  cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
    cscfp = cmcf->servers.elts;

    for (m = 0; cf->cycle->modules[m]; m++) {
        if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }

        module = cf->cycle->modules[m]->ctx;
        mi = cf->cycle->modules[m]->ctx_index;

        /* init http{} main_conf's */

        if (module->init_main_conf) {
            rv = module->init_main_conf(cf, ctx->main_conf[mi]);
            if (rv != NGX_CONF_OK) {
                goto failed;
            }
        }
     //然後開始merge config。
     /* 每個級別都會儲存對應的ctx(main/ser/loc),怎麼說呢,就是在解析HTTP main中建立了3個ctx(main/srv/loc),而在HTTP srv block中將會建立2個ctx(srv/loc),這時發生重複了,那就需要merge了。比如一個命令(srv_offset)在HTTP main中有一個,那麼Nginx將會把它放入到HTTP main的ctx的srv ctx中,然後server block也有一個,那麼Nginx會繼續把它放到Server ctx的 srv_conf中,最後merge他們。*/
        rv = ngx_http_merge_servers(cf, cmcf, module, mi);//合併server{}及其以下的local{}
        if (rv != NGX_CONF_OK) {
            goto failed;
        }

當merge完畢之後,然後就是location的處理,初始化location tree,建立handler phase,呼叫postconfiguration,以及變數的初始化。

    /*
    經過配置的讀取之後,所有server都被儲存在http core模組的main配置中的servers陣列中,而每個server裡面的location都被按配置中
    出現的順序儲存在http core模組的loc配置的locations佇列中,上面的程式碼中先對每個server的location進行排序和分類處理,這一步
    發生在 ngx_http_init_location()函式中:
    */
      /*location處理比較複雜*/
       if (ngx_http_init_locations(cf, cscfp[s], clcf) != NGX_OK) {
            return NGX_CONF_ERROR;
        }
/*    根據已經按照location字串排序過的雙向連結串列,快速地構建靜態的二叉查詢樹。與ngx_http_init_locations方法類似,速個操作也是遞迴進行的
          */
        /*
        下面的ngx_http_init_static_location_trees函式就會將那些普通的location(就是ngx_http_init_locations中name noname regex以外的location(exact/inclusive)),
        即staticlocation,進行樹化(一種三叉樹)處理,之所以要做這樣的處理,是為了在處理http請求時能高效的搜尋的匹配的location配置。
        */

        if (ngx_http_init_static_location_trees(cf, clcf) != NGX_OK) {
            return NGX_CONF_ERROR;
        }
    }

  //初始化handler phase
    if (ngx_http_init_phases(cf, cmcf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }
   for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }

        module = ngx_modules[m]->ctx;
//呼叫回撥
        if (module->postconfiguration) {
            if (module->postconfiguration(cf) != NGX_OK) {
                return NGX_CONF_ERROR;
            }
        }
    }
//開始初始化變數
    if (ngx_http_variables_init_vars(cf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }

做完上面的工作之後,下面進行初始化socket的相關東西,比如設定讀寫回調 函式等


    if (ngx_http_init_phase_handlers(cf, cmcf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }


    /* optimize the lists of ports, addresses and server names */

    if (ngx_http_optimize_servers(cf, cmcf, cmcf->ports) != NGX_OK) {
        return NGX_CONF_ERROR;
    }

ngx_cycle_init剩下的部分,當配置檔案解析完畢後,就開始初始化core module的config

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;
            }
        }
    }

接下來就是建立並初始化所有建立的共享記憶體

  if (ngx_shm_alloc(&shm_zone[i].shm) != NGX_OK) {
            goto failed;
        }

        if (ngx_init_zone_pool(cycle, &shm_zone[i]) != NGX_OK) {
            goto failed;
        }

        if (shm_zone[i].init(&shm_zone[i], NULL) != NGX_OK) {
            goto failed;
        }

接下來就是開啟監聽,初始化所有的模組

.....
if (ngx_open_listening_sockets(cycle) != NGX_OK) {
    goto failed;
}
....
  // 初始化所有的模組
    if (ngx_init_modules(cycle) != NGX_OK) {
        /* fatal */
        exit(1);
    }

到時為止ngx_init_cycle()函式主要的工作都已經完成了。

總結

要搞清Nginx模組和框架之間的去協作不僅要清楚ngx_command_t結構體、ngx_module_t結構體,ngx_conf_t結構以及ngx_cycle_t結構體的關係,還要學會使用回撥方法來分析整個流程 。上面見到ngx_http_block函式中的location的初始化這裡 並沒有仔細展開,因為mail 和stream中並沒有這一步,等以後有涉及到再 詳細瞭解。