定時器事件的創建
Libevent 一般調用evtimer_new來定義一個定時器事件
#define evtimer_new(b, cb, arg) event_new((b), -1, 0, (cb), (arg))
從宏定義來看,這個事件和io、signal事件的區別在於fd項爲-1,表示並不關注, 並且events項爲0, 並不是想象中的EV_TIMEOUT.
evtimer_new只是初始化了一個一般的超時事件,而真正將一個事件進行超時處理是在
event_add函數的第二個參數必須指定一個超時時間。
總結來說,不管event_new創建了一個什麼類型的event,如果在event_add的第二個參數添加了一個超時時間,則該事件就會進行超時處理了。
定時器事件實例
[email protected]:~/test/libevent/my_libevent_test>more libevent_test_timeout.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <event.h>
#include <time.h>
void do_timeout(evutil_socket_t fd, short event, void *arg)
{
printf("do timeout (time: %ld)!\n", time(NULL));
}
void create_timeout_event(struct event_base *base)
{
struct event *ev;
struct timeval timeout;
//ev = evtimer_new(base, do_timeout, NULL);
ev = event_new(base, -1, EV_PERSIST, do_timeout, NULL);
if (ev) {
timeout.tv_sec = 5;
timeout.tv_usec = 0;
event_add(ev, &timeout);
}
}
int main(int argc, char *argv[])
{
struct event_base *base;
base = event_base_new();
if (!base) {
printf("Error: Create an event base error!\n");
return -1;
}
create_timeout_event(base);
event_base_dispatch(base);
return 0;
}
event_add 對定時器事件的處理
event_add ——>event_add_internal
通過event_add調用event_add_internal時,tv_is_absolute爲0.
event結構中的events是event_new時候傳入的參數,如
/**
* @name event flags
*
* Flags to pass to event_new(), event_assign(), event_pending(), and
* anything else with an argument of the form "short events"
*/
/**@{*/
/** Indicates that a timeout has occurred. It's not necessary to pass
* this flag to event_for new()/event_assign() to get a timeout. */
#define EV_TIMEOUT 0x01
/** Wait for a socket or FD to become readable */
#define EV_READ 0x02
/** Wait for a socket or FD to become writeable */
#define EV_WRITE 0x04
/** Wait for a POSIX signal to be raised*/
#define EV_SIGNAL 0x08
/**
* Persistent event: won't get removed automatically when activated.
*
* When a persistent event with a timeout becomes activated, its timeout
* is reset to 0.
*/
#define EV_PERSIST 0x10
/** Select edge-triggered behavior, if supported by the backend. */
#define EV_ET 0x20
/**@}*/
而event結構中的ev_flags 根據不同的event表項設置的。
/*如果 tv_is_absolute不爲0, 則tv表示絕對時間, 而不是間隔差值*/
static inline int
event_add_internal(struct event *ev, const struct timeval *tv,
int tv_is_absolute)
{
struct event_base *base = ev->ev_base;
int res = 0;
int notify = 0;
......
......
/*
* prepare for timeout insertion further below, if we get a
* failure on any step, we should not change any state.
*/
/*如果新添加的事件處理器是定時器,且它尚未被添加到通用定時器隊列或時間堆中,則爲該定時器
* 在時間堆上預留一個位置,如果當前時間堆數組大小夠了,min_heap_reserve直接返回,如果不夠,resize數組大小,
* 保證可以插入新的堆節點
* ev->ev_flags 爲EVLIST_TIMEOUT, 在本函數中通過event_queue_insert(base, ev, EVLIST_TIMEOUT);
* 如果eve->ev_flags 爲EVLIST_TIMEOUT 說明該事件已經在time堆中了*/
if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {
if (min_heap_reserve(&base->timeheap,
1 + min_heap_size(&base->timeheap)) == -1)
return (-1); /* ENOMEM == errno */
}
......
......
/*處理沒有激活的READ WRITE SIGNAL 事件*/
if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&
!(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {
.......
}
/*
* we should change the timeout state only if the previous event
* addition succeeded.
*/
/*將事件處理器添加到通用定時器隊列或者事件堆中。
* res != -1 表示對I/O事件和信號事件的添加成功,沒有出錯
* tv != NULl, 表示設置了超時事件*/
if (res != -1 && tv != NULL) {
struct timeval now;
int common_timeout;
/*
* for persistent timeout events, we remember the
* timeout value and re-add the event.
*
* If tv_is_absolute, this was already set.
*/
/*對於persist的時間事件,如果是相對時間參數,用ev_io_timeout記錄這個相對值,
* 因爲每一次的起始時間是不一樣的,需要在不同的起始時間加上相對時間值*/
if (ev->ev_closure == EV_CLOSURE_PERSIST && !tv_is_absolute)
ev->ev_io_timeout = *tv;
/*
* we already reserved memory above for the case where we
* are not replacing an existing timeout.
*/
/*如果該事件處理器已經被插入通用定時器隊列或時間堆中,則先刪除它*/
if (ev->ev_flags & EVLIST_TIMEOUT) {
/* XXX I believe this is needless. */
if (min_heap_elt_is_top(ev))
notify = 1;
event_queue_remove(base, ev, EVLIST_TIMEOUT);
}
/* Check if it is active due to a timeout. Rescheduling
* this timeout before the callback can be executed
* removes it from the active list. */
/*如果待處理的事件已經被激活,且原因是超時, 則從活動事件隊列中刪除它,
* 以避免其回調函數被執行。
* 對於信號事件處理器,必要時還需將其ncalls成爲設置爲0,使得乾淨地終止信號
* 事件的處理*/
if ((ev->ev_flags & EVLIST_ACTIVE) &&
(ev->ev_res & EV_TIMEOUT)) {
if (ev->ev_events & EV_SIGNAL) {
/* See if we are just active executing
* this event in a loop
*/
if (ev->ev_ncalls && ev->ev_pncalls) {
/* Abort loop */
*ev->ev_pncalls = 0;
}
}
event_queue_remove(base, ev, EVLIST_ACTIVE);
}
/*獲取當前事件now*/
gettime(base, &now);
common_timeout = is_common_timeout(tv, base);
/*計算絕對事件,當前時間加上時間間隔*/
if (tv_is_absolute) {
ev->ev_timeout = *tv;
} else if (common_timeout) {
struct timeval tmp = *tv;
tmp.tv_usec &= MICROSECONDS_MASK;
evutil_timeradd(&now, &tmp, &ev->ev_timeout);
ev->ev_timeout.tv_usec |=
(tv->tv_usec & ~MICROSECONDS_MASK);
} else {
evutil_timeradd(&now, tv, &ev->ev_timeout);
}
event_debug((
"event_add: timeout in %d seconds, call %p",
(int)tv->tv_sec, ev->ev_callback));
/*將定時器事件添加到通用事件隊列或者最小堆中*/
event_queue_insert(base, ev, EVLIST_TIMEOUT);
if (common_timeout) {
/*如果是通用定時器,並且是尾隊列頭節點時,將ctl結構中的timeout_event事件放入最小堆中*/
struct common_timeout_list *ctl =
get_common_timeout_list(base, &ev->ev_timeout);
if (ev == TAILQ_FIRST(&ctl->events)) {
common_timeout_schedule(ctl, &now, ev);
}
} else {
/* See if the earliest timeout is now earlier than it
* was before: if so, we will need to tell the main
* thread to wake up earlier than it would
* otherwise. */
if (min_heap_elt_is_top(ev))
notify = 1;
}
}
/* if we are not in the right thread, we need to wake up the loop */
if (res != -1 && notify && EVBASE_NEED_NOTIFY(base))
evthread_notify_base(base);
_event_debug_note_add(ev);
return (res);
}
最小堆 min_heap_reserve/min_heap_size
最小堆實現是用一個數組實現,類似c++ 裏面的vector
一般添加的定時器事件都是最小堆的形式存儲。
struct event_base {
.....
truct min_heap timeheap;
.....
};
typedef struct min_heap
{
struct event** p; -------------struct event*結構的數組
unsigned n, a; -------------a:當前分配的總個數;n---當前已經使用的數組個數
} min_heap_t;
unsigned min_heap_size(min_heap_t* s) { return s->n; } -----返回當前已使用的堆個數,即數組個數
int min_heap_reserve(min_heap_t* s, unsigned n)
{
/* 如果要插入的個數大於堆的總大小, 重新分配堆數組的個數
* 如果要插入的個數小於堆的總大小,表示堆數組有空間可以新的節點*/
if (s->a < n)
{
struct event** p;
unsigned a = s->a ? s->a * 2 : 8; /*第一次分配爲8, 後續每次重分配的大小爲上一次的2倍*/
/*如果2倍的新空間大小還比n小,設置數據大小爲n*/
if (a < n)
a = n;
if (!(p = (struct event**)mm_realloc(s->p, a * sizeof *p))) -----分配大小爲a的struct event *數組內存
return -1;
s->p = p;
s->a = a;
}
return 0;
}
通用時間
由於tv_usec是32比特長度, 而表示微秒數只需要20位,
(因爲微秒數不能超過999999, 2的20次~= 1048576), 所以用32bits的最後20bits表示微秒數,用最前面的4bits表示magic號,中間8bits表示event_base結構中的通用定時器common_timeout_queues的數組索引,所以數組元素最多256個
判斷是否爲通用時間的方法是:
最高8bit的值爲5.
通用時間一般是人爲手動添加的。
#define COMMON_TIMEOUT_IDX_MASK 0x0ff00000
#define COMMON_TIMEOUT_IDX_SHIFT 20
#define COMMON_TIMEOUT_MAGIC 0x50000000
#define COMMON_TIMEOUT_MASK 0xf0000000
#define COMMON_TIMEOUT_IDX(tv) \
(((tv)->tv_usec & COMMON_TIMEOUT_IDX_MASK)>>COMMON_TIMEOUT_IDX_SHIFT)
/** Return true iff if 'tv' is a common timeout in 'base' */
static inline int
is_common_timeout(const struct timeval *tv, const struct event_base *base)
{
int idx;
if ((tv->tv_usec & COMMON_TIMEOUT_MASK) != COMMON_TIMEOUT_MAGIC)
return 0;
idx = COMMON_TIMEOUT_IDX(tv);
return idx < base->n_common_timeouts;
}
event_base_dispatch/event_base_loop
int
event_base_loop(struct event_base *base, int flags)
{
const struct eventop *evsel = base->evsel;
struct timeval tv;
struct timeval *tv_p;
int res, done, retval = 0;
/* Grab the lock. We will release it inside evsel.dispatch, and again
* as we invoke user callbacks. */
EVBASE_ACQUIRE_LOCK(base, th_base_lock);
/*一個event_base只允許運行一個事件循環*/
if (base->running_loop) {
event_warnx("%s: reentrant invocation. Only one event_base_loop"
" can run on each event_base at once.", __func__);
EVBASE_RELEASE_LOCK(base, th_base_lock);
return -1;
}
base->running_loop = 1; /*標記該event_base已經開始運行*/
clear_time_cache(base); /*清除event_base的系統時間緩存*/
if (base->sig.ev_signal_added && base->sig.ev_n_signals_added)
evsig_set_base(base);
done = 0;
#ifndef _EVENT_DISABLE_THREAD_SUPPORT
base->th_owner_id = EVTHREAD_GET_ID();
#endif
base->event_gotterm = base->event_break = 0;
while (!done) {
base->event_continue = 0;
/*查看是否需要跳出循環, 程序可以調用event_loopexit_cb() 設置event_gotterm標記
* 調用event_base_loopbreak()設置event_break 標記*/
/* Terminate the loop if we have been asked to */
if (base->event_gotterm) {
break;
}
if (base->event_break) {
break;
}
/*校準系統時間*/
timeout_correct(base, &tv);
tv_p = &tv;
/*base裏面目前激活的事件數目爲0,並且爲阻塞性的I/O複用, 則取時間堆上面的最小堆節點的超時時間作爲epoll的超時時間*/
if (!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK)) {
timeout_next(base, &tv_p); /*獲取時間堆上堆頂元素的超時值, 即I/O複用系統調用本次應該設置的超時值*/
} else {
/*
* if we have active events, we just poll new events
* without waiting.
*/
/*如果有就緒事件尚未處理, 則將I/O複用系統調用的超時時間"置0"。
* 這樣I/O複用系統調用直接返回, 程序也就可以立即處理就緒事件了*/
evutil_timerclear(&tv);
}
/*如果event_base 中沒有註冊任何事件, 則直接退出事件循環*/
/* If we have no events, we just exit */
if (!event_haveevents(base) && !N_ACTIVE_CALLBACKS(base)) {
event_debug(("%s: no events registered.", __func__));
retval = 1;
goto done;
}
/* update last old time */
gettime(base, &base->event_tv); /*更新系統時間*/
/*之所以要在進入dispatch之前清零,是因爲進入
*dispatch後,可能會等待一段時間。cache就沒有意義了。
*如果第二個線程此時想add一個event到這個event_base裏面,在
*event_add_internal函數中會調用gettime。如果cache不清零,
*那麼將會取這個cache時間。這將取一個不準確的時間.*/
clear_time_cache(base); /*清除event_base的系統時間緩存*/
/*調用事件多路分發器的dispatch方法等待事件, 將就緒事件插入活動事件隊列*/
res = evsel->dispatch(base, tv_p);
if (res == -1) {
event_debug(("%s: dispatch returned unsuccessfully.",
__func__));
retval = -1;
goto done;
}
/*將時間緩存更新爲當前系統時間*/
update_time_cache(base);
/* 檢查時間堆上的到期事件並依次執行之 */
timeout_process(base);
if (N_ACTIVE_CALLBACKS(base)) {
/*調用event_process_active 函數依次處理就緒的信號事件和I/O事件*/
int n = event_process_active(base);
if ((flags & EVLOOP_ONCE)
&& N_ACTIVE_CALLBACKS(base) == 0
&& n != 0)
done = 1;
} else if (flags & EVLOOP_NONBLOCK)
done = 1;
}
event_debug(("%s: asked to terminate loop.", __func__));
done:
/*事件循環結束, 清空事件緩存, 並設置停止循環標誌*/
clear_time_cache(base);
base->running_loop = 0;
EVBASE_RELEASE_LOCK(base, th_base_lock);
return (retval);
}
timeout_next——根據Timer事件計算evsel->dispatch的最大等待時間
static int
timeout_next(struct event_base *base, struct timeval **tv_p)
{
/* Caller must hold th_base_lock */
struct timeval now;
struct event *ev;
struct timeval *tv = *tv_p;
int res = 0;
/*取出最小堆中的最小節點*/
ev = min_heap_top(&base->timeheap);
/*節點爲空,直接返回 tv_p爲NULL, epoll永遠阻塞*/
if (ev == NULL) {
/* if no time-based events are active wait for I/O */
*tv_p = NULL;
goto out;
}
/*獲取當前時間*/
if (gettime(base, &now) == -1) {
res = -1;
goto out;
}
/*如果定時器的時間值小於當前時間,表明該定時器值已經過期了,不能使用, epoll永遠阻塞*/
if (evutil_timercmp(&ev->ev_timeout, &now, <=)) {
evutil_timerclear(tv);
goto out;
}
/*計算時間的差值, 該差值作爲epoll的超時時間*/
evutil_timersub(&ev->ev_timeout, &now, tv);
EVUTIL_ASSERT(tv->tv_sec >= 0);
EVUTIL_ASSERT(tv->tv_usec >= 0);
event_debug(("timeout_next: in %d seconds", (int)tv->tv_sec));
out:
return (res);
}
timeout_process—處理超時事件,將超時事件插入到激活鏈表中
把最小堆中的最小節點的時間作爲epoll的超時時間,如果超時了或者有事件發生,都循環判斷一下最小堆中的事件是否超時了,如果是,則處理timeout事件
/* Activate every event whose timeout has elapsed. */
static void
timeout_process(struct event_base *base)
{
/* Caller must hold lock. */
struct timeval now;
struct event *ev;
/*如果時間堆爲空,則退出*/
if (min_heap_empty(&base->timeheap)) {
return;
}
/*獲取當前時間*/
gettime(base, &now);
/*循環最小堆中的元素, 如果時間已經達到,則將event添加到active隊列中, 並置標記EV_TIMEOUT*/
while ((ev = min_heap_top(&base->timeheap))) {
if (evutil_timercmp(&ev->ev_timeout, &now, >))
break;
/* delete this event from the I/O queues */
event_del_internal(ev);
event_debug(("timeout_process: call %p",
ev->ev_callback));
event_active_nolock(ev, EV_TIMEOUT, 1);
}
}