libev 源碼解析


一  libev簡介

  libev是一個輕量級的事件通知庫,具備支持多種事件通知能力,通過對libev的源碼的閱讀,可以清楚了解事件通知實現內部機制。

二 核心數據結構

 在libev中關鍵的數據結構是,loop結構體,該結構體定義的字段較多,但是主要核心的可以分為兩大類

ev_loop結構體(loop為ev_loop結構的全局變量)的字段定義在ev_vars.h頭文件中,然后在ev.c中通過include的方式導入

 

1.各類事件的watcher集合

 loop中有支持很多類型的事件,如下

ev_io                 // IO可讀可寫
ev_stat               // 文件屬性變化 ev_signal // 信號處理 ev_timer // 相對定時器 ev_periodic // 絕對定時器 ev_child // 子進程狀態變化 ev_fork // fork事件 ev_cleanup // event loop退出觸發事件 ev_idle // event loop空閑觸發事件 ev_embed // 嵌入另一個后台循環 ev_prepare // event loop之前事件 ev_check // event loop之后事件 ev_async // 線程間異步事件

這些事件的監控管理都對應一種類型的watcher數組,比如

    (loop)->anfds :維護所有fd事件

    (loop)->timers :維護所有的定時器

    (loop)->periodics:周期性事件

     (loop)->prepares:該事件是loop啟動之間就會執行的事件

等,每類事件都能在loop結構中找到對應的數組來維護對應的watchers。

2.2 watcher結構

    對於不同類型的事件的watcher,采用繼承的方式來實現各個類型的watcher,libev是使用c的宏定義來實現繼承(宏的奇技淫巧在libev中隨處可見,這也導致libev看起來比較晦澀)

 這個是公共watcher得到結構,是會被所有的子watcher所共享。

  active:表示在loop中對應的watcher數組中的下標

  pending & EV_DECL_PRIORITY:分別用來記錄在全局loop->pendings中列 & 行下標(loop->pendings下文會介紹)

  EV_CB_DECLARE (type) :回調函數入口

 

 比如io事件的watcher如上定義,類似的各種watcher都采取此類方式來生成(這樣的好處是可以減少代碼的重復度,降低了代碼維護的成本,但是可讀性方面也相應降低)

2.全局觸發事件集合loop->pendings

loop->pendings記錄管理當前已經發生等待調用回調函數的watcher集合,libev檢查到事件發生后將對應的watcher加入到loop->pendings

 2.1 數據結構:

  *Watcher[N][M]:二位數組用來維護一輪循環下來,需要觸發回調函數的watcher

  其中N:是每一類watcher的優先級

 另外還有loop->pendingcnt結構,也是一個二維數組,用來維護pending中每一行最大watcher下標。

 以下是事件函數回調過程的代碼,

本質上就是個遍歷loop->pendings挨個調用watcher的注冊的回調函數,可以看到按照優先級從高(小)到低(大),對於同一優先級watcher按照下標從大到小的方式來調用callBack函數。

 值得注意的,就是pendingcnt結構,該結構維護當前每一行watcher數組當前的最大下標。

三 事件觸發之io事件

  該節將會以IO事件在EPOLL平台的觸發整個流程來闡述libev是如何來實現事件觸發回調的整個過程。在介紹IO事件觸發之前需要介紹一下相關的數據結構

       ANFD:用來記錄對一個fd的所有監聽事件,每一個監聽事件使用鏈表結構進行組織。
  

  anfds:是ANFD*類型的數組,用來管理每一個fd的監聽的事件

  changfds:是int*類型數組,每個元素記錄當前發生更改的fd,比如加入新的監聽fd,或者fd的監聽事件發送修改。

在每次循環之前,都會對changefds和anfds的結構中對應的fd事件列表的所有event進行 | 操作,得到當前整個fd當前的監聽事件和ANFD.events進行對比,如果沒有修改將不會該表epoll的fd的監聽事件,這樣做的好處,避免了無效的修改,保證了所有對epoll的修改都是必須的,畢竟頻繁對epoll進行修改代價還是挺大的。

    好了,下面正式分析IO事件是如何觸發的,當一個fd監聽事件加入時

 step1:調用ev_io_start將watcher加入到anfds中,並將修改記錄在changfds中

  steps2: 調用fd_reify,通過比對changfds & anfds確定是否需要加入epoll事件,此時顯然是需要的

    

 

    核心代碼如下,第一處:暫時保存fd的events,第二處:通過遍歷watcher鏈表計算新的events,比較前后是否發送變化,第三處:如果發生變化將修改epoll的監聽事件

    自此完成監聽事件的添加。

  step3:調用backend_poll函數得到當前監聽已經發生的事件,

    #得到事件的fd & 已經發生的events,從anfds中獲取對應的ANFD

        #遍歷ANFD中的watcher鏈表,比對監聽事件和已經發生的監聽事件,如果符合將該watcher加入到loop->pendings,修改watcher中的pending變量標記在loop->pendings中的數組下標

  step4:遍歷循環loop->pendings結構,挨個調用回調函數,從而完成事件觸發的一個完整過程

四 事件觸發之定時器事件

       定時器采用小根堆的方式來維護所有的timer,libev整個過程是采用loop循環的方式,周期性的檢測是否有事件發生

在loop中會獲取當前堆頂的timer(最近要發生的)以及其他信息來計算當前可以sleep多長時間,從而可以保證進程在休眠的這段時間也不會有事件發生而沒有及時通知。

五 ev_run函數解析

 ev_run將整個libev的各類事件通知流程串起。整個過程就是個大循環。

    過程大致分為

           1.檢測是否有fork事件,如果有進行fork事件的回調函數

           2.在loop之前調用prepare事件的回調函數。

           3.檢測fd的監聽事件是否發生變化,是否需要修改epoll的監聽事件

           4.計算需要休眠的事件

     #根據定時器 & 周期任務 & timeout_blocktime(超時事件收集間隔事件) & io_blocktime(io事件收集間隔事件) 等信息計算此次循環需要sleep的時間

           5.如果需要休眠則進行休眠

           6.進程從休眠態喚起后,從epoll(pool,kqueue)中獲取發生的事件,將對應的watcher加入到 loop->pendings中

           7.將定時時間到了的定時器,加入loop->pendings中

          8.收集周期任務,加入loop->pendings中

          9.收集空閑事件加入loop->pendings中

   10.依此對loop->pendings中的watcher中注冊的回調函數

   自此整個loop完成,總體來說就是在整個loop中檢測所有的監聽的事件是否發生,然后依次對發生的事件,調用注冊的回調函數。

六 源碼文件結構

  #個平台網絡編程接口,不同平台使用不同的文件,從而支持多平台

   ev_pool.c

     ev_port.c

     ev_kqueue.c

     ev_select.c

   ev_vars.h:定義ev_loop數據結構,使用宏定義的方式進行定義

   ev_warp.c:,使用宏定義的方式封裝全局變量loop中字段的訪問

   ev.c:整個libev的核心部分,實現了整個libev的事件通知的大部分業務邏輯

七 總結

  libev從整個設計來看還是比較精巧的,大體上將整個事件通知機制划分為兩個階段

  #事件發生檢測:

    各個事件檢測過程實現不太一樣(IO事件,定時器,周期性任務等各不一樣),將檢測到發生的事件加入loop->pendings中

  #事件回調觸發

    對loop->pendings的事件,遍歷依次觸發回調函數

  整個libev可能作者出於對代碼復用減少重復代碼的原因,大量使用宏定義,甚至用宏定義實現了簡單的繼承關系,這也使得整個項目代碼看起來比較晦澀難懂。

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM