• libevent源码学习(15):信号event的处理


    目录

    信号event处理流程

    与信号event相关的结构体

    初始化工作

    创建一个信号event

    添加一个信号event

    信号回调函数

    信号event的激活

           Libevent中的event,主要分为三大类:io读写event、超时事件以及信号event。前面的文章对前两类的event都进行了分析,下面就来说一下Libevent是如何处理信号event的。
    信号event处理流程

           不管使用的是什么后端IO复用模型,这些复用模型本身都是只支持读写IO事件的,Libevent所实现的“信号event处理”,实际上也是把信号event最终转换到了IO读写event。它的工作原理为:用户层面指定一个需要监听的信号sig以及回调函数custom_cb,对于Libevent,它会为sig定义一个信号处理函数evsig_handler,然后再定义一个socket pair,这是一对全双工套接字,向其中一个写入数据那么就可以从另一个中读出数据,Libevent会将其中之一固定为读端,而另一个则为写端,并为读端套接字注册一个IOevent名为ev_signal,其回调函数为evsig_cb。

          当需要监听的信号sig到达,就会自动去调用刚才设置的信号处理函数evsig_handler,在evsig_handler中会向写端套接字写入一个字节(这个字节实际上就是sig的值),此时读端套接字就会收到这个字节,触发可读事件,激活ev_signal然后调用evsig_cb,在evsig_cb函数中,会将所有监听sig信号的event全部激活,这些event中就包含根据用户要求所定义的event,处理这个event的时候就会回调custom_cb,这样,就完成了监听sig信号到回调custom_cb的信号event处理过程。如下图所示:

    与信号event相关的结构体

        struct event_base {
            ......
            const struct eventop *evsigsel;//信号处理后端相关函数
            /** Data to implement the common signal handelr code. */
            struct evsig_info sig;//信号相关信息
                ......
            /** Mapping from file descriptors to enabled (added) events */
            struct event_io_map io;
         
            /** Mapping from signal numbers to enabled (added) events. */
            struct event_signal_map sigmap;
         
            ......
        };

           前面说过,event_base在初始化的时候就会绑定一个IO复用模型后端到evsel成员中,但是对于信号event,整个处理的过程中是不会也不应该直接使用这些IO复用后端,而是使用信号event专用的后端,并且将其绑定到evsigsel中,在该后端结构体中只含有添加和删除函数,如下所示:

        static const struct eventop evsigops = {
            "signal",
            NULL,
            evsig_add,
            evsig_del,
            NULL,
            NULL,
            0, 0, 0
        };

            然后再说event_base中的sig成员,这是一个evsig_info类型的结构体变量,该类型定义如下:

        typedef void (*ev_sighandler_t)(int);
         
        struct evsig_info {
            struct event ev_signal; //需要添加到event_io_map中监听读端套接字
            /* 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;  //标识ev_signal是否被添加到io map中
            /* Count of the number of signals we're currently watching. */
            int ev_n_signals_added; //监听的信号数量
         
        #ifdef _EVENT_HAVE_SIGACTION
            struct sigaction **sh_old; //sigaction *数组,每一个元素都指向一个sigaction,其中含有信号的处理函数
        #else
            ev_sighandler_t **sh_old; //如果没有定义_EVENT_HAVE_SIGACTION,那么就是一个函数指针数组,存储每个信号的回调函数指针
        #endif
            /* Size of sh_old. */
            int sh_old_max;  //sh_old数组的大小
        };

           这里的ev_signal_pair[2]就是前面所说的socket pair全双工套接字。而evsig_info中的ev_signal,就是用于监听socket pair中读端套接字的event。sh_old是一个二级指针,也可以看做数组,它每个元素都对应了一个信号的处理函数。
    初始化工作

           对于普通的IO复用模型,是在event_base的初始化过程中将相应的后端结构体绑定到evsel中的,同样的,信号相关的后端结构体也是在event_base的初始化过程中绑定到evsigsel中的,并且还会做很多事情,如下所示:

        struct event_base *
        event_base_new_with_config(const struct event_config *cfg)
        {
            ......
            for (i = 0; eventops[i] && !base->evbase; i++) {
                ......
                base->evsel = eventops[i]; //将该backup作为base使用的backup
                base->evbase = base->evsel->init(base); //用选定的backup的初始化函数来初始化base中的evbase
            }
                ......
        }
        //以epoll后端为例
        static void *
        epoll_init(struct event_base *base)
        {
            ......
            evsig_init(base);
        }
         
        int
        evsig_init(struct event_base *base)
        {
            if (evutil_socketpair(
                    AF_UNIX, SOCK_STREAM, 0, base->sig.ev_signal_pair) == -1) {//创建了两个互相连接的套接字放到ev_signal_pair中
               ......
            }
         
            evutil_make_socket_closeonexec(base->sig.ev_signal_pair[0]);//调用exec时关闭套接字
            evutil_make_socket_closeonexec(base->sig.ev_signal_pair[1]);
            base->sig.sh_old = NULL;
            base->sig.sh_old_max = 0;
         
            evutil_make_socket_nonblocking(base->sig.ev_signal_pair[0]);//将创建的两个套接字都设置为非阻塞
            evutil_make_socket_nonblocking(base->sig.ev_signal_pair[1]);
         
            ......
        }

             在evsig_init函数中,会先调用evutil_socketpair来创建一对全双工套接字,该函数定义如下:

        int
        evutil_socketpair(int family, int type, int protocol, evutil_socket_t fd[2])//创建一对相互连接的套接字,保存在fd[2]中
        {
        #ifndef WIN32
            return socketpair(family, type, protocol, fd);
        #else
            return evutil_ersatz_socketpair(family, type, protocol, fd);
        #endif
        }
         
        int
        evutil_ersatz_socketpair(int family, int type, int protocol,
            evutil_socket_t fd[2])
        {
            /* This code is originally from Tor.  Used with permission. */
         
            /* This socketpair does not work when localhost is down. So
             * it's really not the same thing at all. But it's close enough
             * for now, and really, when localhost is down sometimes, we
             * have other problems too.
             */
        #ifdef WIN32
        #define ERR(e) WSA##e
        #else
        #define ERR(e) e
        #endif
            evutil_socket_t listener = -1;
            evutil_socket_t connector = -1;
            evutil_socket_t acceptor = -1;
            struct sockaddr_in listen_addr;
            struct sockaddr_in connect_addr;
            ev_socklen_t size;
            int saved_errno = -1;
         
            if (protocol
                || (family != AF_INET
        #ifdef AF_UNIX
                    && family != AF_UNIX
        #endif
                )) {
                EVUTIL_SET_SOCKET_ERROR(ERR(EAFNOSUPPORT));
                return -1;
            }
            if (!fd) {
                EVUTIL_SET_SOCKET_ERROR(ERR(EINVAL));
                return -1;
            }
         
            listener = socket(AF_INET, type, 0);
            if (listener < 0)
                return -1;
            memset(&listen_addr, 0, sizeof(listen_addr));
            listen_addr.sin_family = AF_INET;
            listen_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);//listen监听本地环回地址
            listen_addr.sin_port = 0;    /* kernel chooses port.     */
            if (bind(listener, (struct sockaddr *) &listen_addr, sizeof (listen_addr))
                == -1)
                goto tidy_up_and_fail;
            if (listen(listener, 1) == -1)//开始监听
                goto tidy_up_and_fail;
         
            connector = socket(AF_INET, type, 0); //创建connector套接字
            if (connector < 0)
                goto tidy_up_and_fail;
            /* We want to find out the port number to connect to.  */
            size = sizeof(connect_addr);
            if (getsockname(listener, (struct sockaddr *) &connect_addr, &size) == -1)//获取listener绑定的ip地址,保存到connect_addr中
                goto tidy_up_and_fail;
            if (size != sizeof (connect_addr))
                goto abort_tidy_up_and_fail;
            if (connect(connector, (struct sockaddr *) &connect_addr,
                        sizeof(connect_addr)) == -1)//将connector与listener连接
                goto tidy_up_and_fail;
         
            size = sizeof(listen_addr);
            acceptor = accept(listener, (struct sockaddr *) &listen_addr, &size);//通过acceptor可以向connector发送数据
            if (acceptor < 0)
                goto tidy_up_and_fail;
            if (size != sizeof(listen_addr))
                goto abort_tidy_up_and_fail;
            evutil_closesocket(listener);
            /* Now check we are talking to ourself by matching port and host on the
               two sockets.     */
            if (getsockname(connector, (struct sockaddr *) &connect_addr, &size) == -1)//确保是本地环回监听
                goto tidy_up_and_fail;
            if (size != sizeof (connect_addr)
                || listen_addr.sin_family != connect_addr.sin_family
                || listen_addr.sin_addr.s_addr != connect_addr.sin_addr.s_addr
                || listen_addr.sin_port != connect_addr.sin_port)
                goto abort_tidy_up_and_fail;
            fd[0] = connector;//通过connector向listener发信息
            fd[1] = acceptor; //通过acceptor向connector发信息
         
            return 0;
         
            ......
        }

            创建一对全双工套接字的流程是:先创建一个listener,监听本地环回端口,相当于服务端。创建一个connector作为客户端向listener发起连接,listener通过accept函数与connecotr建立连接,新连接套接字为acceptor,此时acceptor和connector就可以互发消息,成为一对全双工套接字。

           再回到evsig_init函数中,接着就会把创建的这对套接字设置为非阻塞,并且在执行exec时关闭。接下来会调用event_assign函数,设置ev_signal的监听对象就是这对套接字之一,并且监听读事件,回调函数为evsig_cb,这点就对应了前面处理流程中紫色框部分。然后就是将信号处理后端函数结构体绑定到event_base中的evsigsel上。

        int
        evsig_init(struct event_base *base)
        {
            ......
         
            event_assign(&base->sig.ev_signal, base, base->sig.ev_signal_pair[1],
                EV_READ | EV_PERSIST, evsig_cb, base);//pair[1]为读端,为其注册ev_signal,类型为永久性读事件
         
            base->sig.ev_signal.ev_flags |= EVLIST_INTERNAL;//标识为内部事件
            event_priority_set(&base->sig.ev_signal, 0);//signal event优先级为0
         
            base->evsigsel = &evsigops;//绑定信号处理后端函数调用结构体
         
            return 0;
        }

           到这里,初始化就算是完成了,关于ev_signal的回调函数后面再说,接下来就进入信号event的处理过程。
    创建一个信号event

           创建一个io event的函数是event_new,如果要创建信号event,一种方法是直接设置event_new的参数为EV_SIGNAL,不过这种方法对于用户来说是非常不友好的。为此,Libevent在event.h中进行了如下的宏定义:

        #define evsignal_add(ev, tv)        event_add((ev), (tv))
        #define evsignal_assign(ev, b, x, cb, arg)            
            event_assign((ev), (b), (x), EV_SIGNAL|EV_PERSIST, cb, (arg))
        #define evsignal_new(b, x, cb, arg)                
            event_new((b), (x), EV_SIGNAL|EV_PERSIST, (cb), (arg))
        #define evsignal_del(ev)        event_del(ev)
        #define evsignal_pending(ev, tv)    event_pending((ev), EV_SIGNAL, (tv))
        #define evsignal_initialized(ev)    event_initialized(ev)

            可以看到,这里实际上对io event的相关函数的参数进行了“特殊化处理”,最终得到了信号event的相关函数。此时,就可以通过evsignal_new函数来创建一个信号event了,实际上就是创建了一个EV_SIGNAL|EV_PERSIST的永久信号事件。而该函数内部实际上又会调用event_assign函数。需要注意的是,创建普通event时第二个参数传入的是需要监听的的文件描述符,而这里创建信号event时传入的第二个参数则应当是需要监听的信号值了,比如说需要监听的信号是SIGUSR1,那么调用evsignal_new时,传入的第二个参数就应该直接使用SIGUSR1。evsignal_new的第三个参数cb自然就应当是用户需要监听的信号发生后,期待调用的函数。

           接下来再来分析如何添加一个信号event。
    添加一个信号event

           如上所述,添加一个信号event使用evsignal_add函数,实际上就是event_add函数。前面创建的信号event,其events成员已经被设置为了EV_SIGNAL|EV_PERSIST。因此,event_add函数中会调用evmap_signal_add函数将该event添加到event_signal_map中,evmap_signal_add如下所示:

        int
        evmap_signal_add(struct event_base *base, int sig, struct event *ev)
        {
            const struct eventop *evsel = base->evsigsel;//使用的是信号回调函数结构体
            struct event_signal_map *map = &base->sigmap;
            struct evmap_signal *ctx = NULL;
         
            if (sig >= map->nentries) {
                if (evmap_make_space(
                    map, sig, sizeof(struct evmap_signal *)) == -1)
                    return (-1);
            }
            GET_SIGNAL_SLOT_AND_CTOR(ctx, map, sig, evmap_signal, evmap_signal_init,
                base->evsigsel->fdinfo_len); //ctx指向sigmap的entries[sig]对应的evmap_signal,evmap_signal中含有一个event双向链表
         
            if (TAILQ_EMPTY(&ctx->events)) {
                if (evsel->add(base, ev->ev_fd, 0, EV_SIGNAL, NULL)//调用的实际上是evsigsel中的add函数
                    == -1)
                    return (-1);
            }
         
            TAILQ_INSERT_TAIL(&ctx->events, ev, ev_signal_next);
         
            return (1);
        }

           可以看到,在通过TAILQ_INSERT_TAIL将前面创建的event添加到event_signal_map之前,会先调用信号回调后端函数中的add函数,也就是前面的evsig_add函数,该函数定义如下:

        static int
        evsig_add(struct event_base *base, evutil_socket_t evsignal, short old, short events, void *p)
        {
            struct evsig_info *sig = &base->sig;
            (void)p;
         
            EVUTIL_ASSERT(evsignal >= 0 && evsignal < NSIG);
         
            /* catch signals if they happen quickly */
            EVSIGBASE_LOCK();
            if (evsig_base != base && evsig_base_n_signals_added) {
                event_warnx("Added a signal to event base %p with signals "
                    "already added to event_base %p.  Only one can have "
                    "signals at a time with the %s backend.  The base with "
                    "the most recently added signal or the most recent "
                    "event_base_loop() call gets preference; do "
                    "not rely on this behavior in future Libevent versions.",
                    base, evsig_base, base->evsel->name);
            }
            evsig_base = base;
            evsig_base_n_signals_added = ++sig->ev_n_signals_added;
            evsig_base_fd = base->sig.ev_signal_pair[0];//pair[0]是写端
            EVSIGBASE_UNLOCK();
         
            event_debug(("%s: %d: changing signal handler", __func__, (int)evsignal));
            if (_evsig_set_handler(base, (int)evsignal, evsig_handler) == -1) {//设置信号处理函数
                goto err;
            }
         
         
            if (!sig->ev_signal_added) {//如果还没有添加ev_signal,就调用event_add添加,如果添加过了就不用再添加了
                if (event_add(&sig->ev_signal, NULL))//ev_signal的事件类型为READ|PERSIST
                    goto err;
                sig->ev_signal_added = 1;
            }
         
            return (0);
         
        err:
            EVSIGBASE_LOCK();
            --evsig_base_n_signals_added;
            --sig->ev_n_signals_added;
            EVSIGBASE_UNLOCK();
            return (-1);
        }

            evsig_add函数做了两件重要的事情:设置信号回调函数,将监听读端套接字事件的ev_signal进行添加。

            信号回调函数的设置是通过_evsig_set_handler函数实现的,实际上它内部还是用的signal或者sigaction函数,并且将设置的信号回调函数都放到了sig的sh_old数组中,数组的索引就是信号值。该函数定义如下:

        int
        _evsig_set_handler(struct event_base *base,
            int evsignal, void (__cdecl *handler)(int))
        {
        #ifdef _EVENT_HAVE_SIGACTION
            struct sigaction sa;  //存储新的信号处理信息
        #else
            ev_sighandler_t sh; //信号处理函数指针
        #endif
            struct evsig_info *sig = &base->sig;
            void *p;
         
            /*
             * resize saved signal handler array up to the highest signal number.
             * a dynamic array is used to keep footprint on the low side.
             */
            if (evsignal >= sig->sh_old_max) {//数组大小不够,就进行扩容
                int new_max = evsignal + 1;
                event_debug(("%s: evsignal (%d) >= sh_old_max (%d), resizing",
                        __func__, evsignal, sig->sh_old_max));
                p = mm_realloc(sig->sh_old, new_max * sizeof(*sig->sh_old));
                if (p == NULL) {
                    event_warn("realloc");
                    return (-1);
                }
         
                memset((char *)p + sig->sh_old_max * sizeof(*sig->sh_old),
                    0, (new_max - sig->sh_old_max) * sizeof(*sig->sh_old));
         
                sig->sh_old_max = new_max;
                sig->sh_old = p;
            }
         
            /* allocate space for previous handler out of dynamic array */
            sig->sh_old[evsignal] = mm_malloc(sizeof *sig->sh_old[evsignal]);
            if (sig->sh_old[evsignal] == NULL) {
                event_warn("malloc");
                return (-1);
            }
            //添加信号处理函数
            /* save previous handler and setup new handler */
        #ifdef _EVENT_HAVE_SIGACTION
            memset(&sa, 0, sizeof(sa));
            sa.sa_handler = handler;//信号处理函数
            sa.sa_flags |= SA_RESTART;//被信号中断的系统调用会自动重启
            sigfillset(&sa.sa_mask);//阻塞所有信号
         
            if (sigaction(evsignal, &sa, sig->sh_old[evsignal]) == -1) {//sig->sh_old[evsignal]中保存evsignal原本的信号处理函数
                event_warn("sigaction");
                mm_free(sig->sh_old[evsignal]);
                sig->sh_old[evsignal] = NULL;
                return (-1);
            }
        #else
            if ((sh = signal(evsignal, handler)) == SIG_ERR) {
                event_warn("signal");
                mm_free(sig->sh_old[evsignal]);
                sig->sh_old[evsignal] = NULL;
                return (-1);
            }
            *sig->sh_old[evsignal] = sh; //存储信号处理函数
        #endif
         
            return (0);
        }

           通过_evsig_set_handler函数,就把信号sig的回调函数设置为evsig_handler,也就是调用_evsig_set_handler的第三个参数。当信号sig达到时,就会自动调用evsig_handler函数。

           evsig_handler函数后面再说,先回到evsig_add函数中。设置好信号回调函数之后,就会将ev_signal通过event_add函数进行添加,前面说过,ev_signal的监听事件类型为EV_READ|EV_PERSIST,它用来监听socket pair中的读端套接字是否有可读事件发生。

           也就是说,通过evsig_add函数,不仅设置了信号回调函数,还设置监听读端套接字是否有可读事件发生。

           接下来再来看看信号回调函数到底做了什么事。
    信号回调函数

           evsig_handler函数定义如下所示:

        static void __cdecl
        evsig_handler(int sig)//信号处理函数,当信号发生时调用该函数
        {
            int save_errno = errno;
        #ifdef WIN32
            int socket_errno = EVUTIL_SOCKET_ERROR();
        #endif
            ev_uint8_t msg;
         
            if (evsig_base == NULL) {
                event_warnx(
                    "%s: received signal %d, but have no base configured",
                    __func__, sig);
                return;
            }
         
        #ifndef _EVENT_HAVE_SIGACTION
            signal(sig, evsig_handler);
        #endif
         
            /* Wake up our notification mechanism */
            msg = sig;
            send(evsig_base_fd, (char*)&msg, 1, 0);//向写端pair[0]写入信号值,那么读端pair[1]就会收到该信号值
            errno = save_errno;
        #ifdef WIN32
            EVUTIL_SET_SOCKET_ERROR(socket_errno);
        #endif
        }

            这个函数的功能很简单,就是向socket pair中的写端写入一个字节的数据,而这一字节就是信号的值。当这个字节通过socket pair的写端套接字写入之后,socket pair的读端就会读到该字节,读端套接字可读事件发生,监听该事件的ev_signal就会被激活。激活后调用ev_signal的回调函数evsig_cb,该函数定义如下:

        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 = recv(fd, signals, sizeof(signals), 0);//从读端pair[1]读取数据,fd是非阻塞的,如果
                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) {//遍历所有被捕获的信号,将其对应的event激活
                if (ncaught[i])
                    evmap_signal_active(base, i, ncaught[i]);
            }
            EVBASE_RELEASE_LOCK(base, th_base_lock);
        }

            evsig_cb会从读端套接字中读取数据到signals数组中,读出的每一个字节都代表一个发生的信号值,用ncaught数组来存储每个信号发生的次数,并且对于每个发生的信号,都调用evmap_signal_active进行处理,可以参考event_signal_map的激活。
    信号event的激活

           evmap_signal_active主要是找到event_signal_map中,监听信号值为sig的那一个evmap_signal,在这个evmap_signal中,包含了所有监听信号值为sig的event组成的双向链表,然后直接遍历这个双向链表,把每个元素都按照EV_SIGNAL的激活方式调用event_active_nolock函数。

        void
        evmap_signal_active(struct event_base *base, evutil_socket_t sig, int ncalls)
        {
            struct event_signal_map *map = &base->sigmap;
            struct evmap_signal *ctx;
            struct event *ev;
         
            EVUTIL_ASSERT(sig < map->nentries);
            GET_SIGNAL_SLOT(ctx, map, sig, evmap_signal);   //ctx保存信号值为sig的evmap_signal,也就是一个event的双向链表
         
            TAILQ_FOREACH(ev, &ctx->events, ev_signal_next)
                event_active_nolock(ev, EV_SIGNAL, ncalls);//遍历信号值为Sig的event双向链表,将每个event以EV_SIGNAL方式激活
        }

           而对于event_active_nolock函数来说,做的事情很少,就是把传入的event添加到激活队列中,如下所示:

        void
        event_active_nolock(struct event *ev, int res, short ncalls)
        {
            ......
            event_queue_insert(base, ev, EVLIST_ACTIVE); //将event插入到激活队列中
            ......
        }

           到这里,用来监听用户指定的信号值的那个信号event就被添加到了激活队列中,接下来,就等待主循环处理激活队列时去处理那个信号event。

        int
        event_base_loop(struct event_base *base, int flags)  
        {
                ......
                if (N_ACTIVE_CALLBACKS(base)) {  //如果激活队列中有事件
                    int n = event_process_active(base); //执行激活队列中的event相应的回调函数,返回的n是成功执行的非内部事件数目
                ......
        }
         
        static int
        event_process_active(struct event_base *base)//遍历base的激活队列中所有event,调用其回调函数
        {
            ......
            for (i = 0; i < base->nactivequeues; ++i) {  //遍历激活队列中的事件
                if (TAILQ_FIRST(&base->activequeues[i]) != NULL) {  //同一个优先级下可以有多个事件
                    base->event_running_priority = i;   //设置当前的优先级
                    activeq = &base->activequeues[i];   //获取优先级i下的所有event组成的链表
                    c = event_process_active_single_queue(base, activeq); //遍历activeq链表,调用其中每个event的回调函数
                    ......
                }
            }
        }
         
        static int
        event_process_active_single_queue(struct event_base *base,
            struct event_list *activeq)
        {
                ......
                switch (ev->ev_closure) { //在调用回调函数是否进行其他行为
                case EV_CLOSURE_SIGNAL: //信号事件回调处理方式
                    event_signal_closure(base, ev);
                    break;
                case EV_CLOSURE_PERSIST:   //对于永久事件,在调用回调函数之前会重新调用event_add来添加该事件到对应队列中
                    event_persist_closure(base, ev);
                    break;
                default:
                case EV_CLOSURE_NONE:   //对于一般事件,直接调用回调函数
                    EVBASE_RELEASE_LOCK(base, th_base_lock);//释放锁
                    (*ev->ev_callback)(
                        ev->ev_fd, ev->ev_res, ev->ev_arg);  //调用回调函数
                    break;
                }
                ......
        }
         

          激活处理的过程就是event_base_loop——event_process_active——event_process_active_single_queue,在event_process_active_single_queue函数中,会判断激活处理事件的回调关闭方式ev_closure,而对于信号event来说,在evsignal_new时由于传入的参数为EV_SIGNAL|EV_PERSIST,因此evsignal_new内部调用的event_assign会直接设置信号event的ev_closure为EV_CLOSURE_SIGNAL,也就是说,处理激活的信号event最终是通过event_signal_closure函数实现的,该函数定义如下:

        static inline void
        event_signal_closure(struct event_base *base, struct event *ev)
        {
            ......
            while (ncalls) {//信号发生了多少次就调用多少次回调函数
                ncalls--;
                ev->ev_ncalls = ncalls;
                if (ncalls == 0)
                    ev->ev_pncalls = NULL;
                (*ev->ev_callback)(ev->ev_fd, ev->ev_res, ev->ev_arg);
         
                ......
            }
        }

           这里的ncalls,实际上就是evsig_cb函数中记录的信号值sig的发生次数,信号event激活时只处理一次,但是在这一次中会根据信号发生的次数来决定调用多少次回调函数,而这里的回调函数就是用户最开始设定的“当信号发生时应当调用的函数了”。
    ————————————————
    版权声明:本文为CSDN博主「HerofH_」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/qq_28114615/article/details/97657766

  • 相关阅读:
    图片的像素和位图相关的知识
    LeetCode第四十四题-字符串匹配
    LeetCode第四十三题-字符串相乘
    LeetCode第四十二题-可装雨水的容量
    LeetCode第四十一题-寻找数组中的最小的缺失正整数
    LeetCode第四十题-求组合所有可能性(2)
    LeetCode第三十九题-求组合所有可能性
    LeetCode第三十八题-报数
    LeetCode第三十七题-实现数独
    LeetCode第三十六题-判断九宫格是否有效
  • 原文地址:https://www.cnblogs.com/cnhk19/p/14538734.html
Copyright © 2020-2023  润新知