libev源码解析——总览
? ? ? ? libev是個(gè)非常優(yōu)秀的基于事件的循環(huán)庫,很多開源軟件,比如nodejs就是使用其實(shí)現(xiàn)基礎(chǔ)功能。本系列將對(duì)該庫進(jìn)行源碼分析。(轉(zhuǎn)載請(qǐng)指明出于breaksoftware的csdn博客)
? ? ? ? 不知道是被墻了還是網(wǎng)站不再維護(hù),它的官網(wǎng)(http://libev.schmorp.de/)在國內(nèi)已經(jīng)沒法訪問了。但是我們?nèi)匀豢梢詮膅ithub上下載其源碼(https://github.com/enki/libev)。
使用樣例
? ? ? ? libev支持相對(duì)時(shí)間定時(shí)器、絕對(duì)時(shí)間定時(shí)器、文件狀態(tài)監(jiān)控和信號(hào)監(jiān)控等功能。我們可以在它基礎(chǔ)上,通過少量的代碼實(shí)現(xiàn)穩(wěn)健完善的功能。
? ? ? ? 我們先看一段實(shí)現(xiàn)定時(shí)器功能的代碼
#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;
}
? ? ? ? 這段代碼的結(jié)構(gòu)非常簡(jiǎn)單。首先我們要定義一個(gè)名為timeout_cb的回調(diào)函數(shù)用于響應(yīng)定時(shí)器。然后定義一個(gè)ev_timer結(jié)構(gòu)(監(jiān)視器),它通過ev_timer_init進(jìn)行初始化。初始化的參數(shù)包含之前定義的響應(yīng)函數(shù)指針和迭代器超時(shí)時(shí)間。ev_timer準(zhǔn)備好后,通過ev_timer_start將其和一個(gè)ev_loop進(jìn)行綁定。最后使用ev_run方法運(yùn)行起來這個(gè)ev_loop指針,從而實(shí)現(xiàn)一個(gè)完整的定時(shí)器功能。
? ? ? ? 可見使用libev庫非常方便。其實(shí)我們之后見到的其他用法和上面步驟是類似的,即:
- 初始化ev_loop。
- 定義監(jiān)視器。
- 定義回調(diào)函數(shù)。
- 監(jiān)視器和回調(diào)函數(shù)關(guān)聯(lián)。
- 監(jiān)視器和ev_loop關(guān)聯(lián)。
- ev_run將ev_loop運(yùn)行起來。
? ? ? ? 假如上面代碼是個(gè)框架使用的雛形,那么如果讓我們?nèi)ピO(shè)計(jì)這樣的框架,該如何設(shè)計(jì)呢?
模型設(shè)計(jì)
? ? ? ? 首先我們需要考慮到的是使用sleep還是使用事件模型去實(shí)現(xiàn)邏輯等待。
? ? ? ? 如果使用sleep方法,那么我們就要在喚醒后去檢測(cè)各個(gè)事件,比如要檢測(cè)文件狀態(tài)是否發(fā)生變化,比如定時(shí)器時(shí)間是否已經(jīng)超時(shí)。于是有個(gè)問題,就是sleep多久怎么確定?我們不知道是5秒后還是1秒后文件狀態(tài)發(fā)生變化,那么只能最小粒度sleep了。那么這就意味著線程在短暫掛起后,馬上檢測(cè)一系列可能尚未發(fā)生改變的事件。這種設(shè)計(jì)明顯很消耗CPU,而且非常低效。
? ? ? ? 如果使用事件模型去等待,就可以解決上述問題。但是像定時(shí)器,在系統(tǒng)中并沒有事件與其對(duì)應(yīng)。于是我們需要考慮下對(duì)于沒有事件對(duì)應(yīng)的功能怎么通過事件模型去封裝。
? ? ? ? 其次我們需要考慮使用單線程模型還是多線程模型。
? ? ? ? 單線程模型是讓主流程和事件響應(yīng)函數(shù)在一個(gè)線程中執(zhí)行。其偽代碼是
If (event is ready) {event_callback(); // in the main thead
}
? ? ? ? 其特點(diǎn)是實(shí)現(xiàn)簡(jiǎn)單,但是事件響應(yīng)函數(shù)的效率將嚴(yán)重影響主流程對(duì)事件的響應(yīng)速度。比如A、B兩個(gè)事件同時(shí)發(fā)生,理論上我們希望兩個(gè)事件的響應(yīng)函數(shù)被同時(shí)執(zhí)行,或者在允許存在的系統(tǒng)調(diào)用時(shí)間差(比如創(chuàng)建線程的消耗)內(nèi)執(zhí)行。然而單線程模型則會(huì)讓一個(gè)響應(yīng)函數(shù)執(zhí)行完后再去執(zhí)行另一響應(yīng)函數(shù),于是就出現(xiàn)排隊(duì)現(xiàn)象。所以單線程模型無法保證及時(shí)響應(yīng)。
? ? ? ? 多線程模型則完全避免了上述問題。它可在事件發(fā)生后啟動(dòng)一個(gè)線程去處理響應(yīng)函數(shù)。當(dāng)然相對(duì)來說多線程模型比較復(fù)雜,需要考慮多線程同步問題。
If (event is ready) {thread_excute(event_callback); // run in another thread
}
? ? ? ? 那么libev對(duì)上面兩個(gè)問題是怎么選擇的呢?對(duì)于sleep和事件模型,libev選擇的是后者,所以它是“高性能”的。對(duì)于單線程和多線程,libev選擇的是前者。至于原因我并不知道,可能是作者希望它足夠簡(jiǎn)單,或者希望它能在不支持多線程的系統(tǒng)上使用。但是要說明一點(diǎn),并不是說libev不支持多線程。因?yàn)橐粋€(gè)單線程模型的執(zhí)行體,是可以放在其他若干個(gè)線程中執(zhí)行的,只要保證數(shù)據(jù)同步。
單/多線程編譯
? ? ? ? libev提供了各種編譯選項(xiàng)以支持各種特性。比如在支持多線程的系統(tǒng)上,我們可以指定EV_MULTIPLICITY參數(shù),以讓libev編譯出多線程版本。
? ? ? ? libev對(duì)于單線程版本的數(shù)據(jù)都是以全局靜態(tài)變量形式提供。而對(duì)于多線程版本,則提供了一個(gè)結(jié)構(gòu)體——ev_loop保存數(shù)據(jù),這樣不同線程持有各自的數(shù)據(jù)對(duì)象,從而做到數(shù)據(jù)隔離。
#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
? ? ? ?不管是哪個(gè)版本,它們都提供了ev_default_loop_ptr變量。多線程版本它將指向全局靜態(tài)變量default_loop_struct,這樣對(duì)于使用了多線程版本又不想維護(hù)ev_loop結(jié)構(gòu)對(duì)象的用戶來說,直接使用這個(gè)對(duì)象就行了,非常方便。
? ? ? ? 然后再看下ev_vars.h的引入。其實(shí)現(xiàn)如下:
#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結(jié)構(gòu)體中被引入的。這樣在編譯器展開文件時(shí),它將會(huì)被定義到結(jié)構(gòu)體內(nèi)部。在單線程版本中,VAR宏被聲明為定義一個(gè)靜態(tài)全局變量的形式。這種利用宏和編譯展開技術(shù),在不同結(jié)構(gòu)中定義相同類型數(shù)據(jù)的方式還是很有意思的。
? ? ? ? 但是又會(huì)有個(gè)問題,如何去訪問這些變量呢?在單線程中,它們是靜態(tài)變量,所有位置可以直接通過名稱訪問。而多線程版本中,則需要通過一個(gè)ev_loop結(jié)構(gòu)體去引導(dǎo)。相關(guān)的代碼總不能通過EV_MULTIPLICITY宏來區(qū)分不同變量形式吧?如果那樣,代碼將變得非常難看。我們看下libev怎么巧妙解決這個(gè)問題的。
? ? ? ? 上面代碼塊的多線程定義區(qū)間,引入了ev_wrap.h文件。其實(shí)現(xiàn)如下:
#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)
……
? ? ? ? 這樣使用一個(gè)和變量相同名稱的宏替代了通過ev_loop結(jié)構(gòu)體對(duì)象訪問的變量。且這個(gè)宏名稱和單線程版本中靜態(tài)變量名相同。這樣就讓不同版本的關(guān)鍵變量“同名”了。于是代碼對(duì)這些變量的訪問直接使用其原始名稱即可——單線程中使用的是真實(shí)變量名,多線程中使用的是宏。
? ? ? ? 這樣的設(shè)計(jì),又引入一個(gè)問題。那就是所有使用這些變量的函數(shù),在多線程版本中,需要提供一個(gè)名字為loop的ev_loop結(jié)構(gòu)體對(duì)象;而在單線程版本中則不需要。為了固化這個(gè)名稱,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
? ? ? ? 之后我們?cè)诖a中導(dǎo)出可見的EV_P和EV_A就是為了保證不同版本的實(shí)現(xiàn)在代碼層面是相同的。
總結(jié)
以上是生活随笔為你收集整理的libev源码解析——总览的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 静态分析C语言生成函数调用关系的利器——
- 下一篇: libev源码解析——监视器(watch