ngx_timeofday,定时器管理
ngx獲取時間有兩個方法,一個是ngx_gettimeofday(),另一個是ngx_timeofday()。前者就是gettimeofday(),我們重點來分析一下后者。
ngx_timeofday()的定義:
#define ngx_timeofday() (ngx_time_t *) ngx_cached_time從名字上直觀看出這是一個緩存時間。
為何設置這個緩存時間呢?nginx對時間的操作很頻繁,在很多地方有獲取當前時間的需求,而實際上時間的獲取并不一定要非常精確。這樣,使用緩存,就能一定程度上大大降低調用gettimeofday()的時間消耗,而帶來的時間誤差在可接受范圍。
當然,有些場合是需要獲取精確時間的,那么nginx也提供了這樣的機制,我們在后面介紹。下面先來看看時間緩存的設計。
查找ngx_cached_time,在core/ngx_times.c中的?ngx_time_update(void) 函數進行更新。
關鍵代碼:
77 time_t sec;78 ngx_uint_t msec;79 ngx_time_t *tp;80 struct timeval tv;81 82 if (!ngx_trylock(&ngx_time_lock)) { //對全局變量的更新需要加鎖83 return;84 }85 86 ngx_gettimeofday(&tv); //獲取當前系統時間87 88 sec = tv.tv_sec;89 msec = tv.tv_usec / 1000;90 91 ngx_current_msec = (ngx_msec_t) sec * 1000 + msec;92 93 tp = &cached_time[slot]; // 獲取當前slot的時間94 95 if (tp->sec == sec) { //比較當前slot與剛剛計算出來的時間,若相同則返回。96 tp->msec = msec;97 ngx_unlock(&ngx_time_lock);98 return;99 } 100 101 if (slot == NGX_TIME_SLOTS - 1) { // slot數共64個,循環使用,也就是可以保存64個緩存時間。 102 slot = 0; 103 } else { 104 slot++; //使用下一個slot存當前時間 105 } 106 107 tp = &cached_time[slot]; //將當前時間放到新的slot中 108 109 tp->sec = sec; 110 tp->msec = msec;171 ngx_cached_time = tp; //將當前的slot時間賦給ngx_cached_time
可見,這個函數維護了64個緩存時間。而每次調用ngx_update_time更新時間后,ngx_timeofday都將訪問到最后緩存的時間。
那么,ngx_update_time在哪里執行呢?在事件觸發時。
以event/modules/ngx_epoll_module.c 為例:
556 static ngx_int_t 557 ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags) { …… 572 events = epoll_wait(ep, event_list, (int) nevents, timer); …… 576 if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) { 577 ngx_time_update(); 578 } …… }?可見,當epoll有一個新事件要處理時,就先更新一下時間。
而其中的if里面有三個條件:flags、NGX_UPDATE_TIME、ngx_event_timer_alarm,其中NGX_UPDATE_TIME為1,flags設置的值與后面介紹的timer_resolution相關,此處先省略。
那么我們先來看看ngx_event_timer_alarm的怎么回事。
38 sig_atomic_t ngx_event_timer_alarm;sig_atomic_t的一個原子操作的int。它是如何更新的呢?
561 void 562 ngx_timer_signal_handler(int signo) 563 { 564 ngx_event_timer_alarm = 1; 569 }在有定時器信號中斷時,該值就被設置為1。這個函數只有在timer_resolution被設置后才起作用,timer_resolution后面介紹。
而ngx_timer_signal_handler回調是如何注冊的呢?
629 if (ngx_timer_resolution && !(ngx_event_flags & NGX_USE_TIMER_EVENT)) { //設置timer_resolution時才生效。 630 struct sigaction sa; 631 struct itimerval itv; 632 633 ngx_memzero(&sa, sizeof(struct sigaction)); 634 sa.sa_handler = ngx_timer_signal_handler; //注冊信號中斷回調函數 635 sigemptyset(&sa.sa_mask); 636 637 if (sigaction(SIGALRM, &sa, NULL) == -1) { 638 ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, 639 "sigaction(SIGALRM) failed"); 640 return NGX_ERROR; 641 } 642 643 itv.it_interval.tv_sec = ngx_timer_resolution / 1000; 644 itv.it_interval.tv_usec = (ngx_timer_resolution % 1000) * 1000; 645 itv.it_value.tv_sec = ngx_timer_resolution / 1000; 646 itv.it_value.tv_usec = (ngx_timer_resolution % 1000 ) * 1000; 647 648 if (setitimer(ITIMER_REAL, &itv, NULL) == -1) { //使用setitimer系統調用設置系統定時器,當超時時,發出SIGALRM信號,喚醒中斷的epoll_wait,執行定時事件。 649 ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, 650 "setitimer() failed"); 651 } 652 }?也就是說timer_resolution設置后提供的一個時間中斷信號回調。
?
而維護64個slot的目的是什么呢?其實這是一個比較取巧的地方。nginx采用master/worker的方式工作,每個worker進程維護自己的timeofday,并且實際情況是對于時間緩存是讀多寫少的需求。那么有可能在多線程操作時會存在讀寫沖突,如一個讀操作讀到一半,一個寫操作覆蓋數據,這樣讀出來的數據就亂掉了。雖然nginx目前采用多進程單線程的模式,但是實現時可能有多線程,并且作者也考慮了多線程的應用場景。為了避免沖突,可以采用加鎖方式,但顯然比較低效。而作者的實現是使用64個slot,每次有更新需求時,會將最新時間寫到下一個slot(循環),這樣當前的讀操作就不會受到影響。而64個slot的設置使得沖突的概率微乎其微了。
?
當然,上面的處理存在一個弊端,要是nginx許久不來時間,那么緩存的時間將非常不準。并且有的場景需要緩存時間較為精確,于是nginx引入了timer_resolution的配置項。配置該項后,nginx將使用事件中斷機制來驅動定時器,而非使用紅黑樹中的最小時間最為epoll_wait的超時時間來驅動定時器。即此時定時器將定期被中斷而不再受限于紅黑樹中的最小時間。
if (ngx_timer_resolution) {timer = NGX_TIMER_INFINITE; //當timer_resolution設置時,定時器的超時時間設為-1,即不會超時。此時epoll_wait通過定時中斷信號來執行喚醒動作。flags = 0; } else {timer = ngx_event_find_timer(); //不設置時將從RB樹中查找最小時間作為喚醒時間flags = NGX_UPDATE_TIME; //flags設置為1,此處flags就是上面ngx_time_update判斷的三個條件之一。}
到這里,上面的三個參數可以理解了。"flags & NGX_UPDATE_TIME || ngx_event_timer_alarm",兩種情況:1.非設置timer_resolution時,flags=1,此時條件恒為真,因此每次process_event時都執行時間更新;2.設置了timer_resolution,此時flags=0,只有當ngx_event_timer_alarm=1即有時間信號中斷才執行時間更新(更新后會把ngx_event_timer_alarm置零),即process_event處理的就是時間中斷事件。這就是更新緩存時間的兩種機制了。?
?
這時,我們再來看看nginx的定時器,通過它可以控制nginx定時執行某些任務。而在epoll模型中,定時器發揮著至關重要作用,我們來看看nginx是如何利用定時器的。
epoll_wait阻塞時可以被三種時間喚醒:讀寫事件發生、等待時間超時和事件信號中斷。而后兩者的實現都與定時器密切相關。“定時器的執行其實就是在事件循環每執行一遍就檢查一遍定時器紅黑樹,找出所有超時的定時事件,一一執行之。事件循環不可能是一個無限空跑的循環,否則等同于死循環會吃掉大多數cpu的,因此事件循環里有一個阻塞點那就是epoll_wait。有了wait就解決了循環空跑的問題,但這個wait的時間是多久呢?1秒,2秒,1分,2分。。。wait時間過長會導致定時器不準確,wait時間過短,足夠短,就會退化為無等待循環。”[引自http://blog.csdn.net/marcky/article/details/7623335,這篇文章講得非常不錯]。于是,nginx引入的兩種定時功能,一是通過紅黑樹的最小超時時間,二是通過timer_resolution的定時信號中斷。
243 delta = ngx_current_msec; 244 245 (void) ngx_process_events(cycle, timer, flags); //這個就是處理epoll事件的函數。開頭的關于ngx_time_update就是在這個函數里面實現的 246 247 delta = ngx_current_msec - delta; // delta記錄了上面這個函數消耗的時間 248 249 ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, 250 "timer delta: %M", delta); 251 252 if (ngx_posted_accept_events) { 253 ngx_event_process_posted(cycle, &ngx_posted_accept_events); 254 } 255 256 if (ngx_accept_mutex_held) { 257 ngx_shmtx_unlock(&ngx_accept_mutex); 258 } 259 260 if (delta) { //當沒有epoll事件時,本次檢查RB樹的時間與上次間隔太短以至于認為是0,此時基本不會有新的超時事件產生,就無需再去檢查一遍了,這是nginx的一個很細微的性能優化。 261 ngx_event_expire_timers(); // nginx去檢查紅黑樹,找出所有的超時事件,一一執行。 262 }通過上面分析,nginx使用了兩種機制管理定時器,目的在于管理定時器,高效執行定時事件。
總結
以上是生活随笔為你收集整理的ngx_timeofday,定时器管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Valgrind ---内存调试,内存泄
- 下一篇: FFMPEG使用参数详解