nginx原始碼閱讀(十五).事件模組小結(從解析配置到事件的處理)
前言
本小節主要是整理一下前幾節分析的nginx的核心模組的ngx_events_module
以及事件模組,關於事件模組什麼時候初始化以及事件的處理等,因此不會涉及到太多具體的程式碼,主要是把握事件模組的整體。
配置項結構體的建立及賦值
在使用一個模組的功能之前,我們需要先根據配置檔案來定製該模組的功能,對於事件模組來說,有選取哪種事件模型,以及單個程序的最大連線數等配置指令。
在ngx_init_cycle
中第一次呼叫ngx_conf_parse
,遇到了event {}
配置項時,會回撥ngx_events_module
中ngx_commands_t
結構體的set
成員指向的函式。而ngx_events_module
set
函式指標呼叫ngx_events_block
函式,它所做的工作大致如下:
- 申請儲存配置項結構體指標的陣列以及指向該陣列的指標的空間
- 呼叫所有事件模組的
create_conf
方法(它們會各自申請儲存配置項的結構體的空間),將指向這些結構體的指標收集起來 - 解析
nginx.conf
檔案,讓所有事件模組解析自己感興趣的配置項並存儲到結構體中(回撥ngx_commands_t
結構體的set
成員指向的函式) - 最後呼叫所有事件模組的
init_conf
方法,init_conf
方法的主要工作就是將沒有設定初值的配置項結構體成員設定為預設值
對於ngx_event_core_module
來說,它關注的配置有連線池的大小(worker_connections、connections)、選擇哪一種事件驅動機制(use)、是否一次性儘可能的接收連線(multi_accept)、是否使用負載均衡鎖(accept_mutex)、用負載均衡鎖後,延遲多少毫秒後再試圖處理新連線(accept_mutex_delay)等
對於ngx_epoll_module
來說,它關注的配置有監聽的事件個數,即傳入epoll_wait
的第三個引數(epoll_events)、初始分配的非同步I/O事件個數 worker_aio_requests
初始化工作
經過了以上步驟,ngx_init_cycle
接著遍歷ngx_modules
陣列,呼叫各模組的init_module
方法,根據配置項結構體中的成員做一些初始化工作。對於ngx_event_core_module
模組來說,該函式會根據其配置項結構體的內容初始化一些用於統計變數(與事件模組關係不是很大),而ngx_epoll_module
則沒有實現該方法。
接下來的工作,就可以開始分不同程序進行了。還記得nginx通過什麼樣的步驟來開啟的子程序的嗎?首先配置檔案中設定的worker_processes
多程序模式下,nginx會呼叫ngx_master_process_cycle
函式,即master
程序的工作迴圈。在master
程序的工作迴圈中,它會先設定一些訊號的捕捉函式,別忘了master
程序靠各種標誌位來選擇進行什麼樣的工作。接著就會呼叫ngx_start_worker_processes
函式啟動worker_processes
個子程序。在ngx_start_worker_processes
中,呼叫的核心函式是ngx_spawn_process
,其中fork
了程序之後,會呼叫ngx_worker_process_cycle
,即子程序的工作迴圈。
在子程序的工作迴圈中,第一件事就是呼叫ngx_worker_process_init
,它會呼叫所有模組的init_process
函式。對於事件模組中的ngx_event_core_module
模組來說,它對應的init_process
主要做了以下工作:
- 初始化定時器
- 呼叫配置中指定的事件驅動模組的
init
方法 - 若設定了
timer_resolution
,則定時呼叫ngx_timer_signal_handler
,控制時間精度 - 預分配連線池(
cycle->connections
) - 預分配讀事件(
cycle->read_events
) - 預分配寫事件(
cycle->write_events
) - 將對應的讀、寫事件放置到對應的
connections
連線中 - 初始化連線池,當前所有連線均為空閒連線,因此
cycle->free_connections
指向連線池首部 - 設定監聽套接字上讀事件的處理方法為
ngx_event_accept
,即建立新連線 - 將監聽套接字的讀事件新增到事件驅動模組中(若打開了
accept_mutex
鎖不會執行這一步)
處理事件
接著子程序正式開始工作,而其工作的核心函式就是ngx_process_events_and_timers
,若開啟了accept_mutex
並且當前程序的負載還沒有達到總連線的7/8,會讓程序去獲取accept_mutex
鎖,獲取到該鎖的程序可以處理新連線,沒有獲取到的只能呼叫epoll_wait
處理已有的連線的事件,不過過若在epoll_wait
上阻塞了accept_mutex_delay
毫秒之後,又可以再次嘗試去獲取鎖。呼叫epoll_wait
並處理事件的函式是ngx_process_events
,對應epoll模組中是ngx_epoll_process_events
。
獲取到了accept_mutex
鎖的程序會將就緒的事件放入對應的post事件佇列中延後處理,因為如果還是在ngx_process_events
函式中呼叫處理函式handler
,會導致程序長時間佔有accept_mutex
鎖得不到釋放,導致其他程序獲取不到該鎖。因此ngx_process_events
還負責將設定了NGX_POST_EVENTS
標誌位的事件加入post事件佇列,注意post事件佇列有兩個,一個是建立新連線的事件的佇列,另一個是其他普通的讀/寫事件的佇列。在ngx_process_events_and_timers
中先將post事件佇列中建立新連線的事件處理了,然後釋放鎖,接著再處理post事件佇列的其他事件。
小結
本小節梳理了一下從main
函式開始再到事件模組處理事件的大致過程,這對把握nginx整個框架很有幫助,畢竟http、mail這些模組其實都屬於事件消費模組,需要圍繞著事件模組展開。