Nginx:事件模塊


參考資料<深入理解Nginx>

 

根據不同的系統內核,Nginx會使用不同的事件驅動機制,本次描述的場景是使用epoll來驅動事件的處理。

 

 

epoll的使用方法

1.int epoll_create(int size);

epoll_create返回一個句柄,之后epoll的使用將依靠這個句柄來標識。參數size只是告訴epoll所要處理的大致事件數目,一些內核版本的實現中,這個參數沒有任何意義。

2.int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);

epoll_ctl向epoll對象中添加、修改或者刪除感興趣的事件,返回0表示成功,否則返回-1。

參數epfd是epoll_create方法返回的句柄,而op參數的意義如下表

參數fd是待檢測的連接套接字,第四個參數是在告訴epoll對什么樣的事件感興趣,它使用的epoll_event結構體定義如下

struct epoll_event {
    _uint32_t events;
    epoll_data_t data;
};

   events取值如下表

  而data成員是一個epoll_data聯合

typedef union epoll_data {
    void *ptr;
    int fd;
    uint32_t u32;
    uint64_t u64;
} epoll_data_t;

這個data成員還與具體的使用方式相關。例如,ngx_epoll_module模塊使用了給ptr成員,作為指向ngx_connection_t連接的指針。

3.int epoll_wait(int fd,struct epoll_event *events,int maxevents,int timeout);

   第一個參數epfd是epoll的描述符。第二個參數events則是分配好的epoll_event結構體數組,epoll將會把發生的事件賦值到events數組中,

   第三個參數表示本次可以返回的最大事件數目,第四個參數表示在沒有檢測到時間發生時最多等待的時間。

 

ngx_epoll_module模塊

該模塊使用如下的上下文結構

ngx_event_module_t  ngx_epoll_module_ctx = {
    &epoll_name,
    ngx_epoll_create_conf,               /* create configuration */
    ngx_epoll_init_conf,                 /* init configuration */

    {
        ngx_epoll_add_event,             /* add an event */
        ngx_epoll_del_event,             /* delete an event */
        ngx_epoll_add_event,             /* enable an event */
        ngx_epoll_del_event,             /* disable an event */
        ngx_epoll_add_connection,        /* add an connection */
        ngx_epoll_del_connection,        /* delete an connection */
        NULL,                            /* process the changes */
        ngx_epoll_process_events,        /* process the events */
        ngx_epoll_init,                  /* init the events */
        ngx_epoll_done,                  /* done the events */
    }
};

在Nginx的啟動過程中。ngx_epoll_init方法將會被調用,它主要做了兩件事情:

1.調用epoll_create方法創建epoll對象。

2.創建event_list數組,用於進行epoll_wait調用時傳遞內核態的事件。

 

ngx_epoll_add_event通過調用epoll_ctl向epoll中添加或者刪除事件。可以通過這個函數的部分源碼來了解一下該過程

 1 static ngx_int_t
 2 ngx_epoll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
 3 {
 4     int                  op;
 5     uint32_t             events, prev;
 6     ngx_event_t         *e;
 7     ngx_connection_t    *c;
 8     struct epoll_event   ee;
 9 
10     //每個事件的data成員都存放着其對應的ngx_connection_t連接
11     c = ev->data;
12 
13     //確定當前時間是讀事件還是寫事件
14     events = (uint32_t) event;
15 
16     ...
17     //根據active標志位確定是否為活躍事件,以決定到底是修改還是添加事件
18     if (e->active) {
19         op = EPOLL_CTL_MOD;
20         events |= prev;
21 
22     } else {
23         op = EPOLL_CTL_ADD;
24     }
25 
26     ee.events = events | (uint32_t) flags;
27     ee.data.ptr = (void *) ((uintptr_t) c | ev->instance);
28 
29     //調用epoll_ctl方法向epoll中添加事件
30     if (epoll_ctl(ep, op, c->fd, &ee) == -1) {
31         ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
32                       "epoll_ctl(%d, %d) failed", op, c->fd);
33         return NGX_ERROR;
34     }
35 
36     //標識為活躍事件
37     ev->active = 1;
38 
39     return NGX_OK;
40 }
View Code

同理,ngx_epoll_del_event方法也通過類似的方式刪除事件。

對於ngx_epoll_add_connection和ngx_epoll_del_connection方法,也是調用epoll_ctl進行處理,只是每一個連接都對應讀/寫事件。

 

ngx_epoll_process_events方法則調用epoll_wait來獲取事件並且處理事件,下面是該函數的部分源碼

 1 static ngx_int_t
 2 ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
 3 {
 4     int                       events;
 5     uint32_t               revents;
 6     ngx_int_t             instance, i;
 7     ngx_event_t         *rev, *wev, **queue;
 8     ngx_connection_t  *c;
 9 
10 
11     //一開始就是等待事件,最長等待時間為timer
12     events = epoll_wait(ep, event_list, (int) nevents, timer);
13     
14     ...
15     //循環開始處理收到的所有事件
16     for (i = 0; i < events; i++) {
17         //ptr成員指向的是ngx_connection_t,但最后一位有特殊含義,需要將它屏蔽掉
18         c = event_list[i].data.ptr;
19         instance = (uintptr_t) c & 1;
20         c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);
21         
22         //取出讀事件
23         rev = c->read;
24         ...
25         //取得發生一個事件
26         revents = event_list[i].events;
27 
28         ...
29         //該事件是一個讀事件而且是活躍的
30         if ((revents & EPOLLIN) && rev->active) {
31 
32             ...
33             //如果設置了NGX_POST_EVENTS標識,事件放入到相應的隊列中
34             if (flags & NGX_POST_EVENTS) {
35                 queue = (ngx_event_t **) (rev->accept ?
36                                &ngx_posted_accept_events : &ngx_posted_events);
37 
38                 ngx_locked_post_event(rev, queue);
39             //否則立刻處理
40             } else {
41                 rev->handler(rev);
42             }
43         }
44       
45         //獲取寫事件
46         wev = c->write;
47 
48         if ((revents & EPOLLOUT) && wev->active) {
49             
50             ...
51             if (flags & NGX_POST_EVENTS) {
52                 ngx_locked_post_event(wev, &ngx_posted_events);
53 
54             } else {
55                 wev->handler(wev);
56             }
57         }
58     }
59     ...
60     return NGX_OK;
61 }
View Code

 

 

ngx_process_events_and_timers流程

在Nginx中,每個worker進程都在ngx_worker_process_cycle方法中循環處理事件。

處理分發事件實際上就是調用的ngx_process_events_and_timers方法,循環調用該函數就是在處理所有的事件,該方法的核心的操作主要有以下3個:

1.調用所使用的事件驅動模塊實現的process_events方法,處理網絡事件(調用ngx_epoll_process_events方法)。

2.處理兩個post事件隊列中的事件(調用ngx_event_process_posted方法)。

3.處理定時器時間(調用ngx_event_expire_timers方法)

下圖展示了該方法的流程,結合前面的進行理解

事實上,在調用上面循環的方法之前ngx_event_core_module模塊會做一些初始化工作:

1.預分配cycle->connection數組充當連接池

2.預分配所有寫事件到cycle->read_events數組

3.預分配所有讀事件到cycle->write_events數組

(Nginx中連接池跟讀寫事件都是這個階段預先分配好的,其大小跟配置項有關)

4.為所有ngx_listening_t監聽對象中的connectin成員分配連接,同時對監聽端口的讀事件設置處理方法為ngx_event_accept(最后有這個方法的介紹)

5.將監聽對象連接的讀事件添加到事件驅動模塊中,這樣,epoll等事件模塊就開始檢測監聽服務,並開始向用戶提供服務了。 

 

建立新連接

處理新連接事件的回調函數是ngx_event_accept,其原型如下

void ngx_event_accept(ngx_event_t *ev);

下圖展示了該方法的流程


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM