zoukankan      html  css  js  c++  java
  • libevent2源码分析之三:信号的初始化流程

    libevent2对信号的响应也进行了封装,使之与socket操作一样对外提供统一的接口。这里的信号一般指linux的信号。由于信号与socket相关的编程接口有较大的不同,因此在内部实现也有一些区别。

    IO操作(socket算作是IO操作)的evsel类似,在event_base中也定义了信号的操作变量。

    [event.h, event_base]

    const struct eventop *evsigsel;


    定义对信号的操作。

    [signal.c] 

    static const struct eventop evsigops = {

        "signal",

        NULL,

        evsig_add,

        evsig_del,

        NULL,

        NULL,

        0, 0, 0

    };

     

    信号只有adddel两个操作。在evsig_init中,用上面定义的变量对event_base->evsigsel进行了赋值。这个实现在 evsig_init中的最后几行代码。

    libevent如何将信号的通知与IO就绪的通知进行封装,对外提供统一的编程模型呢?答案是用socketpair. socketpair是一对socket,实现全双工的通讯,如果向一端的socketpair[0]写入数据,在另一端的socketpair[1]中可读取。作为信号传递的工具,在一端写入信号的值,另一端则读取。

     

    初始化调用顺序: event_base_new() -> event_base_new_with_config() -> base->evsel->init()(select_init) -> evsig_init()

    [signal.c]

    int evsig_init(struct event_base *base)

    {

        /*

         * Our signal handler is going to write to one end of the socket

         * pair to wake up our event loop.  The event loop then scans for

         * signals that got delivered.

         */

        if (evutil_socketpair(

                AF_UNIX, SOCK_STREAM, 0, base->sig.ev_signal_pair) == -1) {

            event_sock_err(1, -1, "%s: socketpair", __func__);

            return -1;

        }

     

        evutil_make_socket_closeonexec(base->sig.ev_signal_pair[0]);

        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]);

     

    // 初始化,并设置event的内部回调

        event_assign(&base->sig.ev_signal, base, base->sig.ev_signal_pair[1],

            EV_READ | EV_PERSIST, evsig_cb, base);

     

        base->sig.ev_signal.ev_flags |= EVLIST_INTERNAL;

        event_priority_set(&base->sig.ev_signal, 0);

     

        base->evsigsel = &evsigops;

        return 0;

    }

     

    上面的代码中,event_assignsocketpair[1]作为触发事件的内部fd, 并将回调关联到 evsign_cb 函数。可见利用了IO的底层实现,所有的事件的响应都会回调到evsign_cb这个函数。再看一下evsig_cb这个内部回调函数的实现。

    [signal.c]

    /* Callback for when the signal handler write a byte to our signaling socket */

    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);

    ...

            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) {

            if (ncaught[i])

                evmap_signal_active(base, i, ncaught[i]);

        }

        EVBASE_RELEASE_LOCK(base, th_base_lock);

    }

     

    主要工作就是,从socket中读取数据,然后将读取到的数据作为信号,通过evmap_signal_active通知到上层。从上面的代码分析,这是socket接收方的处理。那么作为信号的捕获者并通过socket发送消息的发送方在哪里?又做了什么些事情呢? 

     

    要响应一个linux信号,必需通过signalsigation关联一个信号量和一个响应函数。建立这种关联是在evsig_add函数中完成的。

    [signal.c]

    static int

    evsig_add(struct event_base *base, evutil_socket_t evsignal, short old, short events, void *p)

    {

        struct evsig_info *sig = &base->sig;

    ...

    /// 初始化静态变量

        evsig_base = base;

        evsig_base_n_signals_added = ++sig->ev_n_signals_added;

        evsig_base_fd = base->sig.ev_signal_pair[0];

    ...

    /// 

        if (_evsig_set_handler(base, (int)evsignal, evsig_handler) == -1) {

            goto err;

        }

    ...

    }

     

    int

    _evsig_set_handler(struct event_base *base,

        int evsignal, void (__cdecl *handler)(int))

    {

    ...

    sig->sh_old[evsignal] = mm_malloc(sizeof *sig->sh_old[evsignal]);

    ...

    #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) {

            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_add中定义了与信号量关联的响应函数是evsig_handler,其实现如下:

    static void __cdecl

    evsig_handler(int sig)

    {

    ...

        /* Wake up our notification mechanism */

        msg = sig;

        send(evsig_base_fd, (char*)&msg, 1, 0);

        errno = save_errno;

    ...

    }

     

    evsig_base_fd是socketpair[0],它是在evsig_add函数中被赋值的。

    流程是这样的:evsig_add作为入口函数,其调用流程将某个linux信号通过sigaction或signal关联到evsig_handler这个回调函数,在回调函数的内部将信号量作为数据写到socketpair[0],发送给另一端。另一端通过 socketpair[1]读取到信号后,回调保存在 base->sig.ev_signal 中的回调函数,即 evsig_cb. 在这个函数的内部,完成了将事件通知到上层,到这时候,其实现与IO的事件触发完全一样了。

     

  • 相关阅读:
    良好的三元组(求已排列好的数组中各个元素的排位)
    山理工oj 2556传说中的数据结构
    山理oj 1177 时间间隔
    山理oj1525:字符统计2
    linux常用命令
    多线程并发教程
    合理设置线程数量
    Java多线程处理任务(摘抄)
    解决2013Lost connection to MySQL server during query错误方法
    javaMail邮件发送
  • 原文地址:https://www.cnblogs.com/qkhh/p/3679439.html
Copyright © 2011-2022 走看看