Nginx源碼分析-Epoll模塊

Linux平臺上,Nginx使用epoll完成事件驅動,實現高併發;本文將不對epoll本身進行介紹(網上一堆一堆的文章介紹epoll的原理及使用方法,甚至源碼分析等),僅看一下Nginx是如何使用epoll的。

Nginx在epoll模塊中定義了好幾個函數,這些函數基本都是作爲回調註冊到事件抽象層的對應接口上,從而實現了事件驅動的具體化,我們看如下的一段代碼:

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


這段代碼就是epoll的相關函數註冊到事件抽象層,這裏所謂的事件抽象層在前面的博文中有提過,就是Nginx爲了方便支持和開發具體的I/O模型,從而實現的一層抽象。代碼後面的註釋將功能說明得很詳細了,本文就只重點關注ngx_epoll_init和ngx_epoll_process_events兩個函數,其他幾個函數就暫且忽略了。

ngx_epoll_init主要是完成epoll的相關初始化工作,代碼分析如下:

static ngx_int_t
ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer)
{
    ngx_epoll_conf_t  *epcf;
         /*取得epoll模塊的配置結構*/
    epcf = ngx_event_get_conf(cycle->conf_ctx, ngx_epoll_module);
         /*ep是epoll模塊定義的一個全局變量,初始化爲-1*/
    if (ep == -1) {
         /*創一個epoll對象,容量爲總連接數的一半*/
        ep = epoll_create(cycle->connection_n / 2);
        if (ep == -1) {
            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
                          "epoll_create() failed");
            return NGX_ERROR;
        }
    }
         /*nevents也是epoll模塊定義的一個全局變量,初始化爲0*/
    if (nevents events) {
        if (event_list) {
            ngx_free(event_list);
        }
 
                 /*event_list存儲產生事件的數組*/
        event_list = ngx_alloc(sizeof(struct epoll_event) * epcf->events,
                               cycle->log);
        if (event_list == NULL) {
            return NGX_ERROR;
        }
    }
    nevents = epcf->events;
         /*初始化全局變量ngx_io, ngx_os_is定義爲:
                 ngx_os_io_t ngx_os_io = {
                 ngx_unix_recv,
                 ngx_readv_chain,
                 ngx_udp_unix_recv,
                 ngx_unix_send,
                 ngx_writev_chain,
                 0
                 };(位於src/os/unix/ngx_posix_init.c)
         */
    ngx_io = ngx_os_io;
         /*這裏就是將epoll的具體接口函數註冊到事件抽象層接口ngx_event_actions上。
         具體是上文提到的ngx_epoll_module_ctx中封裝的如下幾個函數
        ngx_epoll_add_event,
        ngx_epoll_del_event,
        ngx_epoll_add_event,
        ngx_epoll_del_event,
        ngx_epoll_add_connection,
        ngx_epoll_del_connection,
        ngx_epoll_process_events,
        ngx_epoll_init,
        ngx_epoll_done,
         */
    ngx_event_actions = ngx_epoll_module_ctx.actions;
#if (NGX_HAVE_CLEAR_EVENT)
         /*epoll將添加這個標誌,主要爲了實現邊緣觸發*/
    ngx_event_flags = NGX_USE_CLEAR_EVENT
#else
         /*水平觸發*/
    ngx_event_flags = NGX_USE_LEVEL_EVENT
#endif
                      |NGX_USE_GREEDY_EVENT /*io的時候,直到EAGAIN爲止*/
                      |NGX_USE_EPOLL_EVENT; /*epoll標誌*/
    return NGX_OK;
}


epoll初始化工作沒有想象中的複雜,和我們平時使用epoll都一樣,下面看ngx_epoll_process_events,這個函數主要用來完成事件的等待並處理。

static ngx_int_t
ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
{
    int                events;
    uint32_t           revents;
    ngx_int_t          instance, i;
    ngx_uint_t         level;
    ngx_err_t          err;
    ngx_log_t         *log;
    ngx_event_t       *rev, *wev, **queue;
    ngx_connection_t  *c;
         /*一開始就是等待事件,最長等待時間爲timer;nginx爲事件
         專門用紅黑樹維護了一個計時器。後續對這個timer單獨分析。
         */
    events = epoll_wait(ep, event_list, (int) nevents, timer);
    if (events == -1) {
        err = ngx_errno;
    } else {
        err = 0;
    }
    if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
        /*執行一次時間更新, nginx將時間緩存到了一組全局變量中,方便程序高效的獲取事件。*/
        ngx_time_update();
    }
         /*處理wait錯誤*/
    if (err) {
        if (err == NGX_EINTR) {
            if (ngx_event_timer_alarm) {
                ngx_event_timer_alarm = 0;
                return NGX_OK;
            }
            level = NGX_LOG_INFO;
        } else {
            level = NGX_LOG_ALERT;
        }
        ngx_log_error(level, cycle->log, err, "epoll_wait() failed");
        return NGX_ERROR;
    }
         /*wait返回事件數0,可能是timeout返回,也可能是非timeout返回;非timeout返回則是error*/
    if (events == 0) {
        if (timer != NGX_TIMER_INFINITE) {
            return NGX_OK;
        }
        ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
                      "epoll_wait() returned no events without timeout");
        return NGX_ERROR;
    }
    log = cycle->log;
         /*for循環開始處理收到的所有事件*/
    for (i = 0; i read;
                 。。。。。。。。。。。。。
 
                 /*取得發生一個事件*/
        revents = event_list[i].events;
 
                 /*記錄wait的錯誤返回狀態*/
        if (revents & (EPOLLERR|EPOLLHUP)) {
            ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0,
                           "epoll_wait() error on fd:%d ev:%04XD",
                           c->fd, revents);
        }
        if ((revents & (EPOLLERR|EPOLLHUP))
             && (revents & (EPOLLIN|EPOLLOUT)) == 0)
        {
            /*
             * if the error events were returned without EPOLLIN or EPOLLOUT,
             * then add these flags to handle the events at least in one
             * active handler
             */
            revents |= EPOLLIN|EPOLLOUT;
        }
                 /*該事件是一個讀事件,並該連接上註冊的讀事件是active的*/
        if ((revents & EPOLLIN) && rev->active) {
            if ((flags & NGX_POST_THREAD_EVENTS) && !rev->accept) {
                rev->posted_ready = 1;
            } else {
                rev->ready = 1;
            }
 
                          /*事件放入相應的隊列中;關於此處的先入隊再處理,在前面的文章中已經介紹過了。*/
            if (flags & NGX_POST_EVENTS) {
                queue = (ngx_event_t **) (rev->accept ?
                               &ngx_posted_accept_events : &ngx_posted_events);
                ngx_locked_post_event(rev, queue); /*入隊*/
            } else {
                rev->handler(rev);
            }
        }
        wev = c->write;
                 /*發生的是一個寫事件,和讀事件完全一樣的邏輯過程*/
        if ((revents & EPOLLOUT) && wev->active) {
            if (flags & NGX_POST_THREAD_EVENTS) {
                wev->posted_ready = 1;
            } else {
                wev->ready = 1;
            }
                          /*先入隊再處理*/
            if (flags & NGX_POST_EVENTS) {
                ngx_locked_post_event(wev, &ngx_posted_events);
            } else {
                wev->handler(wev);
            }
        }
    }
    return NGX_OK;
}


本文將關注的兩個epoll函數也就這麼一點代碼了,但整個epoll還有添加事件和刪除事件等的相關函數,代碼都很簡單,本文就不做具體的分析了。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章