libev源码解析——总览
? ? ? ? libev是個非常優秀的基于事件的循環庫,很多開源軟件,比如nodejs就是使用其實現基礎功能。本系列將對該庫進行源碼分析。(轉載請指明出于breaksoftware的csdn博客)
? ? ? ? 不知道是被墻了還是網站不再維護,它的官網(http://libev.schmorp.de/)在國內已經沒法訪問了。但是我們仍然可以從github上下載其源碼(https://github.com/enki/libev)。
使用樣例
? ? ? ? libev支持相對時間定時器、絕對時間定時器、文件狀態監控和信號監控等功能。我們可以在它基礎上,通過少量的代碼實現穩健完善的功能。
? ? ? ? 我們先看一段實現定時器功能的代碼
#include <ev.h>
#include <stdio.h>ev_timer timeout_watcher;static void
timeout_cb(EV_P_ ev_timer *w, int revents)
{puts("timeout");ev_break(EV_A_ EVBREAK_ONE);
}int main(void)
{struct ev_loop *loop = EV_DEFAULT;ev_timer_init(&timeout_watcher, timeout_cb, 5.5, 0);ev_timer_start(loop, &timeout_watcher);ev_run(loop, 0);return 0;
}
? ? ? ? 這段代碼的結構非常簡單。首先我們要定義一個名為timeout_cb的回調函數用于響應定時器。然后定義一個ev_timer結構(監視器),它通過ev_timer_init進行初始化。初始化的參數包含之前定義的響應函數指針和迭代器超時時間。ev_timer準備好后,通過ev_timer_start將其和一個ev_loop進行綁定。最后使用ev_run方法運行起來這個ev_loop指針,從而實現一個完整的定時器功能。
? ? ? ? 可見使用libev庫非常方便。其實我們之后見到的其他用法和上面步驟是類似的,即:
- 初始化ev_loop。
- 定義監視器。
- 定義回調函數。
- 監視器和回調函數關聯。
- 監視器和ev_loop關聯。
- ev_run將ev_loop運行起來。
? ? ? ? 假如上面代碼是個框架使用的雛形,那么如果讓我們去設計這樣的框架,該如何設計呢?
模型設計
? ? ? ? 首先我們需要考慮到的是使用sleep還是使用事件模型去實現邏輯等待。
? ? ? ? 如果使用sleep方法,那么我們就要在喚醒后去檢測各個事件,比如要檢測文件狀態是否發生變化,比如定時器時間是否已經超時。于是有個問題,就是sleep多久怎么確定?我們不知道是5秒后還是1秒后文件狀態發生變化,那么只能最小粒度sleep了。那么這就意味著線程在短暫掛起后,馬上檢測一系列可能尚未發生改變的事件。這種設計明顯很消耗CPU,而且非常低效。
? ? ? ? 如果使用事件模型去等待,就可以解決上述問題。但是像定時器,在系統中并沒有事件與其對應。于是我們需要考慮下對于沒有事件對應的功能怎么通過事件模型去封裝。
? ? ? ? 其次我們需要考慮使用單線程模型還是多線程模型。
? ? ? ? 單線程模型是讓主流程和事件響應函數在一個線程中執行。其偽代碼是
If (event is ready) {event_callback(); // in the main thead
}
? ? ? ? 其特點是實現簡單,但是事件響應函數的效率將嚴重影響主流程對事件的響應速度。比如A、B兩個事件同時發生,理論上我們希望兩個事件的響應函數被同時執行,或者在允許存在的系統調用時間差(比如創建線程的消耗)內執行。然而單線程模型則會讓一個響應函數執行完后再去執行另一響應函數,于是就出現排隊現象。所以單線程模型無法保證及時響應。
? ? ? ? 多線程模型則完全避免了上述問題。它可在事件發生后啟動一個線程去處理響應函數。當然相對來說多線程模型比較復雜,需要考慮多線程同步問題。
If (event is ready) {thread_excute(event_callback); // run in another thread
}
? ? ? ? 那么libev對上面兩個問題是怎么選擇的呢?對于sleep和事件模型,libev選擇的是后者,所以它是“高性能”的。對于單線程和多線程,libev選擇的是前者。至于原因我并不知道,可能是作者希望它足夠簡單,或者希望它能在不支持多線程的系統上使用。但是要說明一點,并不是說libev不支持多線程。因為一個單線程模型的執行體,是可以放在其他若干個線程中執行的,只要保證數據同步。
單/多線程編譯
? ? ? ? libev提供了各種編譯選項以支持各種特性。比如在支持多線程的系統上,我們可以指定EV_MULTIPLICITY參數,以讓libev編譯出多線程版本。
? ? ? ? libev對于單線程版本的數據都是以全局靜態變量形式提供。而對于多線程版本,則提供了一個結構體——ev_loop保存數據,這樣不同線程持有各自的數據對象,從而做到數據隔離。
#if EV_MULTIPLICITYstruct ev_loop{ev_tstamp ev_rt_now;#define ev_rt_now ((loop)->ev_rt_now)#define VAR(name,decl) decl;#include "ev_vars.h"#undef VAR};#include "ev_wrap.h"static struct ev_loop default_loop_struct;EV_API_DECL struct ev_loop *ev_default_loop_ptr = 0; /* needs to be initialised to make it a definition despite extern */#elseEV_API_DECL ev_tstamp ev_rt_now = 0; /* needs to be initialised to make it a definition despite extern */#define VAR(name,decl) static decl;#include "ev_vars.h"#undef VARstatic int ev_default_loop_ptr;#endif
? ? ? ?不管是哪個版本,它們都提供了ev_default_loop_ptr變量。多線程版本它將指向全局靜態變量default_loop_struct,這樣對于使用了多線程版本又不想維護ev_loop結構對象的用戶來說,直接使用這個對象就行了,非常方便。
? ? ? ? 然后再看下ev_vars.h的引入。其實現如下:
#define VARx(type,name) VAR(name, type name)VARx(ev_tstamp, now_floor) /* last time we refreshed rt_time */
VARx(ev_tstamp, mn_now) /* monotonic clock "now" */
VARx(ev_tstamp, rtmn_diff) /* difference realtime - monotonic time *//* for reverse feeding of events */
VARx(W *, rfeeds)
VARx(int, rfeedmax)
VARx(int, rfeedcnt)VAR (pendings, ANPENDING *pendings [NUMPRI])
VAR (pendingmax, int pendingmax [NUMPRI])
VAR (pendingcnt, int pendingcnt [NUMPRI])
……
? ? ? ? 在多線程版本中,它在ev_loop結構體中被引入的。這樣在編譯器展開文件時,它將會被定義到結構體內部。在單線程版本中,VAR宏被聲明為定義一個靜態全局變量的形式。這種利用宏和編譯展開技術,在不同結構中定義相同類型數據的方式還是很有意思的。
? ? ? ? 但是又會有個問題,如何去訪問這些變量呢?在單線程中,它們是靜態變量,所有位置可以直接通過名稱訪問。而多線程版本中,則需要通過一個ev_loop結構體去引導。相關的代碼總不能通過EV_MULTIPLICITY宏來區分不同變量形式吧?如果那樣,代碼將變得非常難看。我們看下libev怎么巧妙解決這個問題的。
? ? ? ? 上面代碼塊的多線程定義區間,引入了ev_wrap.h文件。其實現如下:
#ifndef EV_WRAP_H
#define EV_WRAP_H
#define acquire_cb ((loop)->acquire_cb)
#define activecnt ((loop)->activecnt)
#define anfdmax ((loop)->anfdmax)
#define anfds ((loop)->anfds)
#define async_pending ((loop)->async_pending)
#define asynccnt ((loop)->asynccnt)
……
? ? ? ? 這樣使用一個和變量相同名稱的宏替代了通過ev_loop結構體對象訪問的變量。且這個宏名稱和單線程版本中靜態變量名相同。這樣就讓不同版本的關鍵變量“同名”了。于是代碼對這些變量的訪問直接使用其原始名稱即可——單線程中使用的是真實變量名,多線程中使用的是宏。
? ? ? ? 這樣的設計,又引入一個問題。那就是所有使用這些變量的函數,在多線程版本中,需要提供一個名字為loop的ev_loop結構體對象;而在單線程版本中則不需要。為了固化這個名稱,libev還為此專門定義了一系列宏。
#if EV_MULTIPLICITY
struct ev_loop;
# define EV_P struct ev_loop *loop /* a loop as sole parameter in a declaration */
# define EV_P_ EV_P, /* a loop as first of multiple parameters */
# define EV_A loop /* a loop as sole argument to a function call */
# define EV_A_ EV_A, /* a loop as first of multiple arguments */
# define EV_DEFAULT_UC ev_default_loop_uc_ () /* the default loop, if initialised, as sole arg */
# define EV_DEFAULT_UC_ EV_DEFAULT_UC, /* the default loop as first of multiple arguments */
# define EV_DEFAULT ev_default_loop (0) /* the default loop as sole arg */
# define EV_DEFAULT_ EV_DEFAULT, /* the default loop as first of multiple arguments */
#else
# define EV_P void
# define EV_P_
# define EV_A
# define EV_A_
# define EV_DEFAULT
# define EV_DEFAULT_
# define EV_DEFAULT_UC
# define EV_DEFAULT_UC_
# undef EV_EMBED_ENABLE
#endif
? ? ? ? 之后我們在代碼中導出可見的EV_P和EV_A就是為了保證不同版本的實現在代碼層面是相同的。
總結
以上是生活随笔為你收集整理的libev源码解析——总览的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 静态分析C语言生成函数调用关系的利器——
- 下一篇: libev源码解析——监视器(watch