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

摘要:
目录事件_信号_映射结构导向的事件_信号-将事件添加到映射以激活事件_信号中的事件_映射中的事件删除事件_信号,与非windows环境中的事件不同,io_Map被定义为event_signal_Map,让我们先看看event_signal映射的结构。根据英文描述,条目中的元素是evmap_ Io*或evmap_ signal*

目录

event_signal_map结构体

向event_signal_map中添加event

激活event_signal_map中的event

删除event_signal_map中的event

以下源码均基于libevent-2.0.21-stable。

       在前文中分析了event_io_map,在windows环境下event_io_map定义为哈希表结构,而在非windows环境下event_io_map则定义为event_signal_map,先来看看event_signal_map的结构。
event_signal_map结构体

    struct event_signal_map {
        /* An array of evmap_io * or of evmap_signal *; empty entries are
         * set to NULL. */
        void **entries;  //数组,如果是io event元素则为evmap_io *,如果是signal event元素则为evmap_signal*
        /* The number of entries available in entries */
        int nentries;  //entries数组的容量大小
    };

        在event_signal_map中定义了一个泛型二级指针entries,由于在C语言中p[i]等价于*(p+i),因此这里的二级指针entries实际上就等价于void *entries[capacity],是一个泛型void *型的指针数组,数组中的每一个元素的类型都是void *。另一个成员nentries则用来描述entries这一数组的容量大小(不是实际元素个数,而是容量大小)。

       根据英文描述,entries中的元素要么是evmap_io *,要么就是evmap_signal *。如果是evmap_io *,那么entries中的每一个元素都指向了一个evmap_io结构体,在event_io_map数据结构分析提到,每一个evmap_io结构体中都包含了一个event的双向链表,那么在entries中就会存在nentries个event的双向链表。如果是evmap_signal,那么entries中的每一个元素都指向了一个evmap_signal机构提,该结构体定义如下:

    struct evmap_signal {
        struct event_list events;
    };

       也就是说,不管是evmap_io *还是evmap_signal *,entries中的每一个元素都指向了一个event的双向链表,而在entries中也就会存在nentries个event的双向链表。

       实际上,对于event_signal_map中每一个的io event,它们都有各自的文件描述符fd,那么它们就位于entries[fd]所对应的那个evmap_io下的双向链表中;同样的,对于event_signal_map中的每一个signal event,它们都有各自的信号值sig,因此它们就位于entries[sig]所对应的那个evmap_signal下的双向链表中。

       可见,不管entries中的元素evmap_io *还是evmap_signal *,event_signal_map的结构都是类似的,如下所示:

向event_signal_map中添加event

      从event_signal_map的结构中,大致能够推断出向event_signal_map中添加event的过程:先根据event的sig找到entries[sig],这是一个指向evmap_signal的指针,然后直接将这个event插入到evmap_signal中的event双向链表中即可。如下所示:

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

       首先需要注意到的是,这里判断了sig是否大于等于event_signal_map中的元素个数,前面说过,信号值为sig的event会最终会放在entries[sig]对应的那个双向链表中,而entries的容量为nentries,说明entries中的最后一个双向链表对应的索引就是nentries-1,如果sig超过了这个值,说明当前entries数组的长度不够,此时就需要对entries进行扩容,扩容时调用的是evmap_make_space函数,该函数定义如下:

    static int
    evmap_make_space(struct event_signal_map *map, int slot, int msize)
    {
        if (map->nentries <= slot) {
            int nentries = map->nentries ? map->nentries : 32; //如果map中没有元素就为32,否则就是其本身
            void **tmp;
     
            while (nentries <= slot) //不断加倍,直到不小于slot
                nentries <<= 1;
     
            tmp = (void **)mm_realloc(map->entries, nentries * msize);//重新分配大小
            if (tmp == NULL)
                return (-1);
     
            memset(&tmp[map->nentries], 0,
                (nentries - map->nentries) * msize);
     
            map->nentries = nentries;
            map->entries = tmp;
        }
     
        return (0);
    }

       evmap_make_space函数的slot参数在这里就是传入的sig值,如果此时entries为空,那么就暂时设置扩容后的容量为32,如果不为空,就暂时保留为原容量值,然后判断扩容后的容量是否大于传入的sig值,如果不大于就直接加倍,保证最终得到的扩容后容量值大于等于sig值。然后根据新容量值重新分配空间并初始化。这里之所以一开始将扩容后的容量值设置为32,是因为在像linux这种操作系统中,信号值的最大值只会取到31,这一点可以通过man 7 signal查看。

       扩容判断之后,GET_SIGNAL_SLOT_AND_CTOR宏,实际上就是找到event的sig对应的那个evmap_signal,并且将这个evmap_signal的指针保存在ctx中。当对应的双向链表为空时,说明没有添加信号值为sig的event,此时就会调用后端方法中的add函数将event添加到了真正的事件监听集合中(参考libevent对epoll的封装),可以发现,对于event_signal_map,相当于只是把其中的每一个双向链表的第一项添加到了真正的事件监听集合中,而在event_io_map中则是会将event_io_map中的所有event都添加到真正的事件监听集合中,这是因为相同fd的event感兴趣的读写事件是可能不一样的,而相同sig的event感兴趣的信号事件是肯定相同的,因此一旦同一信号值中有一个event激活了,那么这相同信号值下的所有event都一样需要被激活。这一点可以从event_signal_map的激活中得以验证:
激活event_signal_map中的event

       激活event_signal_map中的event使用的是evmap_signal_active函数,不过这并不是一个对用户开放的接口。实现原理就是将sig对应的双向链表中所有event都通过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为指向相应双向链表的指针
     
        TAILQ_FOREACH(ev, &ctx->events, ev_signal_next)
            event_active_nolock(ev, EV_SIGNAL, ncalls); //遍历并激活双向链表中的每个event
    }

       event_active_nolock函数可以参考事件的激活。
删除event_signal_map中的event

      根据前面event_signal_map添加event的过程,不难得出删除event的过程,如下所示:

    int
    evmap_signal_del(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;
     
        if (sig >= map->nentries)
            return (-1);
     
        GET_SIGNAL_SLOT(ctx, map, sig, evmap_signal);
     
        if (TAILQ_FIRST(&ctx->events) == TAILQ_LAST(&ctx->events, event_list)) {
            if (evsel->del(base, ev->ev_fd, 0, EV_SIGNAL, NULL) == -1)//如果sig对应的双向链表中只有最后一个event了那么才删除这个event
                return (-1);
        }
     
        TAILQ_REMOVE(&ctx->events, ev, ev_signal_next);
     
        return (1);
    }

       与添加一个event同样的特殊,在删除evmap_signal中的event时,会将该event从event_signal_map中删除,但是这个event感兴趣的signal却不一定会从真正的事件监听集合中删除。只有当其对应的双向链表中只剩下最后一个event的时候才会调用后端方法中的del函数将该signal从真正监听事件集合中删除,如前面所说的,相同sig的event,感兴趣事件都是一样的,激活也是一同被激活的,如果只删除其中某一个event,相同sig的其他event仍然保持对该signal的监听,只有当双向链表中只有最后一个event的时候,删除该event才需要将其从真正的事件监听集合中删除。
————————————————
版权声明:本文为CSDN博主「HerofH_」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_28114615/article/details/96997940

免责声明:文章转载自《libevent源码学习(8):event_signal_map解析》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇小程序支付4个可以发送完整电子邮件的命令行工具下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

windows下编译libevent(2.1.8)及使用

一:获取libevent github地址:https://github.com/libevent/libevent/releases 下载2.1.8稳定版 二:编译libevent 我是用的visual studio 2010,当然也可以使用更高的版本 启动后,进入"Libevent-release-2.1.8-stable"所在的目录 直接执行会报...

Html中Input中屏蔽Enter键

直接在html中加入脚本<input onkeydown="if(event.keyCode==13){event.keyCode=0;event.returnValue=false;}">或在js中建立函数 <script type="text/javascript" language="javascript"> function...

c# 工厂模式 ,委托 ,事件。

有些时间 不用 c#了 ,想 写 委托 和 事件 又会 卡下 ,之前也没认真总结过。干脆 做个小结 。 什么是委托:狭义,不安全函数的指针。不安全在哪里: 任何地方都可以调用委托对象。(实际上委托对象才是函数的指针,而delegate只是一个语法) 什么是事件:狭义,安全的函数指针,安全在哪里:只允许包含事件的类,的内部调用。 联系和区别: delegat...

linux下如何模拟按键输入和模拟鼠标

http://blog.chinaunix.net/u3/94700/showart_2211516.html 查看/dev/input/eventX是什么类型的事件, cat /proc/bus/input/devices设备有着自己特殊的按键键码,我需要将一些标准的按键,比如0-9,X-Z等模拟成标准按键,比如KEY_0,KEY-Z等,所以需要用到按键...

Qt 自定义事件

Qt 自定义事件很简单,同其它类库的使用很相似,都是要继承一个类进行扩展。在 Qt 中,你需要继承的类是 QEvent。 继承QEvent类,你需要提供一个QEvent::Type类型的参数,作为自定义事件的类型值。这里的QEvent::Type类型是QEvent里面定义的一个enum,因此,你是可以传递一个int的。重要的是,你的事件类型不能和已经存在的...

火狐下window.event获取undefined问题

火狐下用window.event来进行相应操作时,会显示undefined,其他浏览器可以, 解决方法: 1,传入对象 $("a").on("click",function(e){var event = e || window.event}); 2,不传入对象 $("a").on("click",function(){var event = argumen...