libevent1.4 阅读记录三
前面我们对I/O模型有了一些了解,现在再来说signal。signal事件的出现对于进程来说是随机的,libevent当信号事件发生时,不是用信号的回调函数直接处理信号,而是在回调函数中设法通知I/O模型有信号事件发生让I/O返回,将信号与I/O事件、计时器事件一起处理。
首先,看一下event_base中的(evsignal_info)sig的结构体
//evsignal.h
struct evsignal_info { struct event ev_signal; //这不是信号事件,这是socket pair的读监听事件,所有关注的信号间接关联到这个事件。 int ev_signal_pair[2]; //socket pair对,一对通过本地环回连接的socket,ev_signal_pair[0]为收到信号后回调函数发送I/O的socket,ev_signal_pair[1]为接收I/O的socket。 int ev_signal_added; //一个标记,表明这个event被注册过 volatile sig_atomic_t evsignal_caught; //一个标记,如果收到至少一个信号,置为1。sig_atomic_t意思是对这个变量的初始化、赋值都是原子的。 struct event_list evsigevents[NSIG]; //注册到signal的事件链表的数组,数组下标为对应的signal sig_atomic_t evsigcaught[NSIG]; //记录signal事件发生次数的数组,数组下标为对应的signal #ifdef HAVE_SIGACTION struct sigaction **sh_old; //记录被修改的signal回调函数及信号屏蔽状态。这是一个signal回调函数指针数组,下标对应的signal的值(例如:信号SIGINT的回调函数指针为sh_old[2])。
我们注册信号时信号回调函数被改为了向socket pair的一端发送数据,当我们注销某个信号时,需要把该信号的回调函数改回去。 #else ev_sighandler_t **sh_old; #endif int sh_old_max; //sh_old数组长度 };
接下里,从源码看一下signal流程,还是以epoll为例
1.在epoll_init中调用evsignal_init完成socekt pair的初始化并连接,并将其中的读socket注册成epoll监听事件
1 int 2 evsignal_init(struct event_base *base) 3 { 4 int i; 5 6 /* 7 * Our signal handler is going to write to one end of the socket 8 * pair to wake up our event loop. The event loop then scans for 9 * signals that got delivered. 10 */ //这段注释说的很清楚了,就是signal回调函数发送消息给这个socket pair的一端来唤醒event loop,然后event loop检查signal并处理 11 if (evutil_socketpair( 12 AF_UNIX, SOCK_STREAM, 0, base->sig.ev_signal_pair) == -1) { //在evutil.c中初始化socket pair对,并连接,发送消息的socket存在base->sig.ev_signal_pair[0],
接收消息的socket存在base->sig.ev_signal_pair[1] 13 #ifdef WIN32 14 /* Make this nonfatal on win32, where sometimes people 15 have localhost firewalled. */ 16 event_warn("%s: socketpair", __func__); 17 #else 18 event_err(1, "%s: socketpair", __func__); 19 #endif 20 return -1; 21 } 22 23 FD_CLOSEONEXEC(base->sig.ev_signal_pair[0]); //从定义来看是说在子进程中不能使用这个句柄,也就是子进程不能用这个socket 24 FD_CLOSEONEXEC(base->sig.ev_signal_pair[1]); //从定义来看是说在子进程中不能使用这个句柄,也就是子进程不能用这个socket 25 base->sig.sh_old = NULL; 26 base->sig.sh_old_max = 0; 27 base->sig.evsignal_caught = 0; 28 memset(&base->sig.evsigcaught, 0, sizeof(sig_atomic_t)*NSIG); 29 /* initialize the queues for all events */ 30 for (i = 0; i < NSIG; ++i) 31 TAILQ_INIT(&base->sig.evsigevents[i]); 32 33 evutil_make_socket_nonblocking(base->sig.ev_signal_pair[0]); 34 evutil_make_socket_nonblocking(base->sig.ev_signal_pair[1]); 35 36 event_set(&base->sig.ev_signal, base->sig.ev_signal_pair[1], 37 EV_READ | EV_PERSIST, evsignal_cb, &base->sig.ev_signal); //初始化socket pair持久化的读事件,用于接收signal回调发送I/O的消息。注意这里还没有注册,只是初始化了这个事件。 38 base->sig.ev_signal.ev_base = base; 39 base->sig.ev_signal.ev_flags |= EVLIST_INTERNAL; 40 41 return 0; 42 }
2.接下来在epoll_add中调用了evsignal_add注册了前面socket pair初始化后的读事件,在epoll_add完成后,还会将这个事件添加到已注册链表中
1 /* Helper: set the signal handler for evsignal to handler in base, so that 2 * we can restore the original handler when we clear the current one. */ 3 int 4 _evsignal_set_handler(struct event_base *base, 5 int evsignal, void (*handler)(int)) 6 { 7 #ifdef HAVE_SIGACTION 8 struct sigaction sa; 9 #else 10 ev_sighandler_t sh; 11 #endif 12 struct evsignal_info *sig = &base->sig; 13 void *p; 14 15 /* 16 * resize saved signal handler array up to the highest signal number. 17 * a dynamic array is used to keep footprint on the low side. 18 */ 19 if (evsignal >= sig->sh_old_max) { //扩充保存原来信号回调函数数组的内存,这里还没有为数组元素分配内存 20 int new_max = evsignal + 1; 21 event_debug(("%s: evsignal (%d) >= sh_old_max (%d), resizing", 22 __func__, evsignal, sig->sh_old_max)); 23 p = realloc(sig->sh_old, new_max * sizeof(*sig->sh_old)); 24 if (p == NULL) { 25 event_warn("realloc"); 26 return (-1); 27 } 28 29 memset((char *)p + sig->sh_old_max * sizeof(*sig->sh_old), 30 0, (new_max - sig->sh_old_max) * sizeof(*sig->sh_old)); 31 32 sig->sh_old_max = new_max; 33 sig->sh_old = p; 34 } 35 36 /* allocate space for previous handler out of dynamic array */ 37 sig->sh_old[evsignal] = malloc(sizeof *sig->sh_old[evsignal]); //为数组中的下标为该信号的元素分配内存,在调用_evsignal_set_handler前已经判断过目标为NULL 38 if (sig->sh_old[evsignal] == NULL) { 39 event_warn("malloc"); 40 return (-1); 41 } 42 43 /* save previous handler and setup new handler */ 44 #ifdef HAVE_SIGACTION 45 memset(&sa, 0, sizeof(sa)); 46 sa.sa_handler = handler; //这是该信号的回调函数,它已经被定义为通过socket pair发送数据。 47 sa.sa_flags |= SA_RESTART; //用于指定信号的处理行为。(SA_RESTART:使被信号打断的系统调用自动重新发起) 48 sigfillset(&sa.sa_mask); //获取之前信号屏蔽情况 49 50 if (sigaction(evsignal, &sa, sig->sh_old[evsignal]) == -1) { //设置新的回调函数及行为,并记录之前的回调函数及行为 51 event_warn("sigaction"); 52 free(sig->sh_old[evsignal]); 53 sig->sh_old[evsignal] = NULL; 54 return (-1); 55 } 56 #else 57 if ((sh = signal(evsignal, handler)) == SIG_ERR) { 58 event_warn("signal"); 59 free(sig->sh_old[evsignal]); 60 sig->sh_old[evsignal] = NULL; 61 return (-1); 62 } 63 *sig->sh_old[evsignal] = sh; 64 #endif 65 66 return (0); 67 } 68 69 int 70 evsignal_add(struct event *ev) 71 { 72 int evsignal; 73 struct event_base *base = ev->ev_base; 74 struct evsignal_info *sig = &ev->ev_base->sig; 75 76 if (ev->ev_events & (EV_READ|EV_WRITE)) 77 event_errx(1, "%s: EV_SIGNAL incompatible use", __func__); 78 evsignal = EVENT_SIGNAL(ev); //这个希望被注册信号的值 79 assert(evsignal >= 0 && evsignal < NSIG); 80 if (TAILQ_EMPTY(&sig->evsigevents[evsignal])) { //如果我们还没有注册过该信号,注册它,如果已经注册过,忽略这次注册 81 event_debug(("%s: %p: changing signal handler", __func__, ev)); 82 if (_evsignal_set_handler( 83 base, evsignal, evsignal_handler) == -1) 84 return (-1); 85 86 /* catch signals if they happen quickly */ 87 evsignal_base = base; //evsignal_base是一个全局变量,它指向的是我们的信号注册在哪个event_base实例。因此多线程用信号可能冲突,如果2个以上的线程执行到这里,但evsignal_base缺只能指向一个线程中libevent实例. 88 89 if (!sig->ev_signal_added) { //如果socket pair的读事件没有被注册就注册它,如果已注册不要重复注册(与我们正在注册的信号事件不是同一个) 90 if (event_add(&sig->ev_signal, NULL)) 91 return (-1); 92 sig->ev_signal_added = 1; 93 } 94 } 95 96 /* multiple events may listen to the same signal */ 97 TAILQ_INSERT_TAIL(&sig->evsigevents[evsignal], ev, ev_signal_next); //将这个信号事件插入到信号事件链表末尾,即使因为已注册过而没有注册成功 98 99 return (0); 100 }
1 static void 2 evsignal_handler(int sig) 3 { 4 int save_errno = errno; 5 6 if (evsignal_base == NULL) { 7 event_warn( 8 "%s: received signal %d, but have no base configured", 9 __func__, sig); 10 return; 11 } 12 13 evsignal_base->sig.evsigcaught[sig]++; //该信号发生次数加1 14 evsignal_base->sig.evsignal_caught = 1; //标记有信号事件发生 15 16 #ifndef HAVE_SIGACTION 17 signal(sig, evsignal_handler); 18 #endif 19 20 /* Wake up our notification mechanism */ 21 send(evsignal_base->sig.ev_signal_pair[0], "a", 1, 0); //socket pair 发送端发送数据 22 errno = save_errno; 23 }
3.当我们event loop过程中收到信号,信号回调函数通过socket pair发送端发送消息,因为我们将socket pair 接收端注册到了epoll监听事件中,epoll_wait将会返回。然后event loop将会调用evsignal_process来调用我们设置信号事件时给定的回调函数
1 void 2 evsignal_process(struct event_base *base) 3 { 4 struct evsignal_info *sig = &base->sig; 5 struct event *ev, *next_ev; 6 sig_atomic_t ncalls; 7 int i; 8 9 base->sig.evsignal_caught = 0; //是否有信号事件发生的标记重置为0 10 for (i = 1; i < NSIG; ++i) { 11 ncalls = sig->evsigcaught[i]; 12 if (ncalls == 0) //如果发生该信号i的次数为0,跳过该信号 13 continue; 14 sig->evsigcaught[i] -= ncalls; //重置该信号发生次数为0 15 16 for (ev = TAILQ_FIRST(&sig->evsigevents[i]); 17 ev != NULL; ev = next_ev) { //这里看上去在一个信号上可以注册多个事件,当发生时所有回调都执行一次,但事实上在我们注册的时候每个信号只能注册一个回调 18 next_ev = TAILQ_NEXT(ev, ev_signal_next); 19 if (!(ev->ev_events & EV_PERSIST)) //如果不是持久化事件,注销这个事件 20 event_del(ev); 21 event_active(ev, EV_SIGNAL, ncalls); //插入到已激活列表中,执行次数为信号发生次数 22 } 23 24 } 25 }
4.最后,我们再来看看如何注销信号事件的
1 int 2 evsignal_del(struct event *ev) 3 { 4 struct event_base *base = ev->ev_base; 5 struct evsignal_info *sig = &base->sig; 6 int evsignal = EVENT_SIGNAL(ev); //先取得要注销信号事件的信号 7 8 assert(evsignal >= 0 && evsignal < NSIG); 9 10 /* multiple events may listen to the same signal */ 11 TAILQ_REMOVE(&sig->evsigevents[evsignal], ev, ev_signal_next); 12 13 if (!TAILQ_EMPTY(&sig->evsigevents[evsignal])) 14 return (0); 15 16 event_debug(("%s: %p: restoring signal handler", __func__, ev)); 17 18 return (_evsignal_restore_handler(ev->ev_base, EVENT_SIGNAL(ev))); //注销信号事件后将信号的回调函数及处理行为等恢复 19 }