在nginx啓動過程當中,模塊的初始化是整個啓動過程當中的重要部分,並且瞭解了模塊初始化的過程對應後面具體分析各個模塊會有事半功倍的效果。在我看來,分析源碼來了解模塊的初始化是最直接不過的了,因此下面主要經過結合源碼來分析模塊的初始化過程。nginx
稍微瞭解nginx的人都知道nginx是高度模塊化的,各個功能都封裝在模塊中,而各個模塊的初始化則是根據配置文件來進行的,下面咱們會看到nginx邊解析配置文件中的指令,邊初始化指令所屬的模塊,指令其實就是指示怎樣初始化模塊的。數組
模塊的初始化主要在函數ngx_init_cycle(src/ngx_cycle.c)中下列代碼完成:緩存
1 ngx_cycle_t * 2 ngx_init_cycle(ngx_cycle_t *old_cycle) 3 { 4 ... 5 //配置上下文 6 cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *)); 7 ... 8 //處理core模塊,cycle->conf_ctx用於存放全部CORE模塊的配置 9 for (i = 0; ngx_modules[i]; i++) { 10 if (ngx_modules[i]->type != NGX_CORE_MODULE) { //跳過不是nginx的內核模塊 11 continue; 12 } 13 module = ngx_modules[i]->ctx; 14 //只有ngx_core_module有create_conf回調函數,這個會調用函數會建立ngx_core_conf_t結構, 15 //用於存儲整個配置文件main scope範圍內的信息,好比worker_processes,worker_cpu_affinity等 16 if (module->create_conf) { 17 rv = module->create_conf(cycle); 18 ... 19 cycle->conf_ctx[ngx_modules[i]->index] = rv; 20 } 21 } 22 //conf表示當前解析到的配置命令上下文,包括命令,命令參數等 23 conf.args = ngx_array_create(pool, 10, sizeof(ngx_str_t)); 24 ... 25 conf.temp_pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log); 26 ... 27 conf.ctx = cycle->conf_ctx; 28 conf.cycle = cycle; 29 conf.pool = pool; 30 conf.log = log; 31 conf.module_type = NGX_CORE_MODULE; //conf.module_type指示將要解析這個類型模塊的指令 32 conf.cmd_type = NGX_MAIN_CONF; //conf.cmd_type指示將要解析的指令的類型 33 //真正開始解析配置文件中的每一個命令 34 if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) { 35 ... 36 } 37 ... 38 //初始化全部core module模塊的config結構。調用ngx_core_module_t的init_conf, 39 //在全部core module中,只有ngx_core_module有init_conf回調, 40 //用於對ngx_core_conf_t中沒有配置的字段設置默認值 41 for (i = 0; ngx_modules[i]; i++) { 42 if (ngx_modules[i]->type != NGX_CORE_MODULE) { 43 continue; 44 } 45 module = ngx_modules[i]->ctx; 46 if (module->init_conf) { 47 if (module->init_conf(cycle, cycle->conf_ctx[ngx_modules[i]->index]) 48 == NGX_CONF_ERROR) 49 { 50 environ = senv; 51 ngx_destroy_cycle_pools(&conf); 52 return NULL; 53 } 54 } 55 } 56 ... 57 }
cycle->conf_ctx是一個指針數組,數組中的每一個元素對應某個模塊的配置信息。ngx_conf_parse用戶真正解析配置文件中的命令,conf存放解析配置文件的上下文信息,如module_type表示將要解析模塊的類型,cmd_type表示將要解析的指令的類型,ctx指向解析出來信息的存放地址,args存放解析到的指令和參數。具體每一個模塊配信息的存放以下圖所示,NGX_MAIN_CONF表示的是全局做用域對應的配置信息,NGX_EVENT_CONF表示的是EVENT模塊對應的配置信息,NGX_HTTP_MAIN_CONF,NGX_HTTP_SRV_CONF,NGX_HTTP_LOC_CONF表示的是HTTP模塊對應的main,server,local域的配置信息。框架
下面咱們來具體看下ngx_conf_parse函數是怎樣解析每一個指令的。模塊化
1 char * 2 ngx_conf_parse(ngx_conf_t *cf, ngx_str_t *filename) 3 { 4 ... 5 if (filename) { 6 //打開配置文件 7 fd = ngx_open_file(filename->data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); 8 ... 9 prev = cf->conf_file; 10 cf->conf_file = &conf_file; 11 //獲取配置文件信息 12 if (ngx_fd_info(fd, &cf->conf_file->file.info) == NGX_FILE_ERROR) { 13 ... 14 } 15 //配置緩衝區用於存放配置文件信息 16 cf->conf_file->buffer = &buf; 17 //NGX_CONF_BUFFER = 4096,直接malloc 18 buf.start = ngx_alloc(NGX_CONF_BUFFER, cf->log); 19 ... 20 //[pos,last)表示緩存中真正存儲了數據的區間,[start,end)表示緩存區的物理區域 21 buf.pos = buf.start; 22 buf.last = buf.start; 23 buf.end = buf.last + NGX_CONF_BUFFER; 24 buf.temporary = 1; 25 cf->conf_file->file.fd = fd; 26 cf->conf_file->file.name.len = filename->len; 27 cf->conf_file->file.name.data = filename->data; 28 cf->conf_file->file.offset = 0; 29 cf->conf_file->file.log = cf->log; 30 cf->conf_file->line = 1; 31 type = parse_file; 32 33 } 34 ... 35 for ( ;; ) { 36 rc = ngx_conf_read_token(cf); //從配置文件中讀取下一個命令 37 ... 38 rc = ngx_conf_handler(cf, rc); //查找命令所在的模塊,執行命令對應的函數 39 ... 40 } 41 ... 42 }
ngx_conf_parse是配置解析的入口函數,它根據當成上下文讀取配置文件數據,進行分析的同時對配置文件語法進行檢查。同時,若是遇到的指令包含塊,這個函數會在指令處理函數修改做用域等上下文信息後,被間接遞歸調用來處理塊中的配置信息。第一次調用ngx_conf_parse時,函數會打開配置文件,設置正在執行的解析類型,讀取命令,而後調用ngx_conf_handler。咱們先來看下ngx_conf_read_token怎麼讀取命令的:函數
1 //解析一個命令和其所帶的參數 2 static ngx_int_t 3 ngx_conf_read_token(ngx_conf_t *cf) 4 { 5 ... 6 found = 0; //標記是否找到一個命令 7 need_space = 0; //下一個字符但願是空格 8 last_space = 1; //上一個字符是空格 9 sharp_comment = 0; //註釋 10 variable = 0; //存在變量 11 quoted = 0; //\符號 12 s_quoted = 0; //單引號 13 d_quoted = 0; //雙引號 14 ... 15 for ( ;; ) { 16 ch = *b->pos++; //當前字符存儲於ch中 17 if (ch == LF) { //換行 18 if (sharp_comment) { //註釋結束,只有行註釋 19 sharp_comment = 0; 20 } 21 } 22 if (sharp_comment) { //跳過註釋,直到換行 23 continue; 24 } 25 if (quoted) { //若是上一個字符是\,則跳過當前字符,後面讀取token會作處理 26 quoted = 0; 27 continue; 28 } 29 if (need_space) { //次字符必須是space分割符 30 /* 31 忽略掉space字符;space字符包括' ','\t',CR,LF 32 若是字符是';','{',這次命令讀取結束; 33 若是字符是')',開始讀取新的token; 34 若是讀到其它字符則解析失敗 35 */ 36 } 37 if (last_space) { //表示一個token開始了,第一個非space字符 38 /* 39 忽略掉space字符; 40 若是字符是';','{','}',這次命令讀取結束; 41 若是字符是'#',字符後面是註釋; 42 若是字符是'\',下一個字符將被忽略 43 若是字符是'"',''',下一個字符在單引號或雙引號中 44 */ 45 } else { //開始讀取新token 46 /* 47 若是字符是'$',後面的非space字符組成變量token; 48 碰到告終束引號'"','''時,token讀取完成; 49 碰到了space字符,token讀取完成 50 */ 51 if (found){ 52 /* 53 將找到的token追加到cf->args數組中,而且每一個token字符串以'\0'結束 54 */ 55 } 56 } 57 } 58 }
下面咱們再來看下命令處理函數ngx_conf_handler:post
1 static ngx_int_t 2 ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last) 3 { 4 ... 5 for (i = 0; ngx_modules[i]; i++) { 6 cmd = ngx_modules[i]->commands; 7 ... 8 for ( /* void */ ; cmd->name.len; cmd++) { 9 //命令名稱對比 10 //模塊類型對比 11 //命令類型對比 12 //命令是否帶塊 13 //檢測命令參數 14 conf = NULL; 15 if (cmd->type & NGX_DIRECT_CONF) { 16 conf = ((void **) cf->ctx)[ngx_modules[i]->index]; 17 18 } else if (cmd->type & NGX_MAIN_CONF) { 19 conf = &(((void **) cf->ctx)[ngx_modules[i]->index]); 20 21 } else if (cf->ctx) { 22 confp = *(void **) ((char *) cf->ctx + cmd->conf); 23 24 if (confp) { 25 conf = confp[ngx_modules[i]->ctx_index]; 26 } 27 } 28 //調用命令中的set函數 29 rv = cmd->set(cf, cmd, conf); 30 ... 31 } 32 } 33 ... 34 }
徹底讀取到一條配置指令後,Nginx會使用該指令名在全部指令定義中進行查找。可是,在進行查找以前,Nginx會先驗證模塊的類型和當前解析函數上下文中的類型是否一致。隨後,在某個模塊中找到匹配的指令定義後,還會驗證指令能夠出現的做用域是否包含當前解析函數上下文中記錄的做用域。最後,檢查指令的參數個數是否和指令定義中標明的一致。spa
校驗工做完成後,Nginx將指令名和全部模塊預約義支持的指令進行對比,找到徹底匹配的配置指令定義。根據配置指令的不一樣類型,配置項的存儲位置也不一樣。指針
NGX_DIRECT_CONF類型的配置指令,其配置項存儲空間是全局做用域對應的存儲空間。這個類型的指令主要出如今ngx_core_module模塊裏。code
1 conf = ((void **) cf->ctx)[ngx_modules[i]->index];
NGX_MAIN_CONF表示配置指令的做用域爲全局做用域。縱觀Nginx整個代碼,除了ngx_core_module的配置指令(同時標識爲NGX_DIRECT_CONF)位於這個做用域中外,另外幾個定義新的子級做用域的指令–events、http、mail、imap,都是非NGX_DIRECT_CONF的NGX_MAIN_CONF指令,它們在全局做用域中並未被分配空間,因此在指令處理函數中分配的空間須要掛接到全局做用域中,故傳遞給指令處理函數的參數是全局做用域的地址。
1 conf = &(((void **) cf->ctx)[ngx_modules[i]->index];
其它類型配置指令項的存儲位置和指令出現的做用域(而且非全局做用域)有關:
1 confp = *(void **) ((char *) cf->ctx + cmd->conf); 2 if (confp) { conf = confp[ngx_modules[i]->ctx_index]; }
配置項將要存儲的位置肯定後,調用指令處理函數,完成配置項初始化和其它工做。
rc = cmd->set(cf, cmd, conf);
下面經過分析模塊HTTP的初始化來進一步加深理解模塊的初始化。
http模塊由不少個模塊組成,這裏主要講解兩個主要模塊的初始化,它們是ngx_http_module和ngx_http_core_module。ngx_http_moduel模塊很簡單,它的定義以下:
1 static ngx_command_t ngx_http_commands[] = { 2 { ngx_string("http"), //http命令描述結構 3 NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, 4 ngx_http_block, 5 0, 6 0, 7 NULL }, 8 ngx_null_command 9 }; 10 static ngx_core_module_t ngx_http_module_ctx = { 11 ngx_string("http"), 12 NULL, 13 NULL 14 }; 15 ngx_module_t ngx_http_module = { 16 NGX_MODULE_V1, 17 &ngx_http_module_ctx, /* module context */ 18 ngx_http_commands, /* module directives */ 19 NGX_CORE_MODULE, /* module type */ 20 ... 21 };
從模塊的定義看,它是一個NGX_CORE_MODULE類型的模塊,就包含一條指令http,當在全局做用域中遇到http指令後,會調用ngx_http_block函數,傳遞給該函數的參數爲:cf,當前配置上下文信息;cmd,http命令描述結構;conf,ngx_http_module模塊在全局做用域數組中的地址。下面看下ngx_http_block函數:
1 static char * 2 ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 3 { 4 ... 5 ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t)); //http main配置結構 6 ... 7 *(ngx_http_conf_ctx_t **) conf = ctx; //將http main配置結構掛到全局做用域上 8 //計算NGX_HTTP_MODULE類型模塊的個數,並計算每一個NGX_HTTP_MODULE模塊在所有NGX_HTTP_MODULE模塊中的下標 9 ngx_http_max_module = 0; 10 for (m = 0; ngx_modules[m]; m++) { 11 if (ngx_modules[m]->type != NGX_HTTP_MODULE) { 12 continue; 13 } 14 ngx_modules[m]->ctx_index = ngx_http_max_module++; 15 } 16 //http的main域配置 17 ctx->main_conf = ngx_pcalloc(cf->pool, 18 sizeof(void *) * ngx_http_max_module); 19 ... 20 //http的server域,用來合併server塊中的域 21 ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module); 22 ... 23 //http的local域,用來合併server塊中的local塊中的域 24 ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module); 25 //建立各個做用域對應的配置結構 26 for (m = 0; ngx_modules[m]; m++) { 27 if (ngx_modules[m]->type != NGX_HTTP_MODULE) { 28 continue; 29 } 30 module = ngx_modules[m]->ctx; 31 mi = ngx_modules[m]->ctx_index; 32 if (module->create_main_conf) { 33 ctx->main_conf[mi] = module->create_main_conf(cf); 34 ... 35 } 36 if (module->create_srv_conf) { 37 ctx->srv_conf[mi] = module->create_srv_conf(cf); 38 ... 39 } 40 if (module->create_loc_conf) { 41 ctx->loc_conf[mi] = module->create_loc_conf(cf); 42 ... 43 } 44 } 45 pcf = *cf; 46 cf->ctx = ctx; //更新配置上下文爲http的上下文 47 ... 48 //遞歸ngx_conf_parse來調用處理http包含的塊的配置信息 49 cf->module_type = NGX_HTTP_MODULE; //模塊類型爲NGX_HTTP_MODULE 50 cf->cmd_type = NGX_HTTP_MAIN_CONF; //指令的做用域 51 rv = ngx_conf_parse(cf, NULL); 52 ... 53 *cf = pcf; //恢復配置上下文 54 ... 55 }
到這裏,已經建立的配置信息以下圖所示:
遞歸調用ngx_conf_parse後,接下來處理的每一個指令都是NGX_HTTP_MAIN_CONF做用域的,每一個指令的處理函數會初始化應配置結構信息,而具體的配置信息存放在哪一個數組裏面由指令定義中的conf字段決定,即必定是main_conf,srv_conf和loc_conf之中的一種。NGX_HTTP_MAIN_CONF做用域中的不少指令都屬於ngx_http_core_module模塊,下面看下這個模塊的定義:
1 static ngx_command_t ngx_http_core_commands[] = { 2 ... 3 { ngx_string("server"), //server指令 4 NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, 5 ngx_http_core_server, 6 0, 7 0, 8 NULL }, 9 { ngx_string("location"), //location指令 10 NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE12, 11 ngx_http_core_location, 12 NGX_HTTP_SRV_CONF_OFFSET, 13 0, 14 NULL }, 15 ... 16 } 17 18 static ngx_http_module_t ngx_http_core_module_ctx = { 19 ngx_http_core_preconfiguration, /* preconfiguration */ 20 NULL, /* postconfiguration */ 21 ngx_http_core_create_main_conf, /* create main configuration */ 22 ngx_http_core_init_main_conf, /* init main configuration */ 23 ngx_http_core_create_srv_conf, /* create server configuration */ 24 ngx_http_core_merge_srv_conf, /* merge server configuration */ 25 ngx_http_core_create_loc_conf, /* create location configuration */ 26 ngx_http_core_merge_loc_conf /* merge location configuration */ 27 }; 28 ngx_module_t ngx_http_core_module = { 29 NGX_MODULE_V1, 30 &ngx_http_core_module_ctx, /* module context */ 31 ngx_http_core_commands, /* module directives */ 32 NGX_HTTP_MODULE, /* module type */ 33 ... 34 };
這個模塊定義了不少指令,其中server和location是比較關鍵的兩條指令,它們分別處理server和location指令後面所帶有的塊。處理這些塊的方法和處理http指令後面的塊的方法相似,都是修改配置上下文,調用ngx_conf_parse來處理,固然在處理server塊時只會生成srv_conf和loc_conf數組,全部的server塊共享一個main_conf數組,在處理location塊時只會生成loc_conf數組,同一個server塊中的location共享這個server塊中的ser_conf數組。
有些指令能同時出如今http塊,server塊和location塊中,而且底層塊中指令會覆蓋上層塊中的定義,若是底層塊中指令沒定義,上層塊的指令定義會傳遞到底層塊中。因此在函數ngx_http_block調用ngx_conf_parse解析處理全部的http模塊的配置指令後,調用ngx_http_merge_servers將http塊中的srv_conf信息傳遞到各個server塊中,在ngx_http_merge_servers中將server塊中的loc_conf信息合併到全部的locations塊中。