目录

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解析的更多相关文章

  1. libevent源码学习

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

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

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

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

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

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

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

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

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

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

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

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

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

  8. libevent源码学习(1):日志及错误处理

    目录 错误处理函数 函数声明 __attribute__指令 函数定义 可变参数宏 _warn_helper函数 日志处理 event_log日志处理入口 日志处理回调函数指针log_fn 设置日志处 ...

  9. libevent源码学习(5):TAILQ_QUEUE解析

    目录 前言 结点定义 链表初始化 链表查询及遍历 链表查询 链表遍历 插入结点 头插法 尾插法 前插法 后插法 删除结点 替换结点 总结 前言 在libevent中使用到了TAILQ数据结构,看了一下 ...

随机推荐

  1. Codeforces 983E - NN country(贪心+倍增优化)

    Codeforces 题面传送门 & 洛谷题面传送门 一道(绝对)偏简单的 D1E,但是我怕自己过若干年(大雾)忘了自己的解法了,所以过来水篇题解( 首先考虑怎么暴力地解决这个问题,不难发现我 ...

  2. LOJ 2372 -「CEOI2002」臭虫集成电路公司(轮廓线 dp)

    题面传送门 u1s1 似乎这题全网无一题解?那就由我来写篇题解造福人类罢(伦敦雾 首先看这数据范围,一脸状压.考虑到每一层的状态与上面两层有关,因此每层转移到下一层的有用信息只有两层,需要用三进制保存 ...

  3. CMSIS-RTOS 信号量Semaphores

    信号量Semaphores 和信号类似,信号量也是一种同步多个线程的方式,简单来讲,信号量就是装有一些令牌的容器.当一个线程在执行过程中,就可能遇到一个系统调用来获取信号量令牌,如果这个信号量包含多个 ...

  4. 百页 PPT BPF 技术全览 - 深入浅出 BPF 技术

    eBPF 从创建开始,短短数年(7年),至今就已经被认为是过去 50 年来操作系统最大的变更,那么 eBPF 技术到底给我们带来了什么样的超能力,以至于得到如此高的评价? 本文从以下内容入手,对 eB ...

  5. Kubernetes:应用自动扩容、收缩与稳定更新

    在前面我们已经学习到了 Pod 的扩容.滚动更新等知识,我们可以手动为 Deployment 等设置 Pod 副本的数量,而这里会继续学习 关于 Pod 扩容.收缩 的规则,让 Pod 根据节点服务器 ...

  6. Android editttext只能输入不能删除(选中后被软键盘遮住)

    感谢https://www.dutycode.com/post-20.html: 解决方法:在布局外外嵌一层scrollview.

  7. applogs流量数据项目学习

    一. 项目介绍 项目的功能主要是面向App开发商提供App使用情况的统计服务 主要是基于用户启动app的统计分析,app只要启动就会上报一条日志记录 (启动日志),当然也会有其他的日志比如说页面访问日 ...

  8. JS模块化,Javascript 模块化管理的历史

    模块管理这个概念其实在前几年前端度过了刀耕火种年代之后就一直被提起. 直接回想起来的就是 cmd amd commonJS 这三大模块管理的印象.接下来,我们来详细聊聊. 一.什么是模块化开发 为了让 ...

  9. Hibernate 错误的问题

    配了好几次的Hibernate,老是在create BeanFactory的时候fail.我是用MyEclipse自带的HIbernate,直接加进去的. private static final T ...

  10. ReactiveCocoa操作方法-秩序

    doNext:      执行Next之前,会先执行这个Block doCompleted:      执行sendCompleted之前,会先执行这个Block - (void)doNext { [ ...