libev源码解析——监视器(watcher)结构和组织形式
? ? ? ? 在《libev源碼解析——總覽》中,我們介紹了libev的一些重要變量在不同編譯參數(shù)下的定義位置。由于這些變量在多線程下沒有同步問題,所以我們將問題簡化,所提到的變量都是線程內(nèi)部獨有的,不用考慮任何多線程問題。(轉(zhuǎn)載請指明出于breaksoftware的csdn博客)
? ? ? ? 之前提到過,libev支持多種功能,比如文件狀態(tài)監(jiān)控、定時器等。這些功能都是有其相對應(yīng)的一個“監(jiān)視器”(watcher),比如文件監(jiān)視器、相對時間定時器監(jiān)視器等。雖然這些監(jiān)視器很多,但是它們都共有一些屬性
#ifndef EV_COMMON
# define EV_COMMON void *data;
#endif#ifndef EV_CB_DECLARE
# define EV_CB_DECLARE(type) void (*cb)(EV_P_ struct type *w, int revents);
#endif#if EV_MINPRI == EV_MAXPRI
# define EV_DECL_PRIORITY
#elif !defined (EV_DECL_PRIORITY)
# define EV_DECL_PRIORITY int priority;
#endif/* shared by all watchers */
#define EV_WATCHER(type) \int active; /* private */ \int pending; /* private */ \EV_DECL_PRIORITY /* private */ \EV_COMMON /* rw */ \EV_CB_DECLARE (type) /* private */
? ? ? ? active表示這個監(jiān)視器是否處于激活狀態(tài)。
? ? ? ? priority表示監(jiān)視器的優(yōu)先級,其值可以從-2~2,共5個級別。其中2是最高級別,-2是最低級別。級別高的監(jiān)視器會優(yōu)先于級別低的監(jiān)視器執(zhí)行。
? ? ? ? cb是事件響應(yīng)函數(shù)指針,data則是用于保存用戶自定義的數(shù)據(jù)。這樣的組合設(shè)計在使用回調(diào)函數(shù)的開源庫中很常見。因為回調(diào)的調(diào)用機會并不由我們掌握,我們無法區(qū)分每次回調(diào)對應(yīng)于我們哪次注冊行為。而可以通過在向框架注冊回調(diào)函數(shù)時保存回調(diào)調(diào)用的數(shù)據(jù)來達到區(qū)分的目的。
? ? ? ? pending用于表示該監(jiān)視器在觸發(fā)過的相同優(yōu)先級下所有監(jiān)視器數(shù)組的索引下標。因為相同優(yōu)先級的監(jiān)視器可能有很多,所以我們需要一個結(jié)構(gòu)保存這樣的一組數(shù)據(jù),于是就需要索引/下標進行區(qū)分。這塊信息我們將在《libev使用方法和源碼解析——關(guān)鍵結(jié)構(gòu)和基本原理2》介紹。
? ? ? ? 最簡單的監(jiān)視器,也是最基礎(chǔ)的監(jiān)視器是ev_watcher。它只具有EV_WATCHER聲明的變量
typedef struct ev_watcher
{EV_WATCHER (ev_watcher)
} ev_watcher;
? ? ? ??? ? ? ??
? ? ? ? 由于相同類型的監(jiān)視器可能有多個,所以我們需要一個結(jié)構(gòu)保存這么一組監(jiān)視器。于是libev使用鏈表的形式保存這樣的數(shù)據(jù)。那使用什么類型的鏈表呢?如果我們每個監(jiān)視器的內(nèi)存結(jié)構(gòu)大小相同,則我們可以使用連續(xù)的內(nèi)存結(jié)構(gòu)??墒侵笪覀儠榻B到,不同監(jiān)視器的大小是不一樣的。于是libev使用的是堆上分配的單向鏈表結(jié)構(gòu)。至于實現(xiàn),我們只要在結(jié)構(gòu)中適當位置保存指向下一個結(jié)構(gòu)地址的指針即可
#define EV_WATCHER_LIST(type) \EV_WATCHER (type) \struct ev_watcher_list *next; /* private */typedef struct ev_watcher_list
{EV_WATCHER_LIST (ev_watcher_list)
} ev_watcher_list;
? ? ? ??
? ? ? ? 其他監(jiān)視器都是在此結(jié)構(gòu)末尾追加了各自需要記錄的數(shù)據(jù)。比如IO監(jiān)視器和子進程監(jiān)視器
typedef struct ev_io
{EV_WATCHER_LIST (ev_io)int fd; /* ro */int events; /* ro */
} ev_io;typedef struct ev_child
{EV_WATCHER_LIST (ev_child)int flags; /* private */int pid; /* ro */int rpid; /* rw, holds the received pid */int rstatus; /* rw, holds the exit status, use the macros from sys/wait.h */
} ev_child;
? ? ? ??
? ? ? ? 我們需要注意下這樣設(shè)計的用意。從下圖可見,任何監(jiān)視器都可以被按ev_watcher大小準確切分,這意味著我們可以使用ev_watcher指向任何監(jiān)視器結(jié)構(gòu)體。同樣的,還可以使用ev_watcher_list指向任何監(jiān)視器。這種設(shè)計的優(yōu)點將在后面展現(xiàn)出來。
? ? ? ? 看了這些監(jiān)視器,我們還不能察覺到libev的底層原理?,F(xiàn)在我們回憶下之前的介紹——libev是一個基于事件的循環(huán)庫。那么事件將是一個核心,然而事件需要一個文件描述符(fd)。文件描述符將和這些監(jiān)視器如何協(xié)作呢?
? ? ? ? 我們可以想象出,一個文件描述符應(yīng)該關(guān)聯(lián)起來多個監(jiān)視器。比如我們要監(jiān)視一個文件是否可讀,那么這個監(jiān)視器將和文件描述符關(guān)聯(lián)。我們還要監(jiān)視這個文件是否可寫入,那么又有一個監(jiān)視器和這個文件描述符關(guān)聯(lián)。那么這些不同的監(jiān)視器將如何圍繞在這個文件描述符周圍呢?鏈表!之前提到的ev_watcher_list鏈表,它可以把不同類型的監(jiān)視器連接在一起。libev就是這么做的,它定義了一個結(jié)構(gòu)ANFD
typedef ev_watcher_list *WL;typedef struct
{WL head;unsigned char events; /* the events watched for */unsigned char reify; /* flag set when this ANFD needs reification (EV_ANFD_REIFY, EV__IOFDSET) */unsigned char emask; /* the epoll backend stores the actual kernel mask in here */unsigned char unused;
#if EV_USE_EPOLLunsigned int egen; /* generation counter to counter epoll bugs */
#endif
#if EV_SELECT_IS_WINSOCKET || EV_USE_IOCPSOCKET handle;
#endif
#if EV_USE_IOCPOVERLAPPED or, ow;
#endif
} ANFD;
? ? ? ? 我們只需要關(guān)注head和events變量。
? ? ? ? head從名字上就可以看出它是一個監(jiān)視器鏈表的頭。這兒提一句,我們看到這是一個單向鏈表,這也意味著以后要對這個鏈表進行元素新增很有可能是在頭部插入,因為那樣做最高效了。
? ? ? ? events變量表示和文件描述符關(guān)聯(lián)的事件,為什么要記錄這個數(shù)據(jù)呢?繼續(xù)以之前的例子為例,我們先要監(jiān)控這個文件的可讀,于是events是EV_READ;現(xiàn)在我們還要監(jiān)控其可寫,于是events變成EV_WRITE|EV_READ,而相應(yīng)的head下將有兩個監(jiān)視器。此時IO模型(select/poll/epoll等)將監(jiān)視該文件描述符的可讀可寫,如果發(fā)生任何之一,將使用發(fā)生的事件去head下各個監(jiān)視器去匹配。
? ? ? ? ANFD結(jié)構(gòu)不需要再擴展了,于是它的結(jié)構(gòu)是穩(wěn)定的。所以我們可以使用連續(xù)的地址空間去保存一組信息。而且我們可以使用文件描述符的值去做其數(shù)組下標,這樣就可以很方便通過文件描述符找到其對應(yīng)的監(jiān)視器鏈表。
? ? ? ? 當然ANFD使用連續(xù)內(nèi)存也是有個前提的,就是文件描述符的值必須在一定的值以下。為什么呢?比如文件描述符的值如果能達到0xFFFFFFFF,那么這個數(shù)組要有0xFFFFFFFF個元素?這明顯是不能接受的。所幸,系統(tǒng)的文件描述符值的上限只有幾萬。
? ? ? ? 在libev中,它使用anfds保存上述數(shù)組。數(shù)組的大小也并非一開始就使用文件描述符上限值,而是隨著使用的文件描述符值增大而增大。
#define array_needsize(type,base,cur,cnt,init) \if (expect_false ((cnt) > (cur))) \{ \int ecb_unused ocur_ = (cur); \(base) = (type *)array_realloc \(sizeof (type), (base), &(cur), (cnt)); \init ((base) + (ocur_), (cur) - ocur_); \}void noinline
ev_io_start (EV_P_ ev_io *w) EV_THROW
{int fd = w->fd;if (expect_false (ev_is_active (w)))return;assert (("libev: ev_io_start called with negative fd", fd >= 0));assert (("libev: ev_io_start called with illegal event mask", !(w->events & ~(EV__IOFDSET | EV_READ | EV_WRITE))));EV_FREQUENT_CHECK;ev_start (EV_A_ (W)w, 1);array_needsize (ANFD, anfds, anfdmax, fd + 1, array_init_zero);
? ? ? ? 數(shù)組重分配后,就讓文件描述符值作為下標的ANFD結(jié)構(gòu)的head指向新的監(jiān)視器
wlist_add (&anfds[fd].head, (WL)w);
? ? ? ? 理論上來說,我們有了這么一個結(jié)構(gòu)就可以滿足libev運行起來了。但是有個問題沒法解決,那就是libev的特性——權(quán)限高的優(yōu)先執(zhí)行。下一節(jié)我們將就這個問題作出解釋。
總結(jié)
以上是生活随笔為你收集整理的libev源码解析——监视器(watcher)结构和组织形式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: libev源码解析——总览
- 下一篇: libev源码解析——调度策略