1. 程式人生 > >libevent原始碼分析(五)

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。
分離訊號捕獲,集中處理回撥函式,整個邏輯清晰簡潔,這樣的做法很值得借鑑。