libev源码解析——定时器监视器和组织形式
? ? ? ? 我們先看下定時器監視器的數據結構。(轉載請指明出于breaksoftware的csdn博客)
/* invoked after a specific time, repeatable (based on monotonic clock) */
/* revent EV_TIMEOUT */
typedef struct ev_timer
{EV_WATCHER_TIME (ev_timer)ev_tstamp repeat; /* rw */
} ev_timer;/* invoked at some specific time, possibly repeating at regular intervals (based on UTC) */
/* revent EV_PERIODIC */
typedef struct ev_periodic
{EV_WATCHER_TIME (ev_periodic)ev_tstamp offset; /* rw */ev_tstamp interval; /* rw */ev_tstamp (*reschedule_cb)(struct ev_periodic *w, ev_tstamp now) EV_THROW; /* rw */
} ev_periodic;
? ? ? ? ev_timer是相對時間定時器監視器,ev_periodic是絕對時間定時器監視器。可以注意到,這兩個監視器結構開頭都擁有近乎相同的成員變量,即EV_WATCHER_TIME宏中定義的那部分
#define EV_WATCHER_TIME(type) \EV_WATCHER (type) \ev_tstamp at; /* private */
? ? ? ? 為什么說“近乎相同”,是因為它們傳遞給EV_WATCHER宏的參數不同,而從會導致各自擁有一個名稱不同的回調函數指針,而其他變量連名稱都一樣。
? ? ? ? 對這兩種監視器,libev并沒有像《libev源碼解析——監視器(watcher)結構和組織形式》文中所述,將這些監視器關聯到文件描述符作為下標的anfds結構中。
? ? ? ? 為什么不放在上述結構中?因為保存到anfds中的監視器,都是要求文件描述符對應的事件發生而被觸發。而定時器是要求文件描述符對應的事件沒有發生,通過等待超時而被觸發(或者被其他無關事件觸發,順帶執行)。所以將定時器監視器保存在這個結構中是沒有用的。同時這也意味著,向pendings結構(《libev源碼解析——調度策略》)中記錄本次觸發的監視器的數據來源并非只有anfds,還有定時器監視器相關的結構。
? ? ? ? 那這個結構是什么呢?我們先看下基礎結構定義
/* base class, nothing to see here unless you subclass */
typedef struct ev_watcher_time
{EV_WATCHER_TIME (ev_watcher_time)
} ev_watcher_time;typedef ev_watcher_time *WT;typedef struct {ev_tstamp at;WT w;
} ANHE;
? ? ? ? ev_watcher_timer和ev_timer、ev_periodic有著近乎一致的數據格式——它們都是使用EV_WATCHER_TIME宏構成開始的幾個成員變量。
? ? ? ? 這意味著,在忽略回調函數名的情況下,我們可以使用ev_watcher_timer指針指向ev_timer結構體對象或者ev_periodic結構體對象。
? ? ? ? 因為ANHE結構中變量w可以指向ev_timer,那么目前我們有兩種保存定時器監視器的方式(以ev_timer為例):
- 連續的ev_timer結構體對象構成的數組
- 連續的ANHE結構對象構成的數組
? ? ? ? 這兩種結構在內存中的布局如下:
? ? ? ? 這兩種結構都有自己的優缺點:
- 結構比較簡單,各個監視器的位置關系由數組下標維護。其缺點調整監視器位置比較低效,因為這意味著比較多的內存需要被轉移。
- 結構則相對復雜,因為其通過一個額外ANHE數組維護各個監視器的關系。但是它可以方便的調整監視器的位置——只要調整各個指針即可。
? ? ? ? 對于結構的選擇,應該依據應用場景決定。我們在《libev源碼解析——定時器原理》中提到,libev需要尋找到“下次執行時間”離現在最近的監視器。如果采用連續的ev_timer結構存儲,則可以分為如下兩種處理方式:
- ev_timer數組中各個元素位置不變,只是修改“下次執行時間”。對于這樣亂序的數組,只有遍歷的辦法才能找到最近的。然而這種方式效率很低。
- ev_timer數組中各個元素位置可變。這樣在修改“下次執行時間”后,就需要對數組元素重新布局。
? ? ? ? 而如果采用連續的ANHE結構存儲,則對于元素位置發生變化的場景,只要修改兩個字段即可,而不用像上面方案那樣要修改七個字段。相比較而言,這個方案更優。libev選用的就是這個方案。
void noinline
ev_timer_start (EV_P_ ev_timer *w) EV_THROW
{
……++timercnt;ev_start (EV_A_ (W)w, timercnt + HEAP0 - 1);array_needsize (ANHE, timers, timermax, ev_active (w) + 1, EMPTY2);ANHE_w (timers [ev_active (w)]) = (WT)w;ANHE_at_cache (timers [ev_active (w)]);
……
}
? ? ? ? timers是保存“相對時間定時器”監視器的數組結構。在ev_periodic_start代碼中,我們可以看到“絕對時間定時器”監視器保存在名字叫periodics的數組中。
? ? ? ? 下一步我們就需要尋找一種相對高效的排序算法。首先我們想到的是順序排序,而且離現在時間最近的元素排在數組第一位。為了方便描述,我們之后將時間改成整數表示。假設我們有如下的監視器:1、3、5、7、9、11、13。如果時間為1的監視器執行了,則將其時間差改成12,則數組的移動方式如下
? ? ? ? w2~w6可以見得,“牽一發而動全身”。那是否有更好的方式?libev采用的是最小堆。關于最小堆操作的示例,可以參見《最小堆 構建、插入、刪除的過程圖解》。以上例,則操作如下圖
? ? ? ?可見,我們將操作元素的次數降低到3次,比順序排列的方案要少很多。
? ? ? ?這種操作是自上而下的,對應的代碼是
inline_speed void
downheap (ANHE *heap, int N, int k)
{ANHE he = heap [k];for (;;){int c = k << 1;if (c >= N + HEAP0)break;c += c + 1 < N + HEAP0 && ANHE_at (heap [c]) > ANHE_at (heap [c + 1])? 1 : 0;if (ANHE_at (he) <= ANHE_at (heap [c]))break;heap [k] = heap [c];ev_active (ANHE_w (heap [k])) = k;k = c;}heap [k] = he;ev_active (ANHE_w (he)) = k;
}
? ? ? ? 這個行為發生在定時器監視器向pendings結構中記錄時,因為離當前時間最近的監視器馬上要執行,所以要修改它下一次執行的時間,然后重新布局結構。
inline_size void
timers_reify (EV_P)
{EV_FREQUENT_CHECK;if (timercnt && ANHE_at (timers [HEAP0]) < mn_now){do{ev_timer *w = (ev_timer *)ANHE_w (timers [HEAP0]);/*assert (("libev: inactive timer on timer heap detected", ev_is_active (w)));*//* first reschedule or stop timer */if (w->repeat){ev_at (w) += w->repeat;if (ev_at (w) < mn_now)ev_at (w) = mn_now;assert (("libev: negative ev_timer repeat value found while processing timers", w->repeat > 0.));ANHE_at_cache (timers [HEAP0]);downheap (timers, timercnt, HEAP0);
? ? ? ? 還有一種是自下而上的重新布局,這種發生在新增一個定時器時。
? ? ? ? 相應代碼是
inline_speed void
upheap (ANHE *heap, int k)
{ANHE he = heap [k];for (;;){int p = HPARENT (k);if (UPHEAP_DONE (p, k) || ANHE_at (heap [p]) <= ANHE_at (he))break;heap [k] = heap [p];ev_active (ANHE_w (heap [k])) = k;k = p;}heap [k] = he;ev_active (ANHE_w (he)) = k;
}
void noinline
ev_timer_start (EV_P_ ev_timer *w) EV_THROW
{if (expect_false (ev_is_active (w)))return;ev_at (w) += mn_now;assert (("libev: ev_timer_start called with negative timer repeat value", w->repeat >= 0.));EV_FREQUENT_CHECK;++timercnt;ev_start (EV_A_ (W)w, timercnt + HEAP0 - 1);array_needsize (ANHE, timers, timermax, ev_active (w) + 1, EMPTY2);ANHE_w (timers [ev_active (w)]) = (WT)w;ANHE_at_cache (timers [ev_active (w)]);upheap (timers, ev_active (w));EV_FREQUENT_CHECK;/*assert (("libev: internal timer heap corruption", timers [ev_active (w)] == (WT)w));*/
}
? ? ? ? 至此,《libev源碼解析》系列已經講完。經過這個系列分析,我們應該可以對libev整體實現原理和關鍵結構有了充分的了解。
? ? ? ? 至于具體實現,可以參見《libev中文手冊》。
?
總結
以上是生活随笔為你收集整理的libev源码解析——定时器监视器和组织形式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: libev源码解析——定时器原理
- 下一篇: 代码打补丁的利器——diff和patch