##另外兩個重要的監控器 前面經過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_reverse
和 feed_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數組中。