Libevent源码分析-----信号event的处理

  转载请注明出处:http://blog.csdn.net/luotuo44/article/details/38538991


信号event的工作原理:

        前面讲解了Libevent如何监听一个IO事件,现在来讲一下Libevent如何监听信号。Libevent对于信号的处理是采用统一事件源的方式。简单地说,就是把信号也转换成IO事件,集成到Libevent中。

        统一事件源的工作原理如下:假如用户要监听SIGINT信号,那么在实现的内部就对SIGINT这个信号设置捕抓函数。此外,在实现的内部还要建立一条管道(pipe),并把这个管道加入到多路IO复用函数中。当SIGINT这个信号发生后,捕抓函数将会被调用。而这个捕抓函数的工作就是往管道写入一个字符(这个字符往往等于所捕抓到信号的信号值)。此时,这个管道就变成是可读的了,多路IO复用函数能检测到这个管道变成可读的了。换言之,多路IO复用函数检测到SIGINT信号的发生,也就完成了对信号的监听工作。这个过程如下图所示:

        

        了解完统一事件源的工作原理,现在来看一下Libevent具体的实现细节。按照上述的介绍,内部实现的工作有:

  1. 创建一个管道(Libevent实际上使用的是socketpair)
  2. 为这个socketpair的一个读端创建一个event,并将之加入到多路IO复用函数的监听之中
  3. 设置信号捕抓函数
  4. 有信号发生,就往socketpair写入一个字节

        统一事件源能够工作的一个原因是:多路IO复用函数都是可中断的。即处理完信号后,会从多路IO复用函数中退出,并将errno赋值为EINTR。有些OS的某些系统调用,比如Linux的read,即使被信号终端了,还是会自启动的。即不会从read函数中退出来。


用于信号event的结构体和变量:

        event_base为信号监听提供了的成员如下:

  1. //event-internal.h文件  
  2. struct event_base {  
  3.   
  4.     const struct eventop *evsigsel;  
  5.     struct evsig_info sig;  
  6.   
  7.     ...  
  8.     struct event_signal_map sigmap;  
  9.     ...  
  10. };  
  11.   
  12.   
  13. //evsignal-internal.h文件  
  14. struct evsig_info {  
  15.     //用于监听socketpair读端的event. ev_signal_pair[1]为读端  
  16.     struct event ev_signal;  
  17.     //socketpair  
  18.     evutil_socket_t ev_signal_pair[2];  
  19.     //用来标志是否已经将ev_signal这个event加入到event_base中了  
  20.     int ev_signal_added;  
  21.     //用户一共要监听多少个信号  
  22.     int ev_n_signals_added;  
  23.   
  24.     //数组。用户可能已经设置过某个信号的信号捕抓函数。但  
  25.     //Libevent还是要为这个信号设置另外一个信号捕抓函数,  
  26.     //此时,就要保存用户之前设置的信号捕抓函数。当用户不要  
  27.     //监听这个信号时,就能够恢复用户之前的捕抓函数。  
  28.     //因为是有多个信号,所以得用一个数组保存。  
  29. #ifdef _EVENT_HAVE_SIGACTION  
  30.     struct sigaction **sh_old;   
  31. #else//保存的是捕抓函数的函数指针,又因为是数组。所以是二级指针  
  32.     ev_sighandler_t **sh_old;   
  33. #endif  
  34.     /* Size of sh_old. */  
  35.     int sh_old_max; //数组的长度  
  36. };  

        在上面代码中,已经可以看到用于socketpair的ev_signal_pair变量,还有struct event结构体变量ev_signal。那么Libevent是在何时创建socketpair以及如何将socketpair和ev_signal相关联的呢?


初始化:

        在前面的博文《 跨平台Reactor接口的实现》中,介绍了Libevent是如何选择一个多路IO复用函数的。在选定一个多路IO复用函数后,就会调用下面一行代码。
  1. base->evbase = base->evsel->init(base);  
        这是初始化代码函数。下面给出poll的init函数。
  1. //poll.c文件    
  2. static void *  
  3. poll_init(struct event_base *base)  
  4. {  
  5.     struct pollop *pollop;  
  6.   
  7.     if (!(pollop = mm_calloc(1, sizeof(struct pollop))))  
  8.         return (NULL);  
  9.   
  10.     evsig_init(base);  
  11.   
  12.     return (pollop);  
  13. }  

        可以看到,其调用了evsig_init函数。而正是这个evsig_init函数完成了创建socketpair并将socketpair的一个读端与ev_signal相关联。

  1. //signal.c文件  
  2. int  
  3. evsig_init(struct event_base *base)  
  4. {  
  5.     //创建一个socketpair  
  6.     if (evutil_socketpair(  
  7.             AF_UNIX, SOCK_STREAM, 0, base->sig.ev_signal_pair) == -1) {  
  8. #ifdef WIN32  
  9.         /* Make this nonfatal on win32, where sometimes people 
  10.            have localhost firewalled. */  
  11.         event_sock_warn(-1, "%s: socketpair", __func__);  
  12. #else  
  13.         event_sock_err(1, -1, "%s: socketpair", __func__);  
  14. #endif  
  15.         return -1;  
  16.     }  
  17.   
  18.     //子进程不能访问该socketpair  
  19.     evutil_make_socket_closeonexec(base->sig.ev_signal_pair[0]);  
  20.     evutil_make_socket_closeonexec(base->sig.ev_signal_pair[1]);  
  21.     base->sig.sh_old = NULL;  
  22.     base->sig.sh_old_max = 0;  
  23.   
  24.   
  25.     evutil_make_socket_nonblocking(base->sig.ev_signal_pair[0]);  
  26.     evutil_make_socket_nonblocking(base->sig.ev_signal_pair[1]);  
  27.   
  28.     //将ev_signal_pair[1]与ev_signal这个event相关联。ev_signal_pair[1]为读端  
  29.     //该函数的作用等同于event_new。实际上event_new内部也是调用event_assign函数完成工作的  
  30.     event_assign(&base->sig.ev_signal, base, base->sig.ev_signal_pair[1],  
  31.         EV_READ | EV_PERSIST, evsig_cb, base);  
  32.   
  33.     //标明是内部使用的  
  34.     base->sig.ev_signal.ev_flags |= EVLIST_INTERNAL;  
  35.     //Libevent中,event是有优先级的。前一篇博文已经说到这一点  
  36.     event_priority_set(&base->sig.ev_signal, 0); //最高优先级  
  37.   
  38.     base->evsigsel = &evsigops;  
  39.   
  40.     return 0;  
  41. }  

        socketpair的两个端都调用evutil_make_socket_closeonexec,因为不能让子进程可以访问的这个socketpair。因为子进程的访问可能会出现扰乱。比如,子进程往socketpair发送信息,使得父进程的多路IO复用函数误以为信号发生了;父进程确实发生了信号,也往socketpair发送了一个字节,但却被子进程接收了这个字节。父进程没有监听到可读。

        在Windows中,并没有直接的可以使用的socketpair API。此时,Libevent就自己实现了一个socketpair。具体可以参考《通用类型和函数》。

 

        在函数的最后可以看到event_base的一个成员evsignal被赋值。evsignal是一个IO复用结构体,而evsigops是专门用于信号处理的 IO复用结构体变量。定义如下:
  1. //signal.c文件  
  2. static const struct eventop evsigops = {  
  3.     "signal",  
  4.     NULL,  
  5.     evsig_add,  
  6.     evsig_del,  
  7.     NULL,  
  8.     NULL,  
  9.     0, 0, 0  
  10. };  

        该结构体只有evsig_add和evsig_del这两个函数指针。实际在工作时有这两个函数就足够了。


将信号event加入到event_base:

        前面的代码已经完成了“创建socketpair并将socketpair的一个读端于ev_signal相关联”。接下来看其他的工作。假如要对一个绑定了某个信号的event调用event_add函数,那么在event_add的内部会调用event_add_internal函数。而event_add_internal函数又会调用evmap_signal_add函数。如果看了之前的博文,应该对这个流程不陌生。下面看看evmap_signal_add函数:
  1. //evmap.c文件  
  2. int  
  3. evmap_signal_add(struct event_base *base, int sig, struct event *ev)  
  4. {  
  5.     //注意这里调用的是base的evsigsel变量。而不是evsel。  
  6.     const struct eventop *evsel = base->evsigsel;  
  7.     struct event_signal_map *map = &base->sigmap;  
  8.       
  9.     ...  
  10.   
  11.     if (TAILQ_EMPTY(&ctx->events)) {  
  12.         //实际调用的是evsig_add函数  
  13.         if (evsel->add(base, ev->ev_fd, 0, EV_SIGNAL, NULL)  
  14.             == -1)  
  15.             return (-1);  
  16.     }  
  17.   
  18.     return (1);  
  19. }  

        上面函数的内部调用了IO复用结构体的add函数指针,即调用了evsig_add。现在我们深入evsig_add函数。

  1. /signal.c文件  
  2. static int  
  3. evsig_add(struct event_base *base, evutil_socket_t evsignal, short old, short events, void *p)  
  4. {  
  5.     struct evsig_info *sig = &base->sig;  
  6.     (void)p;  
  7.   
  8.     //NSIG是信号的个数。定义在系统头文件中  
  9.     EVUTIL_ASSERT(evsignal >= 0 && evsignal < NSIG);  
  10.   
  11.     /* catch signals if they happen quickly */  
  12.     //加锁保护。但实际其锁变量为NULL。所以并没有保护。应该会在以后的版本有所改正  
  13.     //在2.1.4-alpha版本中,就已经改进了这个问题。为锁变量分配了锁  
  14.     EVSIGBASE_LOCK();  
  15.     //如果有多个event_base,那么捕抓信号这个工作只能由其中一个完成。  
  16.     if (evsig_base != base && evsig_base_n_signals_added) {  
  17.         event_warnx("Added a signal to event base %p with signals "  
  18.             "already added to event_base %p.  Only one can have "  
  19.             "signals at a time with the %s backend.  The base with "  
  20.             "the most recently added signal or the most recent "  
  21.             "event_base_loop() call gets preference; do "  
  22.             "not rely on this behavior in future Libevent versions.",  
  23.             base, evsig_base, base->evsel->name);  
  24.     }  
  25.     evsig_base = base;  
  26.     evsig_base_n_signals_added = ++sig->ev_n_signals_added;  
  27.     evsig_base_fd = base->sig.ev_signal_pair[0]; //写端。0是写端,这确实与之前所接触到的有所不同  
  28.     EVSIGBASE_UNLOCK();  
  29.   
  30.     //设置Libevent的信号捕抓函数  
  31.     if (_evsig_set_handler(base, (int)evsignal, evsig_handler) == -1) {  
  32.         goto err;  
  33.     }  
  34.   
  35.     //event_base第一次监听信号事件。要添加ev_signal到event_base中  
  36.     if (!sig->ev_signal_added) {  
  37.         //注意,本函数的调用路径为event_add->event_add_internal->evmap_signal_map->evsig_add  
  38.         //所以这里是递归调用event_add函数。而event_add函数是会加锁的。所以需要锁为递归锁  
  39.         if (event_add(&sig->ev_signal, NULL))//添加一个内部的event  
  40.             goto err;  
  41.         sig->ev_signal_added = 1;  
  42.     }  
  43.   
  44.     return (0);  
  45.   
  46. err:  
  47.     EVSIGBASE_LOCK();  
  48.     --evsig_base_n_signals_added;  
  49.     --sig->ev_n_signals_added;  
  50.     EVSIGBASE_UNLOCK();  
  51.     return (-1);  
  52. }  

        从后面的那个if语句可以得知,当sig->ev_signal_added变量为0时(即用户第一次监听一个信号),就会将ev_signal这个event加入到event_base中。从前面的“统一事件源”可以得知,这个ev_signal的作用就是通知event_base,有信号发生了。只需一个event即可完成工作,即使用户要监听多个不同的信号,因为这个event已经和socketpair的读端相关联了。如果要监听多个信号,那么就在信号处理函数中往这个socketpair写入不同的值即可。event_base能监听到可读,并可以从读到的内容可以判断是哪个信号发生了。

       从代码中也可得知,Libevent并不会为每一个信号监听创建一个event。它只会创建一个全局的专门用于监听信号的event。这个也是“统一事件源”的工作原理。

 

设置信号捕抓函数:

       evsig_add函数还调用了_evsig_set_handler函数完成设置Libevent内部的信号捕抓函数。

  1. //signal.c文件  
  2. typedef void (*ev_sighandler_t)(int);  
  3.   
  4. //evsignal是信号值,handler是信号捕抓函数  
  5. int  
  6. _evsig_set_handler(struct event_base *base,  
  7.     int evsignal, void (__cdecl *handler)(int))  
  8. {  
  9.     //如果有sigaction就优先使用之  
  10. #ifdef _EVENT_HAVE_SIGACTION  
  11.     struct sigaction sa;  
  12. #else  
  13.     ev_sighandler_t sh;  
  14. #endif  
  15.     struct evsig_info *sig = &base->sig;  
  16.     void *p;  
  17.   
  18.   
  19.     //数组的一个元素就存放一个信号。信号值等于其下标  
  20.     if (evsignal >= sig->sh_old_max) { //不够内存。重新分配  
  21.         int new_max = evsignal + 1;  
  22.         event_debug(("%s: evsignal (%d) >= sh_old_max (%d), resizing",  
  23.                 __func__, evsignal, sig->sh_old_max));  
  24.         p = mm_realloc(sig->sh_old, new_max * sizeof(*sig->sh_old));  
  25.         if (p == NULL) {  
  26.             event_warn("realloc");  
  27.             return (-1);  
  28.         }  
  29.   
  30.         memset((char *)p + sig->sh_old_max * sizeof(*sig->sh_old),  
  31.             0, (new_max - sig->sh_old_max) * sizeof(*sig->sh_old));  
  32.   
  33.         sig->sh_old_max = new_max;  
  34.         sig->sh_old = p;  
  35.     }  
  36.   
  37.     //注意sh_old是一个二级指针。元素是一个一级指针。为这个一级指针分配内存  
  38.     /* allocate space for previous handler out of dynamic array */  
  39.     sig->sh_old[evsignal] = mm_malloc(sizeof *sig->sh_old[evsignal]);  
  40.     if (sig->sh_old[evsignal] == NULL) {  
  41.         event_warn("malloc");  
  42.         return (-1);  
  43.     }  
  44.   
  45.     /* save previous handler and setup new handler */  
  46. #ifdef _EVENT_HAVE_SIGACTION  
  47.     memset(&sa, 0, sizeof(sa));  
  48.     sa.sa_handler = handler;  
  49.     sa.sa_flags |= SA_RESTART;  
  50.     sigfillset(&sa.sa_mask);  
  51.   
  52.     //设置信号处理函数  
  53.     if (sigaction(evsignal, &sa, sig->sh_old[evsignal]) == -1) {  
  54.         event_warn("sigaction");  
  55.         mm_free(sig->sh_old[evsignal]);  
  56.         sig->sh_old[evsignal] = NULL;  
  57.         return (-1);  
  58.     }  
  59. #else  
  60.     //设置信号处理函数  
  61.     if ((sh = signal(evsignal, handler)) == SIG_ERR) {  
  62.         event_warn("signal");  
  63.         mm_free(sig->sh_old[evsignal]);  
  64.         sig->sh_old[evsignal] = NULL;  
  65.         return (-1);  
  66.     }  
  67.      //保存之前的信号捕抓函数。当用户event_del这个信号监听后,就可以恢复了  
  68.     *sig->sh_old[evsignal] = sh;  
  69. #endif  
  70.   
  71.     return (0);  
  72. }  
        如果看过《UNIX环境高级编程》信号那章的话,上面这段代码很容易看懂。这里就不讲了。


        这里我们做一个猜测:当我们对某个信号进行event_newevent_add后,就不应该再次设置该信号的信号捕抓函数。否则event_base将无法监听到信号的发生。下面代码验证这猜测。

  1. #include<unistd.h>  
  2. #include<stdio.h>  
  3. #include<signal.h>  
  4. #include<event.h>  
  5.   
  6.   
  7. void sig_cb(int fd, short events, void *arg)  
  8. {  
  9.     printf("in the sig_cb\n");  
  10. }  
  11.   
  12. void signal_handle(int sig)  
  13. {  
  14.     printf("catch the sig %d\n", sig);  
  15. }  
  16.   
  17. int main()  
  18. {  
  19.   
  20.     struct event_base *base = event_base_new();  
  21.   
  22.     struct event *ev = evsignal_new(base, SIGUSR1, sig_cb, NULL);  
  23.     event_add(ev, NULL);  
  24.   
  25.     signal(SIGUSR1, signal_handle);  
  26.   
  27.     printf("pid = %d\n", getpid());  
  28.   
  29.     printf("begin\n");  
  30.     event_base_dispatch(base);  
  31.     printf("end\n");  
  32.   
  33.     return 0;  
  34. }  

        运行上面代码, 通过在外部给这个进程发生信号的方式。可以看到,event_base确实无法监听到信号了。所有信号都被signal_handle捕抓了。


捕抓信号:

        前面的代码中有两个函数并没有讲,分别是信号捕抓函数evsig_handler和调用event_assign时的信号回调函数evsig_cb。
  1. //signal.c文件  
  2. static void __cdecl  
  3. evsig_handler(int sig)  
  4. {  
  5.     ...  
  6.     ev_uint8_t msg;  
  7.   
  8.     if (evsig_base == NULL) {  
  9.         event_warnx(  
  10.             "%s: received signal %d, but have no base configured",  
  11.             __func__, sig);  
  12.         return;  
  13.     }  
  14.   
  15. #ifndef _EVENT_HAVE_SIGACTION  
  16.     //这主要是为了应对旧时代的信号不可靠  
  17.     //现在的OS并不会出现这个问题  
  18.     signal(sig, evsig_handler);  
  19. #endif  
  20.   
  21.     /* Wake up our notification mechanism */  
  22.     msg = sig;  
  23.     send(evsig_base_fd, (char*)&msg, 1, 0); //向socketpair写入一个字节  
  24.   
  25.     ...  
  26. }  

        从evsig_handler函数的实现可以看到,实现得相当简单。只是将信号对应的值写入到socketpair中。evsig_base_fd是socketpair的写端,这是一个全局变量,在evsig_add函数中被赋值的。

        从“统一事件源”的工作原理来看,现在已经完成了对信号的捕抓,已经将该信号的当作IO事件写入到socketpair中了。现在event_base应该已经监听到socketpair可读了,并且会为调用回调函数evsig_cb了。下面看看evsig_cb函数。

  1. //signal.c文件  
  2. static void  
  3. evsig_cb(evutil_socket_t fd, short what, void *arg)  
  4. {  
  5.     static char signals[1024];  
  6.     ev_ssize_t n;  
  7.     int i;  
  8.   
  9.     //NSIG是信号的个数      
  10.     int ncaught[NSIG];  
  11.     struct event_base *base;  
  12.   
  13.     base = arg;  
  14.   
  15.     memset(&ncaught, 0, sizeof(ncaught));  
  16.   
  17.     while (1) {  
  18.         //读取socketpair中的数据。从中可以知道有哪些信号发生了  
  19.         //已经socketpair的读端已经设置为非阻塞的。所以不会被阻塞在  
  20.         //recv函数中。这个循环要把socketpair的所有数据都读取出来  
  21.         n = recv(fd, signals, sizeof(signals), 0);  
  22.         if (n == -1) {  
  23.             int err = evutil_socket_geterror(fd);  
  24.             if (! EVUTIL_ERR_RW_RETRIABLE(err))  
  25.                 event_sock_err(1, fd, "%s: recv", __func__);  
  26.             break;  
  27.         } else if (n == 0) {  
  28.             /* XXX warn? */  
  29.             break;  
  30.         }  
  31.   
  32.         //遍历数据数组,把每一个字节当作一个信号  
  33.         for (i = 0; i < n; ++i) {  
  34.             ev_uint8_t sig = signals[i];  
  35.             if (sig < NSIG)  
  36.                 ncaught[sig]++; //该信号发生的次数  
  37.         }  
  38.     }  
  39.   
  40.     EVBASE_ACQUIRE_LOCK(base, th_base_lock);  
  41.     for (i = 0; i < NSIG; ++i) {  
  42.         if (ncaught[i]) //有信号发生就为之调用evmap_signal_active  
  43.             evmap_signal_active(base, i, ncaught[i]);  
  44.     }  
  45.     EVBASE_RELEASE_LOCK(base, th_base_lock);  
  46. }  

        该回调函数的作用是读取socketpair的所有数据,并将数据当作信号,再根据信号值调用evmap_signal_active。

        有一点要注意,evsig_cb这个回调函数并不是用户为监听一个信号调用event_new时设置的用户回调函数,而是Libevent内部为了处理信号而设置的内部回调函数。累!!


激活信号event:

        虽然如此,但是现在的情况是:当有信号发生时,就会调用evmap_signal_active函数。
  1. //event-internal.h文件  
  2. #define ev_signal_next  _ev.ev_signal.ev_signal_next  
  3.   
  4. #define ev_ncalls   _ev.ev_signal.ev_ncalls  
  5. #define ev_pncalls  _ev.ev_signal.ev_pncalls  
  6.   
  7. //evmap.c文件  
  8. void //后两个参数分别是信号值和发生的次数。  
  9. evmap_signal_active(struct event_base *base, evutil_socket_t sig, int ncalls)  
  10. {  
  11.     struct event_signal_map *map = &base->sigmap;  
  12.     struct evmap_signal *ctx;  
  13.     struct event *ev;  
  14.   
  15.     //通过这个fd找到对应的TAILQ_HEAD  
  16.     GET_SIGNAL_SLOT(ctx, map, sig, evmap_signal);  
  17.   
  18.     //遍历该fd的队列  
  19.     TAILQ_FOREACH(ev, &ctx->events, ev_signal_next)  
  20.         event_active_nolock(ev, EV_SIGNAL, ncalls);  
  21. }  
  22.   
  23.   
  24. //event.c文件  
  25. void  
  26. event_active_nolock(struct event *ev, int res, short ncalls)  
  27. {  
  28.     struct event_base *base;  
  29.   
  30.   
  31.     base = ev->ev_base;  
  32.     ev->ev_res = res;  
  33.   
  34.     //这将停止处理低优先级的event。一路回退到event_base_loop中。  
  35.     if (ev->ev_pri < base->event_running_priority)  
  36.         base->event_continue = 1;  
  37.   
  38.     if (ev->ev_events & EV_SIGNAL) {  
  39. #ifndef _EVENT_DISABLE_THREAD_SUPPORT  
  40.         if (base->current_event == ev && !EVBASE_IN_THREAD(base)) {  
  41.             ++base->current_event_waiters;  
  42.             //由于此时是主线程执行,所以并不会进行这个判断里面  
  43.             EVTHREAD_COND_WAIT(base->current_event_cond, base->th_base_lock);  
  44.         }  
  45. #endif  
  46.         ev->ev_ncalls = ncalls;  
  47.         ev->ev_pncalls = NULL;  
  48.     }  
  49.   
  50.     //插入到激活队列中.插入到队尾  
  51.     event_queue_insert(base, ev, EVLIST_ACTIVE);  
  52.   
  53. }  
        通过evmap_signal_active、event_active_nolock和event_queue_insert这三个函数的调用后,就可以把一个event插入到激活队列了。


        由于这些函数的执行本身就是在Libevent处理event的回调函数之中的(Libevent正在处理内部的信号处理event)。所以并不需要从event_base_loop里的while循环里面再次执行一次evsel->dispatch(),才能执行到这次信号event。即无需等到下一次处理激活队列,就可以执行该信号event了。分析如下:

        首先要明确,现在执行上面三个函数相当于在执行event的回调函数。所以其是运行在event_process_active函数之中的。为什么是在这里,可以参考《Libevent工作流程探究》一文。

  1. //event.c文件  
  2. static int  
  3. event_process_active(struct event_base *base)  
  4. {  
  5.     struct event_list *activeq = NULL;  
  6.     int i, c = 0;  
  7.   
  8.     //从高优先级到低优先级遍历优先级数组  
  9.     for (i = 0; i < base->nactivequeues; ++i) {  
  10.         //对于特定的优先级,遍历该优先级的所有激活event  
  11.         if (TAILQ_FIRST(&base->activequeues[i]) != NULL) {  
  12.             base->event_running_priority = i;  
  13.             activeq = &base->activequeues[i];  
  14.             c = event_process_active_single_queue(base, activeq);  
  15.         }  
  16.     }  
  17.   
  18.     return c;  
  19. }  
  20.   
  21. static int  
  22. event_process_active_single_queue(struct event_base *base,  
  23.     struct event_list *activeq)  
  24. {  
  25.     struct event *ev;  
  26.   
  27.     //遍历该优先级的所有event,并处理之  
  28.     for (ev = TAILQ_FIRST(activeq); ev; ev = TAILQ_FIRST(activeq)) {  
  29.   
  30.         ...//开始处理这个event。会调用event的回调函数  
  31.     }  
  32.   
  33. }  

        从上面的代码可以看到,Libevent在处理内部的那个信号处理event的回调函数时,其实是在event_process_active_single_queue的一个循环里面。因为Libevent内部的信号处理event的优先级最高优先级,并且在前面的将用户信号event插入到队列(即event_queue_insert),在插入到队列的尾部。所以无论用户的这个信号event的优先级是多少,都是在Libevent的内部信号处理event的后面。所以在遍历上面两个函数的里外两个循环时,肯定会执行到用户的信号event。


执行已激活信号event:

        现在看看Libevent是怎么处理已激活的信号event的。

  1. //event.c文件  
  2. static inline void  
  3. event_signal_closure(struct event_base *base, struct event *ev)  
  4. {  
  5.     short ncalls;  
  6.     int should_break;  
  7.   
  8.     /* Allows deletes to work */  
  9.     ncalls = ev->ev_ncalls;  
  10.     if (ncalls != 0)  
  11.         ev->ev_pncalls = &ncalls;  
  12.   
  13.     //while循环里面会调用用户设置的回调函数。该回调函数可能会执行很久  
  14.     //所以要解锁先.  
  15.     EVBASE_RELEASE_LOCK(base, th_base_lock);  
  16.     //如果该信号发生了多次,那么就需要多次执行回调函数  
  17.     while (ncalls) {  
  18.         ncalls--;  
  19.         ev->ev_ncalls = ncalls;  
  20.         if (ncalls == 0)  
  21.             ev->ev_pncalls = NULL;  
  22.         (*ev->ev_callback)(ev->ev_fd, ev->ev_res, ev->ev_arg);  
  23.   
  24.         EVBASE_ACQUIRE_LOCK(base, th_base_lock);  
  25.         //其他线程调用event_base_loopbreak函数中断之  
  26.         should_break = base->event_break;   
  27.         EVBASE_RELEASE_LOCK(base, th_base_lock);  
  28.   
  29.         if (should_break) {  
  30.             if (ncalls != 0)  
  31.                 ev->ev_pncalls = NULL;  
  32.             return;  
  33.         }  
  34.     }  
  35. }  
        可以看到,如果对应的信号发生了多次,那么该信号event的回调函数将被执行多次。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值