目录

信号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

libevent源码学习(15):信号event的处理的更多相关文章

  1. libevent源码学习

    怎么快速学习开源库比如libevent? libevent分析 - sparkliang的专栏 - 博客频道 - CSDN.NET Libevent源码分析 - luotuo44的专栏 - 博客频道 ...

  2. libevent源码学习(9):事件event

    目录在event之前需要知道的event_baseevent结构体创建/注册一个event向event_base中添加一个event设置event的优先级激活一个event删除一个event获取指定e ...

  3. libevent源码学习之event

    timer event libevent添加一个间隔1s持续触发的定时器如下: struct event_base *base = event_base_new(); struct event *ti ...

  4. libevent源码学习(8):event_signal_map解析

    目录event_signal_map结构体向event_signal_map中添加event激活event_signal_map中的event删除event_signal_map中的event以下源码 ...

  5. libevent源码学习(6):事件处理基础——event_base的创建

    目录前言创建默认的event_baseevent_base的配置event_config结构体创建自定义event_base--event_base_new_with_config禁用(避免使用)某一 ...

  6. libevent源码学习(7):event_io_map

    event_io_map 哈希表操作函数 hashcode与equals函数 哈希表初始化 哈希表元素查找 哈希表扩容 哈希表元素插入 哈希表元素替换 哈希表元素删除 自定义条件删除元素 哈希表第一个 ...

  7. libevent源码学习(11):超时管理之min_heap

    目录min_heap的定义向min_heap中添加eventmin_heap中event的激活以下源码均基于libevent-2.0.21-stable.       在前文中,分析了小顶堆min_h ...

  8. libevent源码学习(10):min_heap数据结构解析

    min_heap类型定义min_heap函数构造/析构函数及初始化判断event是否在堆顶判断两个event之间超时结构体的大小关系判断堆是否为空及堆大小返回堆顶event分配堆空间堆元素的上浮堆元素 ...

  9. libevent源码学习(2):内存管理

    目录 内存管理函数 函数声明 event-config.h 函数定义 event_mm_malloc_ event_mm_calloc_ event_mm_strdup_ event_mm_reall ...

随机推荐

  1. 洛谷 P3580 - [POI2014]ZAL-Freight(单调队列优化 dp)

    洛谷题面传送门 考虑一个平凡的 DP:我们设 \(dp_i\) 表示前 \(i\) 辆车一来一回所需的最小时间. 注意到我们每次肯定会让某一段连续的火车一趟过去又一趟回来,故转移可以枚举上一段结束位置 ...

  2. Codeforces 1373F - Network Coverage(模拟网络流)

    Codeforces 题面传送门 & 洛谷题面传送门 提供一个模拟网络流的题解. 首先我们觉得这题一脸可以流的样子,稍微想想可以想到如下建图模型: 建立源点 \(S,T\) 和上下两排点,不妨 ...

  3. 洛谷 P7360 -「JZOI-1」红包(Min-Max 容斥+推式子)

    洛谷题面传送门 hot tea. 首先注意到这个 \(\text{lcm}\) 特别棘手,并且这里的 \(k\) 大得离谱,我们也没办法直接枚举每个质因子的贡献来计算答案.不过考虑到如果我们把这里的 ...

  4. EXCEL ctrl+e 百变用法不只是你用的那么简单

    Excel2013版本中,新增加了一个快捷键:Ctrl+E,可以依据字符之间的关系,实现快速填充功能.一些需要使用公式或者其他功能进行解决的问题,现在只要一个快捷键就可以实现了. 用法1:快速拆解出需 ...

  5. 通过yum安装 memcache

    . 通过yum安装 复制代码代码如下: yum -y install memcached#安装完成后执行:memcached -h#出现memcached帮助信息说明安装成功 2. 加入启动服务 复制 ...

  6. C#点击按钮添加标签

    <asp:Button ID="button1" runat="server" Text="创建" onclick="But ...

  7. Spark基础:(一)初识Spark

    1.Spark中的Python和Scala的Shell (1): Python的Spark Shell 也就是我们常说的PySpark Shell进入我们的Spark目录中然后输入 bin/pyspa ...

  8. 在 Apple Silicon Mac 上 DFU 模式恢复 macOS 固件

    DFU 模式全新安装 macOS Big Sur 或 macOS Monterey 请访问原文链接:https://sysin.org/blog/apple-silicon-mac-dfu/,查看最新 ...

  9. FileReader (三) - 网页拖拽并预显示图片简单实现

    以下是一个很贱很简单的一个 在网页上图拽图片并预显示的demo. 我是从https://developer.mozilla.org/en-US/docs/Web/API/FileReader#Stat ...

  10. 【vector+pair】洛谷 P4715 【深基16.例1】淘汰赛

    题目:P4715 [深基16.例1]淘汰赛 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 这道题因为数据范围不大,所以做法可以非常简单,使用一个vector加上pair就可以了: ...