nginx源碼分析(15)-模塊分析(1)

nginx的內部實現上用四個模塊上下文結構把所有模塊從實現上分開爲四種,不同的模塊上下文支持不同的模塊鉤子。如果拋開實現方式,僅僅從功能邏輯上來 區分,大致可以把所有模塊分爲三類,第一類是nginx的內核模塊;第二類是http模塊;第三類是mail模塊。內核模塊包含核心類模塊、event類 模塊和沒有模塊上下文結構的conf模塊;http模塊是所有的http類模塊;mail模塊是所有的mail類模塊。由此可見,按照功能分類基本上與按 照模塊上下文分類一致。

核心類模塊,即上下文爲ngx_core_module_t結構的模塊有以下幾個:

ngx_module_t ngx_core_module;
ngx_module_t ngx_errlog_module;
ngx_module_t ngx_events_module;
ngx_module_t ngx_http_module;
ngx_module_t ngx_mail_module;
ngx_module_t ngx_openssl_module;
ngx_module_t ngx_google_perftools_module;
ngx_module_t ngx_conf_module;(沒有模塊上下文,這裏把它歸類到核心類,便於後面分析)

ngx_events_module,ngx_http_module,ngx_mail_module 這三個模塊主要是實現了對配置文件中events {...},http {...},mail {...}/imap {...}配置域的解析鉤子,它們嵌套調用event類模塊、http類模塊和mail類模塊實現的指令解析鉤子解析{}中的具體指令,它們沒有實現模塊 上下文的create_conf和init_conf鉤子(也不需要,因爲它們只是一箇中轉而已,不做實際的事情)。

ngx_core_module 實現了配置文件中全局域的大部分指令的解析鉤子,它們也實現模塊上下文的create_conf鉤子和init_conf鉤子,創建和初始化 ngx_core_conf_t配置結構,這個結構可以通過cycle->conf_ctx[module.index]引用到。

ngx_errlog_module實現了配置文件中全局域的error_log指令的解析鉤子,沒有實現模塊上下文的create_conf和init_conf鉤子。

ngx_conf_module實現了全局域的include指令的解析鉤子,沒有實現模塊上下文的create_conf和init_conf鉤子。

ngx_openssl_module 實現了全局域的ssl_engine指令的解析鉤子,實現了模塊上下文的create_conf鉤子,創建ngx_openssl_conf_t配置結 構,這個結構可以通過cycle->conf_ctx[module.index]引用到,也實現了模塊的exit_master鉤子。

事件類模塊,即模塊上下文爲ngx_event_module_t結構的模塊有以下幾個:

ngx_module_t ngx_event_core_module;
ngx_module_t ngx_select_module;
ngx_module_t ngx_poll_module;
ngx_module_t ngx_eventport_module;
ngx_module_t ngx_aio_module;
ngx_module_t ngx_epoll_module;
ngx_module_t ngx_kqueue_module;
ngx_module_t ngx_devpoll_module;
ngx_module_t ngx_rtsig_module;

ngx_event_core_module 實現了配置文件events域的 worker_connections,connections,use,multi_accept,accept_mutex,accept_mutex_delay 和debug_connection指令的解析鉤子,也實現了模塊上下文的create_conf和init_conf鉤子,創建和初始化 ngx_event_conf_t配置結構,這個結構可以通過cycle->conf_ctx[module.index]引用到,另外還實現了模 塊的init_module和init_process鉤子。

ngx_select_module實現了模塊上下文的init_conf 初始化配置鉤子和actions.add、actions.del、actions.enable、actions.disable、 actions.process_events、actions.init、actions.done這幾個事件處理鉤子。

ngx_poll_module 實現了模塊上下文的init_conf初始化配置鉤子和actions.add、actions.del、actions.enable、 actions.disable、actions.process_events、actions.init、actions.done這幾個事件處理鉤 子。

ngx_eventport_module實現了配置文件events域的eventport_events指令的解析鉤子,實現了 模塊上下文的create_conf和init_conf鉤子,創建和初始化ngx_eventport_conf_t配置結構體,這個結構可以通過 cycle->conf_ctx[module.index]引用到,也實現了actions.add、actions.del、 actions.enable、actions.disable、actions.process_events、actions.init、 actions.done這幾個事件處理鉤子。

ngx_aio_module實現了actions.add、actions.del、actions.del_conn、actions.process_events、actions.init、actions.done這幾個事件處理鉤子。

ngx_epoll_module 實現了配置文件events域的epoll_events指令的解析鉤子,實現了模塊上下文的create_conf和init_conf鉤子,創建和初 始化ngx_epoll_conf_t配置結構,這個結構可以通過cycle->conf_ctx[module.index]引用到,也實現了 actions.add、actions.del、actions.enable、actions.disable、actions.add_conn、 actions.del_conn、actions.process_events、actions.init、actions.done這幾個事件處理 鉤子。

ngx_kqueue_module實現了配置文件events域的kqueue_changes和kqueue_events指 令的解析鉤子,實現了模塊上下文的create_conf和init_conf鉤子,創建和初始化ngx_kqueue_conf_t配置結構,這個結構 可以通過cycle->conf_ctx[module.index]引用到,也實現了actions.add、actions.del、 actions.enable、actions.disable、actions.process_changes、 actions.process_events、actions.init、actions.done這幾個事件處理鉤子。

ngx_devpoll_module 實現了配置文件events域的devpoll_changes和devpoll_events指令的解析鉤子,實現了模塊上下文的 create_conf和init_conf鉤子,創建和初始化ngx_devpoll_conf_t配置結構,這個結構可以通過 cycle->conf_ctx[module.index]引用到,也實現了actions.add、actions.del、 actions.enable、actions.disable、actions.process_events、actions.init、 actions.done這幾個事件處理鉤子。

ngx_rtsig_module實現了配置文件events域的rtsig_前綴的幾個 指令的解析鉤子,實現了模塊上下文的create_conf和init_conf鉤子,創建和初始化ngx_rtsig_conf_t配置結構,這個結構 可以通過cycle->conf_ctx[module.index]引用到,也實現了actions.add_conn、 actions.del_conn、actions.process_events、actions.init、actions.done這幾個事件處理 鉤子。

除了ngx_event_core_module之外,其餘的模塊是根據具體的操作系統和IO模型定製的事件處理機制,在支持epoll的linux系統上,nginx默認會選擇epoll模塊。

這 麼多的內核模塊,其實我們只要重點分析幾個:ngx_events_module、ngx_event_core_module和 ngx_epoll_module,因爲分析這兩個模塊就能夠掌握nginx最重要的事件處理機制,其餘的那些要麼很簡單,要麼不適用,簡單瞭解就可以 了。

nginx的事件處理機制也是一個很重要的內容,和進程模型一樣,我們也會採用代碼註釋的方式分析。nginx的事件處理機制可以分 爲構建和運行兩個階段。構建的過程從分析配置指令就開始了,直到worker工作進程調用 ngx_process_events_and_timers(cycle)循環處理之前。

在ngx_init_cycle解析配置文件的時候,當解析到“events {”的時候,將會調用ngx_events_module的events指令解析鉤子ngx_events_block。

static char *
ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    char                 *rv;
    void               ***ctx;
    ngx_uint_t            i;
    ngx_conf_t            pcf;
    ngx_event_module_t   *m;

    /* count the number of the event modules and set up their indices */

    // 對event類模塊點一下數
    ngx_event_max_module = 0;
    for (i = 0; ngx_modules[i]; i++) {
        if (ngx_modules[i]->type != NGX_EVENT_MODULE) {
            continue;
        }

        ngx_modules[i]->ctx_index = ngx_event_max_module++;
    }

    ctx = ngx_pcalloc(cf->pool, sizeof(void *));
    if (ctx == NULL) {
        return NGX_CONF_ERROR;
    }

    *ctx = ngx_pcalloc(cf->pool, ngx_event_max_module * sizeof(void *));
    if (*ctx == NULL) {
        return NGX_CONF_ERROR;
    }

    *(void **) conf = ctx;

    // 調用所有event類模塊的create_conf鉤子,創建配置結構,這些配置結構形成一個數組
    // 這個數組的指針最終會賦給cycle->conf_ctx,這是一個void ****指針,可以把所有
    // 模塊的配置結構有層次的保存下來
    for (i = 0; ngx_modules[i]; i++) {
        if (ngx_modules[i]->type != NGX_EVENT_MODULE) {
            continue;
        }

        m = ngx_modules[i]->ctx;

        if (m->create_conf) {
            (*ctx)[ngx_modules[i]->ctx_index] = m->create_conf(cf->cycle);
            if ((*ctx)[ngx_modules[i]->ctx_index] == NULL) {
                return NGX_CONF_ERROR;
            }
        }
    }

    pcf = *cf;
    cf->ctx = ctx;
    cf->module_type = NGX_EVENT_MODULE;
    cf->cmd_type = NGX_EVENT_CONF;

    // 解析“events {}”塊中的指令集
    rv = ngx_conf_parse(cf, NULL);

    *cf = pcf;

    if (rv != NGX_CONF_OK)
        return rv;

    // 調用每個event類模塊的init_conf鉤子,初始化配置結構,這些配置結構有些已經在解析指令的時候被
    // 填充,這裏主要是處理那些還沒有被指令填充,或者填充的有問題的那些
    for (i = 0; ngx_modules[i]; i++) {
        if (ngx_modules[i]->type != NGX_EVENT_MODULE) {
            continue;
        }

        m = ngx_modules[i]->ctx;

        if (m->init_conf) {
            rv = m->init_conf(cf->cycle, (*ctx)[ngx_modules[i]->ctx_index]);
            if (rv != NGX_CONF_OK) {
                return rv;
            }
        }
    }

    return NGX_CONF_OK;
}

在ngx_init_cycle的最後,將會調用到ngx_event_core_module模塊的init_module鉤子ngx_event_module_init,這個函數的處理過程如下:

1、若配置文件中沒有events section,函數出錯退出;

2、設置時間精度:ngx_timer_resolution = ccf->timer_resolution;

3、檢查一下worker_connections是否超過了進程允許的最大描述符字,超過的話寫一條waring日誌;

4、若master_process off,退出;if(ccf->master == 0) { return NGX_OK; }

5、if(ngx_accept_mutex_ptr) { return NGX_OK; }

6、創建共享內存,用於accept mutex和connection counter:
    /* cl should be equal or bigger than cache line size */

    cl = 128;

    size = cl            /* ngx_accept_mutex */
           + cl;         /* ngx_connection_counter */

    ...

    shm.size = size;
    shm.name.len = sizeof("nginx_shared_zone");
    shm.name.data = (u_char *) "nginx_shared_zone";
    shm.log = cycle->log;

    if (ngx_shm_alloc(&shm) != NGX_OK) {
        return NGX_ERROR;
    }

    shared = shm.addr;

    // 創建accept mutex
    ngx_accept_mutex_ptr = (ngx_atomic_t *) shared;

    // 系統支持原子數據則使用原子數據實現accept mutex,否則使用文件上鎖實現
    if (ngx_shmtx_create(&ngx_accept_mutex, shared, cycle->lock_file.data)
        != NGX_OK)
    {
        return NGX_ERROR;
    }

    // 創建connection counter
    ngx_connection_counter = (ngx_atomic_t *) (shared + 1 * cl);

    ...

    (void) ngx_atomic_cmp_set(ngx_connection_counter, 0, 1);

    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                   "counter: %p, %d",
                   ngx_connection_counter, *ngx_connection_counter);

到 目前爲止,一些配置已經被設置了,這些配置包括全局的cycle,不同模塊的配置結構ngx_<module name>_conf_t(cycle->conf_ctx可以引用到),cycle->connection_n被設置爲 worker_connections(最大連接數),ngx_event_conf_t.use被設置爲選中的(use)事件處理模塊的偏移 值:module->ctx_index。

在每個worker初始化的時候,會調用到ngx_event_core_module的init_process鉤子ngx_event_process_init,這個函數會設置cycle的大部分配置,處理過程如下:

1、設置accpet mutex相關的全局變量:
    if (ccf->master && ccf->worker_processes > 1 && ecf->accept_mutex) {
        ngx_use_accept_mutex = 1;
        ngx_accept_mutex_held = 0;
        ngx_accept_mutex_delay = ecf->accept_mutex_delay;

    } else {
        ngx_use_accept_mutex = 0;
    }   

2、調用ngx_event_timer_init(cycle->log),初始化事件的計時機制,這裏用到了紅黑樹,詳細分析留待以後;

3、調用被use的事件處理機制模塊的actions.init鉤子,linux上使用的是ngx_epoll_module,其actions.init鉤子爲ngx_epoll_init:
static ngx_int_t
ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer)
{
    ngx_epoll_conf_t  *epcf;

    epcf = ngx_event_get_conf(cycle->conf_ctx, ngx_epoll_module);

    // 創建一個epoll句柄,指定監聽數目爲cycle->connection_n / 2
    if (ep == -1) {
        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;
        }  
    }  

    // event_list是被監聽的句柄的列表,默認最大爲512個句柄
    if (nevents < epcf->events) {
        if (event_list) {
            ngx_free(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_io;

    // 可以便捷的訪問到事件處理模塊的各種actions鉤子
    ngx_event_actions = ngx_epoll_module_ctx.actions;

#if (NGX_HAVE_CLEAR_EVENT)
    ngx_event_flags = NGX_USE_CLEAR_EVENT
#else
    ngx_event_flags = NGX_USE_LEVEL_EVENT
#endif
                      |NGX_USE_GREEDY_EVENT
                      |NGX_USE_EPOLL_EVENT;

    return NGX_OK;
}

4、計時器創建和初始化:
    if (ngx_timer_resolution && !(ngx_event_flags & NGX_USE_TIMER_EVENT)) {
        struct sigaction  sa; 
        struct itimerval  itv;

        ngx_memzero(&sa, sizeof(struct sigaction));
        sa.sa_handler = ngx_timer_signal_handler;
        sigemptyset(&sa.sa_mask);

        // 設置SIGALRM的信號處理函數,這個信號初始函數調用ngx_time_update(0, 0)(調用gettimeofday)修改時間cache
        if (sigaction(SIGALRM, &sa, NULL) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "sigaction(SIGALRM) failed");
            return NGX_ERROR;
        }   

        itv.it_interval.tv_sec = ngx_timer_resolution / 1000;
        itv.it_interval.tv_usec = (ngx_timer_resolution % 1000) * 1000;
        itv.it_value.tv_sec = ngx_timer_resolution / 1000;
        itv.it_value.tv_usec = (ngx_timer_resolution % 1000 ) * 1000;

        // 設置定時器,間隔一定時間發送SIGALRM信號,可以參考settimer系統調用的手冊頁
        if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "setitimer() failed");
        }   
    }   

5、對poll,/dev/poll,rtsig三個事件處理模塊的特殊處理,不深究:
if (ngx_event_flags & NGX_USE_FD_EVENT) {
        struct rlimit  rlmt;

        if (getrlimit(RLIMIT_NOFILE, &rlmt) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "getrlimit(RLIMIT_NOFILE) failed");
            return NGX_ERROR;
        }   

        cycle->files_n = (ngx_uint_t) rlmt.rlim_cur;

        cycle->files = ngx_calloc(sizeof(ngx_connection_t *) * cycle->files_n,
                                  cycle->log);
        if (cycle->files == NULL) {
            return NGX_ERROR;
        }   
    }   

6、創建cycle->connection_n個ngx_connection_t結構變量,讓cycle->connections指向它;

7、分別創建cycle->connection_n個讀和寫事件結構變量(ngx_event_t)並初始化其狀態,讓cycle->read_events和cycle->write_events指向它們;

8、把cycle->connections中各個元素串成一個鏈表(用ngx_connection_t.data指向下一個),並讓cycle->free_connections指向頭部第一個元素,即:
cycle->free_connections = &cycle->connections[0],cycle->free_connection_n = cycle->connection_n,這樣就構建了一條空閒的連接鏈表;

9、爲cycle->listening中的每個監聽套接字設置初始的連接以及事件資源。

至 此,事件處理機制構建過程完成了,然而理解事件處理機制上面的分析依然不夠,下一篇會從另外一個角度,從分析事件處理機制中涉及的幾個重要的數據結構入 手:ngx_listening_t、ngx_connection_t、ngx_event_t。這些數據結構構成了nginx事件處理機制的基礎。

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