libevent原始碼分析(五)
libevent-1.4/sample/signal-test.c
event_add(&signal_int, NULL);
將 struct event signal_int新增到struct event_base* base,即註冊號監聽事件以及回撥後新增到Reactor上。
int event_add(struct event *ev, const struct timeval *tv) { struct event_base *base = ev->ev_base; const struct eventop *evsel = base->evsel; void *evbase = base->evbase; int res = 0; /* * prepare for timeout insertion further below, if we get a * failure on any step, we should not change any state. */ 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 */ } if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) && !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) { res = evsel->add(evbase, ev); if (res != -1) event_queue_insert(base, ev, EVLIST_INSERTED); } /* * we should change the timout state only if the previous event * addition succeeded. */ if (res != -1 && tv != NULL) { struct timeval now; /* * we already reserved memory above for the case where we * are not replacing an exisiting timeout. */ if (ev->ev_flags & EVLIST_TIMEOUT) 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. */ if ((ev->ev_flags & EVLIST_ACTIVE) && (ev->ev_res & EV_TIMEOUT)) { /* 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); } gettime(base, &now); evutil_timeradd(&now, tv, &ev->ev_timeout); event_queue_insert(base, ev, EVLIST_TIMEOUT); } return (res); }
注意到event_add管理了定時事件、訊號以及普通事件,而且還是用了佇列,整個結構還是很清楚的,需要關注的是
evsel->add(evbase, ev)——epoll_add(evbase, ev),在event_base_new已經呼叫了epoll_init進行一系列初始化,event_add基本上就是呼叫evsignal_add以及epoll_ctl系統呼叫。
evsignal_add——evsignal_set_handler(base, SIGINT, evsignal_handler),最終會進行訊號相關的系統呼叫:
/* save previous handler and setup new handler */ #ifdef HAVE_SIGACTION memset(&sa, 0, sizeof(sa)); sa.sa_handler = handler; sa.sa_flags |= SA_RESTART; sigfillset(&sa.sa_mask); if (sigaction(evsignal, &sa, sig->sh_old[evsignal]) == -1) { event_warn("sigaction"); free(sig->sh_old[evsignal]); sig->sh_old[evsignal] = NULL; return (-1); } #else if ((sh = signal(evsignal, handler)) == SIG_ERR) { event_warn("signal"); free(sig->sh_old[evsignal]); sig->sh_old[evsignal] = NULL; return (-1); } *sig->sh_old[evsignal] = sh; #endif
有沒有覺得sigaction以及signal很熟悉,這個就是signal的系統呼叫,至此,訊號SIGINT的回撥函式evsignal_handler就被註冊了。
很顯然,這不是使用
event_set(&signal_int, SIGINT, EV_SIGNAL|EV_PERSIST, signal_cb, &signal_int);設定的回撥函式signal_cb,那麼signal_cb怎麼樣才會被呼叫了呢?
看看evsignal_handler函式:
static void evsignal_handler(int sig) { int save_errno = errno; if (evsignal_base == NULL) { event_warn( "%s: received signal %d, but have no base configured", __func__, sig); return; } evsignal_base->sig.evsigcaught[sig]++; evsignal_base->sig.evsignal_caught = 1; #ifndef HAVE_SIGACTION signal(sig, evsignal_handler); #endif /* Wake up our notification mechanism */ send(evsignal_base->sig.ev_signal_pair[0], "a", 1, 0); errno = save_errno; }
其中關鍵部分在於
evsignal_base->sig.evsigcaught[sig]++;//sig=SIGINT
evsignal_base->sig.evsignal_caught = 1;
表明已經捕獲到訊號,使用以上訊號計數以及標誌,推遲處理回撥函式signal_cb,在 event_base_dispatch(base)中處理,下節講解,此時需要記住的是訊號已經被捕獲,並且有計數,關於計數的作用是不會遺漏相同的訊號。
最後,可以看到使用了sockpair的寫端ev_signal_pair[0],關於讀端ev_signal_pair[1]的事件註冊:
epoll_init——evsignal_init——
event_set(&base->sig.ev_signal,base->sig.ev_signal_pair [1],EV_READ|EV_PERSIST, evsignal_cb, &base->sig.ev_signal);
ev_signal_pair[1]被設定為監聽讀就緒事件,在event_base_dispatch(base)中監聽到ev_signal_pair[1]讀事件,從而喚醒event_base_dispatch中的監聽loop,evsignal_cb回撥函式的作用就是讀取sockpair中的資料,並不做任何處理,避免一直觸發讀就緒事件。
喚醒event_base_dispatch中的監聽loop後就會處理訊號捕獲標記和計數,從而執行原本註冊的回撥函式signal_cb。
分離訊號捕獲,集中處理回撥函式,整個邏輯清晰簡潔,這樣的做法很值得借鑑。