libevent支持io事件,timeout事件,signal事件,這篇文件將分析libevent是如何組織signal事件,以及如何實現signal事件響應的。後端
相似於io事件,event_base有另一個hash表sigmap用於存儲signal事件,hash表使用signal number作數組索引,同一個signal number的不一樣事件使用雙向鏈表鏈接(使用struct event結構中的ev_. ev_signal. ev_signal_next成員來構造這個鏈表)。使用到的數據結構有struct event_signal_map與struct evmap_signal,定義以下:數組
/* Used to map signal numbers to a list of events. If EVMAP_USE_HT is not defined, this structure is also used as event_io_map, which maps fds to a list of events. */ struct event_signal_map { /* An array of evmap_io * or of evmap_signal *; empty entries are * set to NULL. */ void **entries; /* The number of entries available in entries */ int nentries; };
/* An entry for an evmap_signal list: notes all the events that want to know when a signal triggers. */ struct evmap_signal { struct event_dlist events; };
event_base中的成員sigmap便是struct event_signal_map結構的實例。entries指向一個動態分配的指針數組,數組長度爲nentries;數組成員是一個指向動態分配的evmap_sinal結構的指針。sigmap的結構大體能夠用圖1-1表示,數據結構
圖1-1 sigmap存儲結構socket
signal事件的後端與io事件的後端不一樣,它們的操做基本不相同,libevent在源文件signal.c中實現signal事件的後端操做,並在event_base中使用成員struct eventop *evsigsel專門指向signal事件的後端操做,未複用指向io後端的evsel指針,signal後端使用到的信息保存在struct evsig_info sig成員中。函數
struct evsig_info的定義以下,這裏主要介紹ev_signal與ev_signal_pair[2]這兩個成員。oop
/* Data structure for the default signal-handling implementation in signal.c */ struct evsig_info { /* Event watching ev_signal_pair[1] */ struct event ev_signal; /* Socketpair used to send notifications from the signal handler */ evutil_socket_t ev_signal_pair[2]; /* True iff we've added the ev_signal event yet. */ int ev_signal_added; /* Count of the number of signals we're currently watching. */ int ev_n_signals_added; /* Array of previous signal handler objects before Libevent started * messing with them. Used to restore old signal handlers. */ #ifdef EVENT__HAVE_SIGACTION struct sigaction **sh_old; #else ev_sighandler_t **sh_old; #endif /* Size of sh_old. */ int sh_old_max; };
在signal後端中,每個被註冊了事件的signal都會使用sigaction函數將signal handler修改成統一的函數static void __cdecl evsig_handler(int sig),這個函數的做用就是向ev_signal_pair[1]中寫觸發的signal_number,激活事件ev_signal。函數定義以下(刪除了跨平臺代碼):ui
static void __cdecl evsig_handler(int sig) { int save_errno = errno; ev_uint8_t msg; if (evsig_base == NULL) { event_warnx( "%s: received signal %d, but have no base configured", __func__, sig); return; } /* Wake up our notification mechanism */ msg = sig; { int r = write(evsig_base_fd, (char*)&msg, 1); (void)r; /* Suppress 'unused return value' and 'unused var' */ } errno = save_errno; }
ev_signal的回調函數也是在signal.c源文件中定義,static void evsig_cb(evutil_socket_t fd, short what, void *arg),它的做用就是從ev_signal_pair[0]上讀回被觸發的signal number,並將sigmap上對應signal_number的全部event加入到event_base的待執行回調函數中。函數定義以下:this
/* Callback for when the signal handler write a byte to our signaling socket */ static void evsig_cb(evutil_socket_t fd, short what, void *arg) { static char signals[1024]; ev_ssize_t n; int i; int ncaught[NSIG]; struct event_base *base; base = arg; memset(&ncaught, 0, sizeof(ncaught)); while (1) { n = read(fd, signals, sizeof(signals)); if (n == -1) { int err = evutil_socket_geterror(fd); if (! EVUTIL_ERR_RW_RETRIABLE(err)) event_sock_err(1, fd, "%s: recv", __func__); break; } else if (n == 0) { /* XXX warn? */ break; } for (i = 0; i < n; ++i) { ev_uint8_t sig = signals[i]; if (sig < NSIG) ncaught[sig]++; } } EVBASE_ACQUIRE_LOCK(base, th_base_lock); for (i = 0; i < NSIG; ++i) { if (ncaught[i]) evmap_signal_active_(base, i, ncaught[i]);//將signal number i 對應的事件加入到待執行回調函數鏈表中 } EVBASE_RELEASE_LOCK(base, th_base_lock); }
當signal發生時,evsig_handler會向ev_signal_pair[1]寫數據(寫入singal number),激活ev_signal事件,ev_signal事件的回調函數evsig_cb會從ev_signal_pair[0]上讀到被觸發的signal number,而後據此從event_base的sigmap中將對應的全部event的回調函數加入到待執行回調函數鏈表,這些回調函數將在event_base_loop中被執行,完成對signal事件的響應。spa