<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                # Nginx源碼-啟動流程概述 ## 寫在前面 我從去年開始接觸到nginx,工作之余也看過不少關于nginx的資料,但也只是停留在使用階段,后來由于工作特性和它打交道次數的越來越多,遇到不少問題也是通過翻閱nginx文檔資料來解決的,整個過程中逐漸感受到了nginx的美妙之處。于是開始嘗試閱讀nginx源碼,并將一些心得體會記錄下來,分享給所有的nginx使用者和愛好者,希望能和大家一起成長,一起進步。 Nginx有著很多優秀的設計思想,底層實現了諸如內存池,連接池,自旋鎖,紅黑樹,共享內存等常見的數據結構。然而,最吸引我的卻是它的進程模型、模塊化設計以及事件驅動,這些是Nginx具有高可靠性、高擴展性、以及輕松應對C10K,C100K場景的核心要素。 ## 進程模型 Nginx使用了管理進程(master)和工作進程(worker)的設計,master進程負責管理各個worker,通過信號或管道的方式來控制worker的動作。當某個worker異常退出時,master進程在大多數情況下會立即啟動新的worker進程替代它。worker進程是真正處理用戶請求的,各worker進程是平等的,它們通過共享內存,原子操作等一些進程間通信機制來實現負載均衡。多進程模型的設計模式充分利用了SMP多核架構的并發處理能力,保障了服務的健壯性。 模塊化設計:Nginx主框架中只提供了少量的核心代碼,大量強大的功能都是在各模塊中實現的,各模塊繼承了同一套標準的接口規范和數據結構,在開發新功能時,只需要按照這套標準來實現邏輯即可。同時,Nginx將模塊進行了分類和分層,官方將模塊分為核心模塊,配置模塊,事件模塊,HTTP模塊,mail模塊。每類模塊都遵循各自的通用接口規范。在Nginx的啟動時各模塊會分別創建存儲配置的結構體以及進行初始化操作,HTTP模塊還會將各自邏輯以hook形式注冊到Nginx的處理請求的11各階段中。這種設計思想給Nginx帶來了良好的擴展性、伸縮性和可靠性。 ## 事件驅動 作為一款web服務器,Nginx處理的事件主要來自于網絡和磁盤,包括TCP連接的建立與斷開,接收和發送網絡數據包,磁盤文件的I/O操作等等,每種類型都對應了一個讀事件和寫事件,Nginx通過event模塊實現了讀寫事件的收集、管理和分發,同時采用了紅黑樹這種高效的數據結構管理各事件定時器。Nginx的事件處理框架完美的支持了各類操作系統提供的事件驅動模型,包括epoll,poll,select,kqueue,eventport等。它定義了一系列運行在不同操作系統,不同內核版本的事件驅動模塊。特別是在Liunx2.6之后,Nginx采用了最強大的事件管理機制epoll,這是它能夠支持百萬級并發連接的重要基石。 我一直試圖將Nginx和生活中的事物類比,在找到案例的過程中發現,我們的組織結構就是一個"活生生"的Nginx。站在模塊的角度上看,網校有研發、測試、運營、產品、客服等多個部門,每個部門分工明確,都專注于完成自己的使命,大家共同的服務于每一個客戶,就好像Nginx的每類模塊有自己獨有的功能,共同愿景就是服務于每一條請求。站在進程的角度上看,網校有總負責人,他下面又有各部門的leader,每個leader又管理著多個worker,領導們就好像Nginx進程模型中的master,他們管理和監控所有的worker進程,同時做出決策并將信號下達給各worker,當某個worker進程因為異常狀況"離職"時,leader就會立馬招一個新的worker替換他。站在事件的角度看,當接受用戶的需求,指揮中心或決策團隊將這些需求轉化為指令下發給各部門,各部門則會驅動各組的worker干活,網校的每個worker都異常勤奮,堅守在自己的工作崗位上全程“無阻塞”的高效工作。可見網校具有優秀的"Nginx基因",有能力支撐起強大的用戶群體和海量的用戶流量。 這里,我會以我的理解向大家講述Nginx的基本設計思想與實現原理,本文章都是基于nginx 1.15.8.1,也是網校現在生產環境正在使用的版本。 ## Nginx啟動流程--創建ngx_cycle_t 當你敲下命令/home/nginx/sbin/nginx -c /home/nginx/conf/nginx.conf啟動nginx,你可知道Nginx都干了哪些事情嗎?本章就讓我們一起探索整個nginx帝國建立的過程吧! ## 1解析命令行ngx_get_options 首先Nginx總得知道你想干嘛,所以需要先解析輸入的命令行參數。nginx會提前定義幾個全局標志位,然后調用ngx_get_options(argc, argv)來解析命令,解析的過程中會修改不同的標志位,nginx會在之后的啟動過程中根據這些標志位來執行不同的動作。例如當你輸入的是“-v”,nginx就知道了原來你只是想獲取當前版本號,那么它會將ngx_show_version這個標志位設置為1,接下里就會調用ngx_show_version_info()函數向屏幕上打印版本信息。下面是常見的命令參數,對應的全局變量標志位和相關動作。 ![1579420883297_FFF1F8E3-4AD3-4034-A9FB-3EE3B035FD0A](https://img.kancloud.cn/02/95/0295658fafa65a685c52f41a935091fb_830x334.gif) ## 2初始化ngx_*_init 這里,nginx開始針對各種環境變量進行初始化操作,其中較為重要的有以下三個: ### 2.1時間初始化ngx_time_init nginx通過ngx_time_init()?函數初始化了幾個全局的時間變量,其中包括了log的時間格式,http協議的時間格式,系統時間等等,然后使用ngx_time_update()函數更新時間值,這里面會使用ngx_trylock()獲取時間更新的互斥鎖,避免進程或線程間并發更新系統時間,最后通過ngx_gettimeofday()系統調用獲取當前時間并更新到nginx獨有的ngx_time_t結構體里。nginx在處理請求的流程中涉及到了大量獲取時間的操作,出于性能的考慮,nginx在啟動的時候就一次性設置好了各時間對象,同時通過ngx_cached_*等全局變量緩存在內存中,在必要的時候再進行更新,就是為了避免過多的系統調用帶來的額外損耗。 ### 2.2正則初始化ngx_regex_init 無論是location的配置,還是rewrite的規則,nginx都支持基于pcre庫的正則表達式,同時nginx在ngx_regex.c里實現了自己的內存管理回調函數ngx_regex_malloc,ngx_regex_free以及全局的ngx_pcre_pool內存池,這部分數據結構的初始化都會在nginx啟動階段執行。 ### 2.3日志初始化ngx_log_init ngx_log_t是nginx存儲日志相關信息的結構體,其中有兩個重要的成員log_level和*file,前者是用來表示日志的級別,nginx的日志一共有STDERR,EMERG,ALERT,CRIT,ERR,WARN,NOTICE,INFO,DEBUG九個級別。而后者是一個ngx_open_file_t的結構體指針,存儲著文件fd以及文件名等關鍵信息。大家知道日志級別和日志路徑都可以通過“error_log”在配置文件中指定,但是到這里nginx還沒有開始解析配置文件,那么它該如何設置log的級別和文件路徑呢?nginx會暫時通過預定的全局變量將log設置成NOTICE級別,同時將文件指定為安裝路徑下的“/logs/error.log”。如果在后面通過解析nginx.conf配置文件發現用戶的有自定義的配置,則會更新ngx_log_t對應的成員變量。 當然在nginx的啟動流程中,還有其他的初始化操作,比如與https相關的ngx_ssl_init,與獲取操作系統內核參數配置相關的ngx_os_init(),與循環冗余校驗相關的ngx_crc32_table_init(),與slab內存管理相關的ngx_slab_sizes_init(),這里就不一一展開,有興趣的可以參考nginx源碼深入研究。 ## 3 socket繼承 這是一個必不可少的階段,因為NGINX需要判斷當前是否處于升級狀態,也就是我們常說的平滑升級。這時候新版本的master進程需要對舊版本的Nginx服務監聽的句柄做繼承處理。繼承操作是通過調用ngx_add_inherited_sockets函數,該函數的實現也很簡單: 首先是通過getenv()系統調用獲取環境變量“NGINX”,那這個環境變量是在哪里設置的呢,它又保存著什么信息呢?其實舊版本master進程是通過execve的方式來產生新的master進程,因此新的master進程并不會像直接fork子進程那樣繼承父進程的fd,于是舊的master進程在execve之前將自己監聽的套接字描述符保存在NGINX環境變量中,形式為“NGINX=fd1;fd2;fd3...”,這樣新版本的master就可以從中獲知舊的master的fd了。新master進程拿到“NGINX”環境變量之后,會通過雙指針的遍歷操作解析出fd1,fd2 ... fdn,同時依次保存在ngx_listening_t類型的數組中,而ngx_listening_t是nginx特有的保存監聽socket信息的結構體。當然,通過這種方式共享fd的前提是舊master進程提前打開了fd并持有文件鎖。 熟悉Unix編程的讀者都知道要描述一個套接字的基礎信息包括了套接字句柄fd和監聽的sockaddr地址。同樣,這些都是ngx_listening_t里的重要成員,另外為了給用戶更多配置上的自由,nginx還往其中添加了更多成員,包括指定監聽時backlog隊列,內核中對應該套接字的緩沖區大小rcvbuf與sndbuf,TCP建立成功之后的handler函數,當前套接字對應的連接信息ngx_connection_t等等,其中大部分是在ngx_set_inherited_sockets函數中設置的。 ## 4 ngx_init_cycle 這是nginx啟動過程中最重要的一環,也是最復雜的一部分,這里面包含了創建目錄,打開文件,初始化共享內存,解析配置文件,初始化各模塊等操作。在了解這部分內容之前,讀者需要了解一個名為ngx_cycle_t的結構體。 ngx_cycle_t是nginx最為核心的一個數據結構體,無論是master進程、worker進程還是cache manager進程都擁有唯一一個ngx_cycle_t結構體,正如它的名字一樣,nginx的整個生命周期都是圍繞著這個結構體來運行的。可想而知nginx在啟動時,必定會創建這樣一個結構體,下面讓我們看看ngx_cycle_t的重要成員變量: ``` struct ngx_cycle_s { voidconf_ctx; //保存所有模塊存儲配置項結構體 ngx_pool_t*pool;//內存池 ngx_log_t*log; //日志對象 ngx_connection_t*free_connections; //可用連接池 ngx_uint_tfree_connection_n;//可以用連接池中的連接總數 ngx_module_t**modules;//保存nginx編譯的所有模塊 ngx_array_tlistening; //保存所有需要監聽的端口 ngx_array_tpaths;//保存nginx要操作的目錄 ngx_list_topen_files;//保存nginx要打開的文件 ngx_list_tshared_memory;//保存所有共享內存 ngx_uint_tconnection_n; //當前進程中所有連接的總數 ngx_connection_t*connections;//當前進程中所有的連接 ngx_event_t*read_events;//當前進程中所有讀事件 ngx_event_t*write_events; //當前進程中所有寫事件 …… }; ``` 當含有動態資源時,動靜分離是一個常用的方案,對于靜態資源我們可以利用Nginx充在ngx_cycle_t里,我們都能找到上文提到的ngx_log_t以及ngx_listening_t的影子。這里先對它有一個初步的印象,把目光專注在與啟動流程相關最為密切的成員的兩個成員上:conf_ctx和modules。 conf_ctx:可以看到它的類型是void,這是因為它是一個數組,其中的每個成員又是一個指針,每個指針又指向另一個存儲著指針的數組。為什么設計的如此復雜,就是因為它保存著所有模塊存儲配置項的結構體信息,好似一個戶口冊,每家每戶的信息都登記在這里,并且分配好了位置序號index。而每個模塊會在自家的戶口簿里(實際上是各自實現的存儲配置的結構體)記錄下自家所在的index序號。這樣一來,nginx想知道任意一個模塊的詳細配置信息,只要拿著該模塊的index序號,然后去戶口冊里查找就行,無需遍歷整個戶口冊。 modules:顧名思義,它表示的就是目前編譯進nginx的所有模塊,它是一個指向元素類型為ngx_module_t的數組指針。ngx_module_t也是nginx中極其重要的結構體,它容納了一個模塊的所有信息,包括模塊類型,模塊指令,模塊的通用接口,以及該模塊在nginx整個聲明周期里注冊的各種回調函數,當然也有我們剛剛提到的“戶口地址”index。這里可以簡單看下它的原型摘要: struct ngx_module_s { /*該模塊在同類型模塊中的位置序號*/ ngx_uint_tctx_index; /*該模塊在所有模塊中的位置序號*/ ngx_uint_tindex; /*模塊名稱*/ char*name; /*模塊簽名*/ const char*signature; /*該模塊的公共接口,它是ngx_module_t和所有模塊的關系紐帶*/ void*ctx; /*存儲著它支持的指令集以及每條指令對應的handler處理方法*/ ngx_command_t*commands; /*模塊類型*/ ngx_uint_ttype; /*模塊在nginx整個聲明周期里注冊的7個回調函數*/ ngx_int_t(*init_master)(ngx_log_t *log); ngx_int_t(*init_module)(ngx_cycle_t *cycle); ngx_int_t(*init_process)(ngx_cycle_t *cycle); ngx_int_t(*init_thread)(ngx_cycle_t *cycle); void(*exit_thread)(ngx_cycle_t *cycle); void(*exit_process)(ngx_cycle_t *cycle); void(*exit_master)(ngx_cycle_t *cycle); …… }; 我對其中的ctx成員簡單說明一下,讀者可能不太能理解注釋里所謂的“該模塊的公共接口”,為什么說它是ngx_module_t和各類模塊的關系紐帶?前面我們提到,nginx對所有模塊都進行了分類和分層,每類模塊都有自己的特性,都實現了自己的特有的方法,那怎樣能將各類模塊都能和ngx_module_t這唯一的結構體關聯起來呢,這時候通過一個void指針類型的ctx變量來進行接口抽象,同類型的模塊只需要遵循這一套規范即可。這里拿核心模塊和http模塊舉例說明: 對于核心模塊,ctx指向的是名為ngx_core_module_t的結構體,這個結構體很簡單,除了一個name成員就只有create_conf和init_conf兩個方法,那么所有的核心模塊都會去實現這兩個方法,如果有一天nginx又創造了新的核心模塊,那他也一定是按照ngx_core_module_t這個公共接口來實現。 typedef struct { ngx_str_tname; void*(*create_conf)(ngx_cycle_t *cycle); char*(*init_conf)(ngx_cycle_t *cycle, void *conf); } ngx_core_module_t;} 對于http模塊,ctx就指向的是名為ngx_http_module_t的結構體,這個結構體里定義了8個通用的方法,分別是http模塊在解析配置文件前后,以及創建、合并http段,server段和loc段配置時所調用的方法,如下所示: 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; 那么nginx在啟動的時候,就可以根據當前的執行上下文來依次調用所有http模塊里ctx所指定的某個方法。更重要的是對于一個開發者來說,只需要按照ngx_http_module_t里的接口規范實現自己想要的邏輯,這樣不僅降低了開發成本,也增加了nginx模塊的可擴展性和可維護性。 ngx_cycle_t和ngx_module_t的相關內容不是本章的重點,就先暫時講解到這里,后續章節里會對它們的每個成員以及設計思想進行詳細剖析。下面我們繼續回到nginx的啟動流程里的ngx_init_cycle()這里。 ### 4.1新舊cycle交接 前面提到了,在進入ngx_init_cycle之前nginx會先解析命令行參數,進行部分初始化操作,繼承監聽句柄等等,那么nginx會先創建一個臨時的ngx_cycle_t用來保存這部分已經確定的信息。然后在進入ngx_init_cycle之后,nginx會創建新的ngx_cycle_t,此時的cycle才是真正的伴隨著nginx進程一生的結構體。那么自然會有一個新舊cycle交接的過程,需要將old_cycle中已確認的成員信息賦值到新cycle里,對應的部分代碼如下: ngx_cycle_t * ngx_init_cycle(ngx_cycle_t *old_cycle) { …… 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; } //依次將old_cycle中的log,conf_prefix,prefix,conf_file,conf_params信息賦值到新cycle中 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; 可能大家會有疑惑,為什么不在進入ngx_init_cycle之前直接將那部分信息放在新的cycle中,然后直接將新cycle作為參數傳到ngx_init_cycle()函數里,這樣不就省去了重復賦值的操作嗎?我的猜想是nginx想把關于ngx_cycle_t的所有初始化操作都集中在ngx_init_cycle()里,作為整個啟動流程中最重要的一步。但是在此之前又需要做一些前置操作,比如解析并保存配置參數,這時候不得不需要一個cycle來保存這些信息,于是就創建了一個臨時的ngx_cycle_t,然后在ngx_init_cycle()里進行新舊cycle的交接工作。 ### 1.4.2容器初始化 細心的讀者可能有注意到上面的ngx_cyele_t原型中有四個成員變量:listening, pathes, open_files, shared_memory,他們對應的數據類型是ngx_array_t和ngx_list_t,除此之外nginx大家族里還有很多類似的成員,比如ngx_queue_t, ngx_rbtree_t, ngx_pool_t等等,這些具有鮮明特征的結構體是nginx的一大亮點,nginx在c基礎上封裝了一層特有的ngx風格的數據結構,包括字符串,數組,鏈表,紅黑樹等常見容器,nginx對內存和效率的嚴苛都體現在了這些基礎結構體的實現細節上。對于剛才提到的那四個變量信息都是存儲在這樣的容器中,那么在nginx的啟動過程中,必定需要創建申請各種容器的內存并進行初始化,設置默認大小等。nginx的主框架會在解析完配置文件時,根據配置信息向各容器中添磚加瓦,例如每解析到一個listen指令,就會往listening對應數組容器中添加對應的sockaddr信息;每解析到一個error_log或access_log指令,就會往open_files對應的鏈表容器里添加打開的日志文件信息。關于容器初始化對應核心代碼如下: //對于nginx所要操作的文件目錄,初始化對應的動態數組容器,默認大小為10 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; } //初始化保存dump文件的動態數組容器 if (ngx_array_init(&cycle->config_dump, pool, 1, sizeof(ngx_conf_dump_t)) != NGX_OK) { ngx_destroy_pool(pool); return NULL; } //初始化保存sentinel的rbtree容器 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; } //對于nginx已經打開的文件,存儲在open_files里,采用的是單鏈表容器 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; } // listening數組,在進入ngx_init_cycle之前可能已經通過繼承賦值了,默認值為10,這里采用了動態數組容器 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)); ### 1.4.3核心模塊creat_conf 這段源碼邏輯十分簡單,我們先簡單瀏覽一遍: 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) { rv = module->create_conf(cycle); if (rv == NULL) { ngx_destroy_pool(pool); return NULL; } cycle->conf_ctx[cycle->modules[i]->index] = rv; } } 從上面的代碼段可以看到,這里nginx只做了兩件事:一是遍歷所有的模塊,找到類型為NGX_CORE_MODULE的模塊,即核心模塊。目前官方定義的核心模塊一共有6個:ngx_core_module, ngx_http_module, ngx_errlog_module, ngx_events_module, ngx_openssl_module,ngx_mail_module。二是調用核心模塊的create_conf方法,并將返回的值保持在conf_ctx中該模塊index對應的位置上。 前文我們有提到,對于核心模塊的公共接口里都有一個create_conf方法,顧名思義就是創建存儲配置項的結構體。從這段代碼中可以看到,nginx主框架只關心核心模塊的配置,那其他非核心模塊怎么辦呢?這時候就交給各核心模塊去管理了,比如所有的http模塊都由核心模塊ngx_http_module管理,所有的event模塊都由核心模塊ngx_events_module管理。前文提到的網校的例子里,最高領導只用關心各部門leader,各部門leader負責管理該部門的員工們,也是一樣的道理。 事實上,nginx會將所有編譯的模塊都保存在一個ngx_modules[]數組中,其中可能包含了上百個模塊,但是核心模塊只有6個,而實現了create_conf方法的又只有三個: ![1579423439993_E62538E3-3259-4e3e-875E-9D2B92071C62](https://img.kancloud.cn/6f/57/6f57d4eb8231b63e95f47fb4f06f5669_829x331.gif) 以我本地環境為例,此時的conf_ctx的內存分配如下圖示,這點也是很容易理解的。 ![1579423507689_3E69AC80-7BB6-48f8-A4D0-4173955D9B25](https://img.kancloud.cn/8c/59/8c59b145aeb688a47562b0ba1e85b20c_830x249.gif) ### 1.4.4解析配置文件 走到這里,用于存儲所有配置項的conf_ctx結構體已經創建完畢,萬事俱備,只欠解析。這時候nginx就開始讀取配置并解析文件nginx.conf: if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) { environ = senv; ngx_destroy_cycle_pools(&conf); return NULL; } 每次解析到一條指令,就會遍歷所有的模塊,找到對該指令感興趣的模塊,然后調用此模塊對應于該指令的handler回調方法,同時會將解析到的配置值存儲到conf_ctx中對應的結構體里。例如,當讀取到“worker_connection 4;”這個條指令時,nignx首先會找到對它感興趣的是核心模塊ngx_core_module,而該模塊對這個指令設置的handler方法是ngx_set_worker_processes函數,這里nginx會將ngx_core_conf_t結構體里的worker_processes成員設置為4,此時上圖中的conf_ctx[0]里發生了變化。整個解析過程好比失物招領現場,每個配置項都有一個模塊去認領,并且會在conf_ctx里記錄下。 關于解析配置文件的代碼這里僅僅只有5行,但事實上整個過程極其復雜,nginx如何識別一個配置項?我們最關心的http段的配置項如何解析和存儲?遇到event,upstream這種塊配置的時候如何處理?存在配置沖突時nginx該如何選擇?很多問題值得我們細細探究,由于篇幅有限,本文不會深入展開,后續章節我會詳細介紹nginx如何解析一份完整的配置文件。 ### 1.4.5核心模塊init_conf 前文在介紹ngx_module_t結構體的時候提到,核心模塊的ctx的成員中包含create_conf和init_conf兩個方法,其中create_conf已經在前面全部執行了,那么這里nginx會遍歷所有的模塊,找出核心模塊后調用其init_conf方法。因為在解析完配置文件之后,nginx發現有些配置項并沒有寫在配置文件中,于是會在這一步進行一些默認值的設置,例如在ngx_core_module的init_conf函數中,會設置daemon,shutdown_timeout, worker_processes等等默認配置。 ### 1.4.6創建目錄與打開文件 在容器初始化中我們有提到nginx會根據配置信息往各容器中添磚加瓦,其中也包括了在各核心模塊在執行create_conf和init_conf方法時對這些容器變量的修改。比如緩存模塊會在ngx_cycle_t結構體的pathes動態數組和open_files添加需要打開的文件或者目錄,那么在這里,nginx就會去檢測各文件是否存在以及是否有讀寫權限,如果不存在,則會創建并打開對應的目錄或文件,其中包括了以下類型的文件。 l臨時文件 /home/nginx/client_body_temp :保存客戶端POST請求的body /home/nginx/( proxy_temp |fastcgi_temp| uwsgi_temp| scgi_temp):保存后端Server返回的Response l訪問日志文件 /home/nginx/logs/access.log l錯誤日志文件 /home/nginx/logs/error.log 同時,nginx也會對ngx_cycle_t的其他容器中已經添加過的成員進行處理,比如這里也會針對shared_memory鏈表開始初始化用于進程間通信的共享內存。 ### 1.4.7 socket創建與監聽 之前在解析配置文件時,所有的模塊已經解析出自己需要監聽的端口,例如http模塊已經在解析http{…}配置項時得到它想要監聽的端口,并且添加了ngx_cycle_t的listening數組中。那么在這里,nginx就會按照listening數組里的每一個ngx_listening_t元素設置socket句柄并監聽端口,這一步主要是通過ngx_open_listening_sockets函數實現的,該函數的核心摘要代碼如下: ngx_int_t ngx_open_listening_sockets(ngx_cycle_t *cycle) { … for (tries = 5; tries; tries--) { /* for each listening socket */ ls = cycle->listening.elts; for (i = 0; i listening.nelts; i++) { //系統調用socket對象 s = ngx_socket(ls[i].sockaddr->sa_family, ls[i].type, 0); … //綁定sockaddr地址 if (bind(s, ls[i].sockaddr, ls[i].socklen) == -1) {…} // listen函數指定了backlog隊列長度 if (listen(s, ls[i].backlog) == -1){…} } return NGX_OK; }} 這里我摘出了最核心的幾行代碼,熟悉網絡編程的讀者能一目了然的看到,這是一個標準的server端的實現流程了,包括了socket創建套接字描述符,bind綁定sockaddr的ip和port,listen指定同時連接的客戶端的最大長度等等。同時這里可以看到為了提高容錯性,nginx對于每個socket對象的創建和監聽,都會有重試5次的重試機會,從代碼TODO注釋里也能看到,未來nginx打算將這個納入配置選項,讓用戶有更大的自由來決定失敗重試次數。 ### 1.4.8收尾工作 最后,nginx就會處理一些收尾性的工作了,比如調用所有模塊的init_module方法來完成模塊的初始化操作,釋放不需要的內存,關閉舊master進程監聽的socket和文件、銷毀臨時內存池等等。到這里整個ngx_init_cycle工作已經接近尾聲了,nginx也在這里孕育出了一個全新的充滿活力的ngx_cycle_t結構體,它將陪伴著nginx進程走過整個生命周期。 ## 5啟動進程 到這里ngx_init_cycle函數已經完成了他的使命了,接下來回到nginx啟動的主流程中,之后nginx將會根據配置的運行模式決定如何工作,這里篇幅有限,后面章節會詳細敘述nginx如何創建各種進程,如何進入工作循環模式,如何監聽網絡事件等等。 ### 小結 本小節主要介紹了nginx的設計理念和思想,并拿網校進行類比,希望能給讀者一個更貼近生活的感受。同時講述了關于nginx啟動流程中關鍵的幾步,也簡單介紹幾個nginx的核心數據結構和設計思想,關于nginx解析配置文件和啟動進程過程會放在后續章節講解。 (文/張報編/劉宣麟)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看