事件庫之Libev(四)

##另外兩個重要的監控器 前面經過IO監控器將Libev的整個工做流程過了一遍。中間濾過了不少與其餘事件監控器相關的部分,可是總體思路以及很明晰了,只要針對其餘類型的watcher看下其初始化和註冊過程以及在ev_run中的安排便可。這裏咱們再分析另兩個經常使用的watcher ###1.分析定時器監控器 定時器在程序中能夠作固定週期tick操做,也能夠作一次性的定時操做。Libev中與定時器相似的還有個週期事件watcher。其本質都是同樣的,只是在時間的計算方法上略有不一樣,並有他本身的一個事件管理的堆。對於定時器事件,咱們按照以前說的順序從ev_init開始看起。html

####1.1定時器監控器的初始化 定時器初始化使用 ev_init(&timer_w,timer_action);,這個過程和以前的IO相似,主要就是設置基類的active、pending、priority以及觸發動做回調函數cb。linux

####1.2設置定時器監控器的觸發條件 經過 ev_timer_set(&timer_w,2,0); 能夠設置定時器在2秒鐘後被觸發。若是第三個參數不是0而是一個大於0的正整數n時,那麼在第一次觸發(2秒後),每隔n秒會再次觸發定時器事件。數組

其爲一個宏定義 do { ((ev_watcher_time *)(ev))->at = (after_); (ev)->repeat = (repeat_); } while (0) 也就是設置派生類定時器watcher的「at」爲觸發事件,以及重複條件「repeat」。數據結構

####1.3將定時器註冊到事件驅動器上框架

ev_timer_start(main_loop,&timer_w);會將定時器監控器註冊到事件驅動器上。其首先 ev_at (w) += mn_now; 獲得將來的時間,這樣放到時間管理的堆「timers」中做爲權重。而後經過以前說過的「ev_start」修改驅動器loop的狀態。這裏咱們又看到了動態大小的數組了。Libev的堆的內存管理也是經過這樣的關係的。具體這裏堆的實現,感興趣的能夠仔細看下實現。這裏的操做就是將這個時間權重放到堆中合適的位置。這裏堆單元的結構爲:函數

typedef struct {
    ev_tstamp at;
    WT w;
} ANHE;

其實質就是一個時刻at上掛一個放定時器watcher的list。當超時時會依次執行這些定時器watcher上的觸發回調函數。oop

####1.4定時器監控器的觸發 最後看下在一個事件驅動器循環中是如何處理定時器監控器的。這裏咱們依然拋開其餘的部分,只找定時器相關的看。在「/* calculate blocking time */」塊裏面,咱們看到計算blocking time的時候會先:學習

if (timercnt) {
    ev_tstamp to = ANHE_at (timers [HEAP0]) - mn_now;
    if (waittime > to) waittime = to;
}

若是有定時器,那麼就從定時器堆(一個最小堆)timers中取得堆頂上最小的一個時間。這樣就保證了在這個時間前能夠從backend_poll中出來。出來後執行timers_reify處理將pengding的定時器。ui

timers_reify中依次取最小堆的堆頂,若是其上的ANHE.at小於當前時間,表示該定時器watcher超時了,那麼將其壓入一個數組中,因爲在實際執行pendings二維數組上對應優先級上的watcher是從尾往頭方向的,所以這裏先用一個數組依時間前後次存下到一箇中間數組loop->rfeeds中。而後將其逆序調用ev_invoke_pending插入到pendings二維數組中。這樣在執行pending事件的觸發動做的時候就能夠保證,時間靠前的定時器優先執行。函數 feed_reversefeed_reverse_done就是將超時的定時器加入到loop->rfeeds暫存數組以及將暫存數組中的pending的watcher插入到pengdings數組的操做。把pending的watcher加入到pendings數組,後續的操做就和以前的同樣了。回依次執行相應的回調函數。線程

這個過程當中還判判定時器的 w->repeat 的值,若是不爲0,那麼會重置該定時器的時間,並將其壓入堆中正確的位置,這樣在指定的時間事後又會被執行。若是其爲0,那麼調用ev_timer_stop關閉該定時器。 其首先經過clear_pending置pendings數組中記錄的該watcher上的回調函數爲一個不執行任何動做的啞動做。

總結一下定時器就是在backend_poll以前經過定時器堆頂的超時時間,保證blocking的時間不超過最近的定時器時間,在backend_poll返回後,從定時器堆中取得超時的watcher放入到pendings二維數組中,從而在後續處理中能夠執行其上註冊的觸發動做。而後從定時器管理堆上刪除該定時器。最後調用和ev_start呼應的ev_stop修改驅動器loop的狀態,即loop->activecnt減小一。並將該watcher的active置零。

對於週期性的事件監控器是一樣的處理過程。只是將timers_reify換成了periodics_reify。其內部會對週期性事件監控器派生類的作相似定時器裏面是否repeat的判斷操做。判斷是否從新調整時間,或者是否重複等邏輯,這些看下代碼比較容易理解,這裏再也不贅述。·

###2.分析信號監控器 分析完了定時器的部分,再看下另外一個比較經常使用的信號事件的處理。Libev裏面的信號事件和Tornado.IOLoop是同樣的,經過一個pipe的IO事件來處理。直白的說就是註冊一個雙向的pipe文件對象,而後監控上面的讀事件,待相應的信號到來時,就往這個pipe中寫入一個值然他的讀端的讀事件觸發,這樣就能夠執行相應註冊的觸發動做回調函數了。

咱們仍是從初始化-》設置觸發條件-》註冊到驅動器-》觸發過程這樣的順序介紹。

####2.1信號監控器的初始化 ev_init(&signal_w,signal_action);這個函數和上面的同樣不用說了 ####2.2設置信號監控器的觸發條件 ev_signal_set(&signal_w,SIGINT);該函數設置了Libev收到SIGINT信號是觸發註冊的觸發動做回調函數。其操做和上面的同樣,就是設置了信號監控器私有的(ev)->signum爲標記。 ####2.3將信號監控器註冊到驅動器上 這裏首先介紹一個數據結構:

typedef struct
{
  EV_ATOMIC_T pending;
  EV_P;
  WL head;
} ANSIG;
static ANSIG signals [EV_NSIG - 1];

EV_ATOMIC_T pending;能夠認爲是一個原子對象,對他的讀寫是原子的。一個表示事件驅動器的loop,以及一個watcher的鏈表。

ev_signal_start中,經過signals數組存儲信號監控單元。該數組和anfds數組相似,只是他以信號值爲索引。這樣能夠立馬找到信號所在的位置。從 Linux 2.6.27之後,Kernel提供了signalfd來爲信號產生一個文件描述符從而能夠用文件複用機制epoll、select等來管理信號。Libev就是用這樣的方式來管理信號的。 這裏的代碼用宏控制了。其邏輯大致是這樣的

#if EV_USE_SIGNALFD
    res = invoke_signalfd
# if EV_USE_SIGNALFD
if  (res is not valied)
# endif
{
    use evpipe to instead
}

這個是框架。其具體的實現能夠參考使用signalfd和evpipe_init實現。其實質就是經過一個相似於管道的文件描述符fd,設置對該fd的讀事件監聽,當收到信號時經過signal註冊的回調函數往該fd裏面寫入,使其讀事件觸發,這樣經過backend_poll返回後就能夠處理ev_init爲該信號上註冊的觸發回調函數了。

在函數evpipe_init裏面也用了一個能夠學習的技巧,和上面的#if XXX if() #endif {} 同樣,處理了不支持eventfd的狀況。eventfd是Kernel 2.6.22之後才支持的系統調用,用來建立一個事件對象實現,進程(線程)間的等待/通知機制。他維護了一個能夠讀寫的文件描述符,可是隻能寫入8byte的內容。可是對於咱們的使用以及夠了,由於這裏主要是得到其可讀的狀態。對於不支持eventfd的狀況,則使用上面說過的,用系統的pipe調用產生的兩個文件描述符分別作讀寫對象,來完成。

####2.4信號事件監控器的觸發 在上面設置信號的pipe的IO事件是,根據使用的機制不一樣,其實現和觸發有點不一樣。對於signalfd。

ev_io_init (&sigfd_w, sigfdcb, sigfd, EV_READ);  /*  for signalfd */
ev_set_priority (&sigfd_w, EV_MAXPRI);
ev_io_start (EV_A_ &sigfd_w);

也就是註冊了sigfdcb函數。該函數:

ssize_t res = read (sigfd, si, sizeof (si));
for (sip = si; (char *)sip < (char *)si + res; ++sip)
    ev_feed_signal_event (EV_A_ sip->ssi_signo);

首先將pipe內容讀光,讓後續的能夠pengding在該fd上。而後對該signalfd上的全部信號弟阿勇ev_feed_signal_event吧每一個信號上的ANSIG->head上掛的watcher都用ev_feed_event加入到pendings二維數組中。這個過程和IO的徹底同樣。

而對於eventfd和pipe則是:

ev_init (&pipe_w, pipecb); 
  ev_set_priority (&pipe_w, EV_MAXPRI);      
  ev_io_set (&pipe_w, evpipe [0] < 0 ? evpipe [1] : evpipe [0], EV_READ);
  ev_io_start (EV_A_ &pipe_w);

pipe_w是驅動器自身的loop->pipe_w。併爲其設置了回調函數pipecb:

#if EV_USE_EVENTFD
    if (evpipe [0] < 0)
    {
    uint64_t counter;
    read (evpipe [1], &counter, sizeof (uint64_t));
    }
    else
#endif
    {
        char dummy[4];      
        read (evpipe [0], &dummy, sizeof (dummy));

    }
...
xxx
...
for (i = EV_NSIG - 1; i--; )
    if (expect_false (signals [i].pending))
        ev_feed_signal_event (EV_A_ i + 1);

這裏將上面的技巧#if XXX if() #endif {}拓展爲了#if XXX if() {} else #endif {} 。這裏和上面的操做實際上是同樣的。後續操做和signalfd裏面同樣,就是讀光pipe裏面的內容,而後依次將watcher加入到pendings數組中。

相關文章
相關標籤/搜索