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)

posted @ 2015-11-08 09:32  gqtc  阅读(944)  评论(0编辑  收藏  举报