Libev源码分析08:Libev中的信号监视器
Libev中的信号监视器,用于监控信号的发生,因信号是异步的,所以Libev的处理方式是尽量的将异步信号同步化。异步信号的同步化方法主要有:signalfd、eventfd、pipe、sigwaitinfo等。这里Libev采用的是前三种方法,最终都是将对异步信号的处理,转化成对文件描述符的处理,也就是将ev_signal转化为处理ev_io。
一:数据结构
1:ev_signal
typedef struct ev_signal { int active; int pending; int priority; void *data; void (*cb)(EV_P_ struct ev_signal *w, int revents); struct ev_watcher_list *next; int signum; } ev_signal;
ev_signal的结构跟ev_io的结构十分类似,前6个成员是完全一样的,最后一个signum记录信号值。前六个成员构成了一个ev_watcher_list结构,因此信号监视器也是按照链表组织的。
2:ANSIG
typedef struct { sig_atomic_t volatile pending; #if EV_MULTIPLICITY struct ev_loop *loop; #endif ev_watcher_list *head; } ANSIG; static ANSIG signals [EV_NSIG - 1];
ANSIG就是Libev内部用来组织ev_signal的结构体,它的成员包括:pending表明该信号是否处于未决状态(触发但尚未处理),head表明该信号对应的监视器链表的头指针,另外,Libev不允许同一个信号出现在多个ev_loop结构中,因此,如果支持多个ev_loop的话,还有一个loop成员记录该信号对应的ev_loop。
signals是ANSIG类型的数组,它的下标就是相应的信号值-1,因此,每个信号都有对应的ANSIG结构。
二:初始化ev_signal
#define ev_signal_set(ev,signum_) do {\ (ev)->signum = (signum_); \ } while (0) #define ev_signal_init(ev,cb,signum) do {\ ev_init ((ev), (cb)); \ ev_signal_set ((ev), (signum)); \ } while (0)
三:使用signalfd处理信号
各个系统支持的信号同步机制各有不同,针对多种信号同步机制,Libev采用下面的优先级循序:signalfd、eventfd、pipe。
signalfd是最简单方便的信号同步机制,可以很容易的将异步的信号的监听转化成对文件描述符的监听。下面首先看一下使用signalfd时的信号处理流程。
1:ev_signal_start
void ev_signal_start (struct ev_loop *loop, ev_signal *w) { if (expect_false (ev_is_active (w))) return; assert (("libev: ev_signal_start called with illegal signal number", w->signum > 0 && w->signum < EV_NSIG)); #if EV_MULTIPLICITY assert (("libev: a signal must not be attached to two different loops", !signals [w->signum - 1].loop || signals [w->signum - 1].loop == loop)); signals [w->signum - 1].loop = loop; #endif #if EV_USE_SIGNALFD if (sigfd == -2) { sigfd = signalfd (-1, &sigfd_set, SFD_NONBLOCK | SFD_CLOEXEC); if (sigfd < 0 && errno == EINVAL) sigfd = signalfd (-1, &sigfd_set, 0); /* retry without flags */ if (sigfd >= 0) { fd_intern (sigfd); /* doing it twice will not hurt */ sigemptyset (&sigfd_set); ev_io_init (&sigfd_w, sigfdcb, sigfd, EV_READ); ev_set_priority (&sigfd_w, EV_MAXPRI); ev_io_start (EV_A_ &sigfd_w); ev_unref (EV_A); /* signalfd watcher should not keep loop alive */ } } if (sigfd >= 0) { sigaddset (&sigfd_set, w->signum); sigprocmask (SIG_BLOCK, &sigfd_set, 0); signalfd (sigfd, &sigfd_set, 0); } #endif ev_start (EV_A_ (W)w, 1); wlist_add (&signals [w->signum - 1].head, (WL)w); ... }
在ev_signal_start中,首先对信号监视器w进行验证:
如果它已经被激活,也就是w->active不为0,直接返回;
信号监视器中的信号值应该处于合法范围(0, EV_NSIG)内,否则进程退出;
该信号对应的ANSIG结构中记录的ev_loop,应该就是参数loop,否则进程退出;
验证完成后,首先记录一下该信号所在的ev_loop:
signals [w->signum - 1].loop = loop;
然后,根据宏EV_USE_SIGNALFD判断系统是否支持signalfd函数,根据loop->sigfd的值判断用户是否使用signalfd函数,在初始化ev_loop的函数loop_init (struct ev_loop *loop, unsigned intflags)中有:
sigfd = flags & EVFLAG_SIGNALFD ? -2 : -1;
因此,用户如果想使用signalfd函数,flags参数中必须有EVFLAG_SIGNALFD,也就是在使用ev_default_loop或者ev_loop_new初始化ev_loop时,必须指明EVFLAG_SIGNALFD标志。
如果系统支持signalfd,并且loop->sigfd为-2的话,则开始调用signalfd函数(这是第一次调用signalfd),创建signalfd文件描述符。如果signalfd支持SFD_NONBLOCK和SFD_CLOEXEC标志的话,则直接在signalfd中设置,否则调用fd_intern,使用fcntl设置(即使signalfd支持这俩标志,也会调用该函数重新设置一遍,无伤大雅)。注意,第一次调用signalfd时,信号集(sigset_t)sigfd_set尚未初始化,在下面初始化。
第一次调用signalfd成功之后,首先清空信号集sigfd_set,然后将signalfd文件描述符加入到Libev内部的IO监视器(ev_io)sigfd_w中,并且启动sigfd_w,注意这里设置sigfd_w的优先级为最高优先级,回调函数为sigfdcb:
sigemptyset (&sigfd_set); ev_io_init (&sigfd_w, sigfdcb, sigfd, EV_READ); ev_set_priority (&sigfd_w, EV_MAXPRI); ev_io_start (EV_A_ &sigfd_w); ev_unref (EV_A); /* signalfd watcher should not keep loop alive */
接下来,将信号w->signum加入到信号集sigfd_set中,阻塞该信号,重新关联signalfd和sigfd_set:
sigaddset (&sigfd_set, w->signum); sigprocmask (SIG_BLOCK, &sigfd_set, 0); signalfd (sigfd, &sigfd_set, 0);
之所以不在调用signalfd的时候阻塞该信号并关联signalfd描述符,是因为所有信号仅使用一个signalfd描述符sigfd,一个IO监视器sigfd_w。当有多个信号监视器时,需要多次调用ev_signal_start,在第一次调用ev_signal_start成功之后,sigfd的值便已是大于等于0的整数了,这样只需要将信号阻塞,然后重新关联sigfd即可,而无需重新创建一个signalfd描述符并加入到IO监视器sigfd_w中。
最后,激活该信号监视器w,并且将其加入到相应的ANSIG结构中:
ev_start (EV_A_ (W)w, 1); wlist_add (&signals [w->signum - 1].head, (WL)w);
这样,如果使用signalfd监控信号,ev_signal_start函数的流程就结束了。接下来,就是监控IO监视器sigfd_w了。当sigfd_set中的一个或多个信号发生时,sigfd变成可读状态,IO监视器sigfd_w触发,在ev_run中,调用ev_invoke_pending时,就会调用它的回调函数sigfdcb。ev_invoke_pending的代码如下:
void ev_invoke_pending (struct ev_loop *loop) { pendingpri = NUMPRI; while (pendingpri) /* pendingpri possibly gets modified in the inner loop */ { --pendingpri; while (pendingcnt [pendingpri]) { ANPENDING *p = pendings [pendingpri] + --pendingcnt [pendingpri]; p->w->pending = 0; EV_CB_INVOKE (p->w, p->events); EV_FREQUENT_CHECK; } } }
该函数中,首先从最高优先级的pendings开始轮训,依次调用其中监视器的回调函数。因IO监视器sigfd_w具有最高优先级,因此如果信号触发了,则sigfd_w的回调函数sigfdcb会首先被调用到。
2:sigfdcb
static void sigfdcb (struct ev_loop *loop, ev_io *iow, int revents) { struct signalfd_siginfo si[2], *sip; /* these structs are big */ for (;;) { ssize_t res = read (sigfd, si, sizeof (si)); /* not ISO-C, as res might be -1, but works with SuS */ for (sip = si; (char *)sip < (char *)si + res; ++sip) ev_feed_signal_event (EV_A_ sip->ssi_signo); if (res < (ssize_t)sizeof (si)) break; } }
该回调函数中,主要是读取sigfd中的信号信息。因struct signalfd_siginfo结构比较大(128字节),这里采用的技巧是每次read时最多只读取2个。
针对读取到的信号值,调用ev_feed_signal_event函数。
3:ev_feed_signal_event
void ev_feed_signal_event (struct ev_loop *loop, int signum) { WL w; if (expect_false (signum <= 0 || signum >= EV_NSIG)) return; --signum; #if EV_MULTIPLICITY /* it is permissible to try to feed a signal to the wrong loop */ /* or, likely more useful, feeding a signal nobody is waiting for*/ if (expect_false (signals [signum].loop != EV_A)) return; #endif signals [signum].pending = 0; for (w = signals [signum].head; w; w = w->next) ev_feed_event (EV_A_ (W)w, EV_SIGNAL); }
在ev_feed_signal_event中,首先检查信号值是否处于合法范围(0, EV_NSIG)内,然后检查该信号对应的ev_loop是否就是当前的loop,如果不是则直接返回。
然后置signals [signum].pending为0,在signals中找到该信号的监视器列表,针对该列表中的所有监视器,调用ev_feed_event,将监视器加入到loop->pendings中。
注意,此时添加信号监视器到loop->pendings的流程,还是处于ev_invoke_pending函数的流程中的,因此,在ev_invoke_pending中,处理完sigfd_w监视器后,接着就会处理到刚刚加到loop->pendings的信号监视器。从而信号自己的回调函数就会被调用到。
这样使用signalfd监控信号的完整流程就结束了。
四:使用eventfd、pipe处理信号
使用eventfd和pipe处理信号的基本思路是一样的,首先创建eventfd描述符或者管道pipe,使用IO监视器监听eventfd描述符或者pipe[0],当信号发生时时,在信号处理程序中,写入eventfd描述符或者pipe[1],从而触发IO监视器,调用回调函数pipecb处理信号。
1:ev_signal_start
首先看下,当不使用signalfd,或者调用signalfd失败时,ev_signal_start的流程:
void ev_signal_start (struct ev_loop *loop, ev_signal *w) EV_THROW { if (expect_false (ev_is_active (w))) return; assert (("libev: ev_signal_start called with illegal signal number", w->signum > 0 && w->signum < EV_NSIG)); #if EV_MULTIPLICITY assert (("libev: a signal must not be attached to two different loops", !signals [w->signum - 1].loop || signals [w->signum - 1].loop == loop)); signals [w->signum - 1].loop = EV_A; #endif ... ev_start (EV_A_ (W)w, 1); wlist_add (&signals [w->signum - 1].head, (WL)w); if (!((WL)w)->next) # if EV_USE_SIGNALFD if (sigfd < 0) /*TODO*/ # endif { struct sigaction sa; evpipe_init (EV_A); sa.sa_handler = ev_sighandler; sigfillset (&sa.sa_mask); sa.sa_flags = SA_RESTART; /* if restarting works we save one iteration*/ sigaction (w->signum, &sa, 0); if (origflags & EVFLAG_NOSIGMASK) { sigemptyset (&sa.sa_mask); sigaddset (&sa.sa_mask, w->signum); sigprocmask (SIG_UNBLOCK, &sa.sa_mask, 0); } } }首先对信号监视器w进行验证,然后激活该信号监视器w,并且将其加入到相应的ANSIG结构中,该过程与使用signalfd的流程一样,不再赘述。
接下来,如果该监视器是当前信号的第一个监视器(((WL)w)->next == NULL),说明这是第一次监听该信号,需要创建该信号的信号处理函数,并且创建eventfd或pipe结构。
首先调用evpipe_init初始化eventfd或pipe结构,暂且不表,下面详述。
然后调用sigaction建立该信号的处理函数为ev_sighandler,并且在调用信号处理函数时,阻塞所有信号,且被信号中断的低速系统调用会被重启
sa.sa_handler = ev_sighandler; sigfillset (&sa.sa_mask); sa.sa_flags = SA_RESTART; /* if restarting works we save one iteration */ sigaction (w->signum, &sa, 0);
如果在初始化ev_loop时指定了EVFLAG_NOSIGMASK标志的话,还需要明确将监听的信号解除阻塞。
if (origflags & EVFLAG_NOSIGMASK) { sigemptyset (&sa.sa_mask); sigaddset (&sa.sa_mask, w->signum); sigprocmask (SIG_UNBLOCK, &sa.sa_mask, 0); }
2:evpipe_init
该函数用来创建eventfd描述符或者pipe,并将信号监视器转换为IO监视器pipe_w。代码如下:
static void evpipe_init (struct ev_loop *loop) { if (!ev_is_active (&pipe_w)) { int fds [2]; # if EV_USE_EVENTFD fds [0] = -1; fds [1] = eventfd (0, EFD_NONBLOCK | EFD_CLOEXEC); if (fds [1] < 0 && errno == EINVAL) fds [1] = eventfd (0, 0); if (fds [1] < 0) # endif { while (pipe (fds)) ev_syserr ("(libev) error creating signal/async pipe"); fd_intern (fds [0]); } evpipe [0] = fds [0]; if (evpipe [1] < 0) evpipe [1] = fds [1]; /* first call, set write fd */ else { /* on subsequent calls, do not change evpipe [1] */ /* so that evpipe_write can always rely on its value. */ /* this branch does not do anything sensible on windows, */ /* so must not be executed on windows */ dup2 (fds [1], evpipe [1]); close (fds [1]); } fd_intern (evpipe [1]); ev_io_set (&pipe_w, evpipe [0] < 0 ? evpipe [1] : evpipe [0], EV_READ); ev_io_start (EV_A_ &pipe_w); ev_unref (EV_A); /* watcher should not keep loop alive */ } }
所有信号使用一个IO监视器pipe_w,如果pipe_w已经处于激活状态,则说明相应的结构已经创建好了,直接返回即可。
然后根据宏EV_USE_EVENTFD判断系统是否支持eventfd,如果支持,则调用eventfd创建eventfd描述符,如果不支持,或者调用eventfd失败,则调用pipe创建管道,并设置管道读端描述符的FD_CLOEXEC和O_NONBLOCK标志。
使用evpipe[0]记录读描述符,evpipe[1]记录写描述符,如果使用eventfd,则evpipe[0]为-1,evpipe[1]为eventfd描述符,读写描述符都是eventfd。
调用fd_intern,使用fcntl设置写描述符evpipe[1]的FD_CLOEXEC和O_NONBLOCK标志。
最后启动内部IO监视器pipe_w,监听读描述符:
ev_io_set (&pipe_w, evpipe [0] < 0 ? evpipe [1] : evpipe [0], EV_READ); ev_io_start (EV_A_ &pipe_w); ev_unref (EV_A); /* watcher should not keep loop alive */
注意,pipe_w监视器的初始化,在loop_init中就已经做了:
#if EV_SIGNAL_ENABLE || EV_ASYNC_ENABLE ev_init (&pipe_w, pipecb); ev_set_priority (&pipe_w, EV_MAXPRI); #endif这里设置pipe_w的回调函数为pipecb,优先级为最高优先级EV_MAXPRI。
当信号产生时,就会调用到信号处理函数ev_sighandler,改处理函数仅仅就是调用函数ev_feed_signal而已。
3:ev_feed_signal
void ev_feed_signal (int signum) { #if EV_MULTIPLICITY struct ev_loop *loop; loop = signals [signum - 1].loop; if (!loop) return; #endif signals [signum - 1].pending = 1; evpipe_write (loop, &sig_pending); }
该函数首先根据信号值得到该信号所在的ev_loop,然后置该信号对应的ANSIG的pending为1,最后调用evpipe_write函数。
4:evpipe_write
void evpipe_write (struct ev_loop *loop, sig_atomic_t volatile *flag) { if (expect_true (*flag)) return; *flag = 1; pipe_write_skipped = 1; if (pipe_write_wanted) { int old_errno; pipe_write_skipped = 0; old_errno = errno; /* save errno because write will clobber it*/ #if EV_USE_EVENTFD if (evpipe [0] < 0) { uint64_t counter = 1; write (evpipe [1], &counter, sizeof (uint64_t)); } else #endif { write (evpipe [1], &(evpipe [1]), 1); } errno = old_errno; } }
注意,当在一个loop中有多个信号发生时,也仅需要产生一个事件而已。本函数主要作用就是当信号发生时向写描述符写入一个事件,即可触发pipe_w中的读事件,表明有一个或者多个信号触发了。但是因为信号处理函数的调用时机是完全随机的,因此,需要有一定的手段保证代码的安全性。
a:sig_pending
该值表示是否有信号处于未决状态(触发但尚未处理)。该值在初始化ev_loop时置为0,调用evpipe_write时,会首先判断该值是否为1。如果该值已经为1,表示已经有监听的信号处于未决状态了,无需再向写描述符写入事件了。该值直到调用pipe_w的回调函数pipecb时,消费掉该事件之后才重置为0,表明从此刻起,若有监听信号触发,才能继续向写描述符写入事件。
b:pipe_write_wanted和pipe_write_skipped
pipe_write_wanted表明是否允许向写描述符写入事件,pipe_write_skipped表明是否信号发生了,却因pipe_write_wanted的关系被暂时忽略了。这两个值在初始化ev_loop时置为0。
在evpipe_write中,首先置pipe_write_skipped为1,如果pipe_write_wanted此时为0,evpipe_write直接返回,这就表明触发信号暂时被忽略掉了(仅仅是暂时的)。否则,重置pipe_write_skipped为0,向写描述符写入事件。
在ev_run中,调用backend_poll之前,会将pipe_write_wanted置为1,表明此刻起写描述符才能接受写入事件,如果此刻之前有监听信号发生的话,则会在信号处理函数调用的evpipe_write中,置pipe_write_skipped为1表示信号暂时忽略掉。
在ev_run中调用backend_poll后立即置pipe_write_wanted为0。如果pipe_write_skipped为1,表明有信号被忽略了,调用ev_feed_event,直接将pipe_w标记为pending状态,将pipe_w加入到loop->pendings中:
do{ ev_tstamp waittime = 0.; pipe_write_wanted = 1; if (expect_true (!(flags & EVRUN_NOWAIT || idleall || !activecnt || pipe_write_skipped))) { waittime = MAX_BLOCKTIME; ... } backend_poll (EV_A_ waittime); pipe_write_wanted = 0; /* just an optimisation, no fence needed */ if (pipe_write_skipped) { assert (("libev: pipe_w not active, but pipe not written", ev_is_active (&pipe_w))); ev_feed_event (EV_A_ &pipe_w, EV_CUSTOM); } EV_INVOKE_PENDING; }while(expect_true ( activecnt && !loop_done && !(flags & (EVRUN_ONCE | EVRUN_NOWAIT)) ))
上面就是ev_run中的相关逻辑,注意:调用backend_poll之后,如果在检测pipe_write_skipped之后才有信号发生的话,此时在evpipe_write中仅设置pipe_write_skipped为1后就返回。然后进入下次循环,因pipe_write_skipped为1,所以waittime为0,backend_poll会立即返回,处理pipe_w的激活事件。
PS:现在还没有想明白,为什么需要pipe_write_wanted和pipe_write_skipped这两个标志,感觉sig_pending已经足够了。
在向写描述符写入事件之后,pipe_w监视器触发,调用回调函数pipecb。
5:pipecb
static void pipecb (struct ev_loop *loop, ev_io *iow, int revents) { int i; if (revents & EV_READ) { #if EV_USE_EVENTFD if (evpipe [0] < 0) { uint64_t counter; read (evpipe [1], &counter, sizeof (uint64_t)); } else #endif { char dummy[4]; read (evpipe [0], &dummy, sizeof (dummy)); } } pipe_write_skipped = 0; #if EV_SIGNAL_ENABLE if (sig_pending) { sig_pending = 0; for (i = EV_NSIG - 1; i--; ) if (expect_false (signals [i].pending)) ev_feed_signal_event (EV_A_ i + 1); } #endif ... }
在pipecb中,首先从eventfd描述符或者pipe[0]中消费掉事件。然后轮训signals数组中每个ANSIG结构的pending字段,只要是信号触发了,则该字段一定为1,从而可以调用ev_feed_signal_event处理该信号。剩下的流程就与使用signalfd时一样了,不再赘述。
四:ev_signal_stop
void ev_signal_stop (struct ev_loop *loop, ev_signal *w) { clear_pending (EV_A_ (W)w); if (expect_false (!ev_is_active (w))) return; wlist_del (&signals [w->signum - 1].head, (WL)w); ev_stop (EV_A_ (W)w); if (!signals [w->signum - 1].head) { #if EV_MULTIPLICITY signals [w->signum - 1].loop = 0; /* unattach from signal */ #endif #if EV_USE_SIGNALFD if (sigfd >= 0) { sigset_t ss; sigemptyset (&ss); sigaddset (&ss, w->signum); sigdelset (&sigfd_set, w->signum); signalfd (sigfd, &sigfd_set, 0); sigprocmask (SIG_UNBLOCK, &ss, 0); } else #endif signal (w->signum, SIG_DFL); } }
在ev_signal_stop中,首先调用clear_pending清除监视器w在loop->pendings中的状态,置w->pending = 0。这里有个技巧是将loop->pendings中,原w所在位置直接赋值为内部伪监视器pending_w,pending_w的回调函数为空函数,会直接返回。
调用wlist_del,将w从该信号的监视器列表中删除,调用ev_stop注销改监视器;
如果相应信号的监视器列表空了,则首先signals [w->signum - 1].loop =0,然后恢复该信号的处理方式:若使用signalfd,则取消阻塞该信号,将该信号从sigfd描述符关联的信号集中删除;若不使用signalfd,则直接恢复该信号的处理方式为默认方式。
五:总结
六:例子
ev_signal signal_w; void signal_action(struct ev_loop *main_loop,ev_signal *signal_w,int e) { puts("\nin signal cb \n"); } int main() { struct ev_loop *main_loop = ev_default_loop(EVFLAG_SIGNALFD); ev_init(&signal_w,signal_action); ev_signal_set(&signal_w,SIGINT); ev_signal_start(main_loop,&signal_w); ev_run(main_loop,0); return 0; }
结果:
#./a.out ^C in signal cb ^C in signal cb ^\Quit (core dumped)