nginx源碼分析——定時器

1. 概述

nginx實現了本身的定時器觸發機制,它與epoll等事件驅動模塊處理的網絡事件不一樣;在網絡事件中,網絡事件的觸發是由內核完成的,而定時器事件則徹底是由nginx自身實現的,它與內核徹底無關。node

定時器的使用及處理流程分兩步:nginx

第一步:初始化事件,設置事件的回調處理函數,而後經過ngx_add_timer()添加定時器事件。網絡

第二步:事件循環,等待超時回調。函數

對於使用者而言,只須要設置事件的回調函數,添加定時器事件,等待回調進行處理便可。ui

2. 相關接口與變量

  • 初始化定時器 
ngx_int_t  ngx_event_timer_init( ngx_log_t * log )
{
    ngx_rbtree_init( &ngx_event_timer_rbtree, &ngx_event_timer_sentinel, ngx_rbtree_insert_timer_value);
    return NGX_OK;
}

該接口主要是初始化用於存儲定時器的全局變量ngx_event_timer_rbtree。spa

  • 查找定時器
ngx_msec_t  ngx_event_find_timer( void )
{
    ngx_msec_int_t  timer;
    ngx_rbtree_node_t *node, *root, *sentinel;

    if( ngx_event_timer_rbtree.root == &ngx_event_timer_sentinel ) {
        return NGX_TIMER_INFINITE;
    }

    root = ngx_event_timer_rbtree.root;
    sentinel = ngx_event_timer_rbtree.sentinel;

    node = ngx_rbtree_min( root, sentinel );
    timer = (ngx_msec_int_t)(node->key - ngx_current_msec);

    return (ngx_msec_t)(timer > 0 ? timer : 0);
}

實質上是獲取最近一個即將超時的事件距離當前時間還有多少毫秒,若是已經超時則返回0,若是沒有定時器事件,則返回-1。code

  • 超時事件處理
void  ngx_event_expire_timers(void)
{
    ngx_event_t  * ev;
    ngx_rbtree_node_t  *node, *root, *sentinel;

    sentinel = ngx_event_timer_rbtree.sentinel;

    for( ; ; ) {
        root = ngx_event_timer_rbtree.root;
        if(root == sentinel) {
            return ;
        }

        node = ngx_rbtree_min(root, sentinel);

        if( (ngx_msec_int_t)(node->key - ngx_current_msec) > 0 ) {
            // 最先的一個定時器都未超時, 直接返回
            return ;
        }

        // 取出定時器事件
        ev = (ngx_event_t*)((char*)node - offsetof(ngx_event_t, timer));
        // 刪除定時器
        ngx_rbtree_delete(&ngx_event_timer_rbtree, &ev->timer);

        ev->timer_set = 0;
        // 設置超時標識
        ev->timedout = 1;
        // 回調處理
        ev->handler(ev);
    }
}
  • 添加定時器
static  ngx_inline  void  ngx_event_add_timer( ngx_event_t * ev, ngx_msec_t timer)
{
    ngx_msec_t  key;
    ngx_msec_int_t  diff;

    // 計算超時的時間
    key = ngx_current_msec + timer;

    if( ev->timer_set ) {
         // 若是該事件已經設置過定時器, 前後兩次設置定時器必須大於默認的最小時間
         diff = (ngx_msec_int_t)(key - ev->timer.key);
         if(ngx_abs(diff) < NGX_TIMER_LAZY_DELAY) {
             return;
         }
         // 大於默認的最小時間, 則刪除老的, 以新的爲準
         ngx_del_timer(ev);
    }

    ev->timer.key = key;
    // 添加到定時器 紅黑樹中
    ngx_rbtree_insert( &ngx_event_timer_rbtree, &ev->timer);
    // 置標識位
    ev->timer_set = 1;
}
  • 刪除定時器
static ngx_inline void  ngx_event_del_timer( ngx_event_t * ev )
{
    ngx_rbtree_delete( &ngx_event_timer_rbtree, &ev->timer);

    ev->timer_set = 0;
}
  •  全局變量
// 用來保存定時器
ngx_rbtree_t    ngx_event_timer_rbtree;
// 系統當前時間
volatile  ngx_msec_t  ngx_current_msec;

----------------------------------------------------------------------------------------------------------------------------------------------接口

 3. 全局變量當前時間的更新

從前面的源碼能夠看出,定時器會依賴於記錄當前時間的全局變量ngx_current_msec,添加定時器是須要根據當前時間計算最終的超時時間;事件循環處理時,也須要根據該變量判斷超時是否發生,那麼這個變量何時會進行更新呢?進程

1). 一般狀況下,全局變量ngx_current_msec的更新會依賴於ngx_process_events傳入的時間參數timer。對於使用epoll模塊的狀況,timer最終會做爲epoll_wait函數調用的參數。事件

事件循環的處理過程當中,會從定時器裏找到最近一個即將超時的時間,同時設置標誌須要進行時間的更新,epoll_wait函數返回後,調用ngx_time_update更新ngx_current_msec。當沒有設置定時器時,epoll_wait等待的時間爲-1,即阻塞等待直到有事件觸發才返回。

2). 在子進程個數大於1個的狀況下,能夠經過配置項accept_mutex_delay控制epoll_wait等待的時長。

3). 經過設置 timer_resolution指定更新的時間粒度

當配置了timer_resolution之後,nginx在啓動時,會經過setitimer告訴內核,每隔多長事件發送一次SIGALRM信號,同時設置捕獲信號的回調處理函數,在這個函數中設置標誌位告訴事件處理模塊須要進行事件的更新。須要注意的是,即使是在沒有設置定時器的狀況下,雖然經過ngx_process_events傳遞給epoll_wait的時間仍舊爲-1,可是因爲有信號的中斷,epoll_wait會返回EINTR,而不是阻塞等待直到有事件觸發。這樣就達到了按照指定的時間粒度,更新全局變量ngx_current_msec了。

//  Ngx_event.c
static void ngx_timer_signal_handler( int signo ) {
    // 置全局標誌位爲1  事件循環處理過程當中判斷該標識位決定是否要更新記錄當前時間的全局變量
    ngx_event_timer_alarm = 1;
}

static ngx_int_t  ngx_event_process_init( ngx_cycle_t * cycle )
{
    if( ccf->master && ccf->worker_processses > 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;
    }

    ...

    // 配置timer_resolution的狀況,設置信號捕獲函數, 而後調用setitimer定時更新
    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);
        if( sigaction(SIGALRM, &sa, NULL) == -1 ){
            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;

        if( setitimer(ITIMER_REAL, &itv, NULL) == -1 ){
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, "setitimer() failed");
        }
    }
    ....
}

void  ngx_process_events_and_timers( ngx_cycle_t * cycle )
{
    if( ngx_timer_resolution ) {
        // 設置有 timer_resolution 的狀況
        timer = NGX_TIMER_INFINITE;
        flags = 0;
    } else {
        // 計算到最近一個即將超時的時間, 沒有定時器 則timer等於-1
        timer = ngx_event_find_timer();
        // 須要更新時間的標誌
        flags = NGX_UPDATE_TIME;
    }

    if( ngx_use_accept_mutex ){
        if( ngx_accept_disabled > 0 ) {
            ngx_accept_disabled--;
        } else {
            if( ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
                return ;
            }
            if( ngx_accept_mutex_held ) {
                flags |= NGX_POST_EVENTS;
            } else {
                // 根據accept_mutex_delay 控制最小更新時間間隔( 默認配置值爲500ms )
                if( timer == NGX_TIMER_INFINITE || timer > ngx_accept_mutex_delay ) {
                    timer = ngx_accept_mutex_delay
                }
            }
        }
    }

    ....
    delta = ngx_current_msec;
    (void)ngx_process_events(cycle, timer, flags);
    delta = ngx_current_msec - delta;

    // 處理定時器事件
    if( delta ) {
        ngx_event_expire_timers();
    }
    ....
}

// Ngx_epoll_module.c
static ngx_int_t  ngx_epoll_process_events(ngx_cycle_t * cycle, ngx_msec_t timer, ngx_uint_t flags)
{
    // timer做爲epoll_wait的參數
    events = epoll_wait(ep, event_list, (int)nevents, timer);
    
    // 根據標誌位更新時間 或者 設置了timer_resolution指定須要更新時間
    if(flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
        ngx_time_update();
    }
    ....
}

----------------------------------------------------------------------------------------------------------------------------------------------

補充:setitimer的說明與epoll_wait返回值的說明

相關文章
相關標籤/搜索