目录

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. 8.3 k8s部署jenkins,通过pv/pvc结合NFS服务器持久化

    1.制作jenkins docker镜像 1.1 下载jenkins wget https://mirrors.tuna.tsinghua.edu.cn/jenkins/war-stable/2.30 ...

  2. 全面了解一致性哈希算法及PHP代码实现

    在设计一个分布式系统的架构时,为了提高系统的负载能力,需要把不同的数据分发到不同的服务节点上.因此这里就需要一种分发的机制,其实就是一种算法,来实现这种功能.这里我们就用到了Consistent Ha ...

  3. vue项目中使用 SheetJS / js-xlsx 导出文件

    1.  npm install xlsx 2. 在App.vue 中引入xlsx import * as XLSX from 'xlsx'; // 数据导出导入所需要的依赖  3.  使用xlsx 3 ...

  4. CF1562E Rescue Niwen!

    开始的时候只会一个\(O(n^2log)\) 即做出所有的\(n^2\)串,显然可以用\(SAM\)来进行这样一个排序,然后\(log\)做. 但这种题我们显然要找一些友好的性质: 我们发现字符串的比 ...

  5. 洛谷 P5044 - [IOI2018] meetings 会议(笛卡尔树+DP+线段树)

    洛谷题面传送门 一道笛卡尔树的 hot tea. 首先我们考虑一个非常 naive 的区间 DP:\(dp_{l,r}\) 表示区间 \([l,r]\) 的答案,那么我们考虑求出 \([l,r]\) ...

  6. Matlab混合编程

    Matlab混合编程 混合编程目的 在Matlab中采用混合编程目的主要包括 利用已有的函数库,避免重复工作 加速计算,特别是减少循环所用时间 利用GPU等进行异构编程 混合编程方法-mex函数 目前 ...

  7. Spark(十)【RDD的读取和保存】

    目录 一.文件类型 1.Text文件 2.Json文件 3.对象文件 4.Sequence文件 二.文件系统 1. MySQL 2. Hbase 一.文件类型 1.Text文件 读写 读取 scala ...

  8. C++ 继续(3n+1)猜想

    1005 继续(3n+1)猜想 (25分)   卡拉兹(Callatz)猜想已经在1001中给出了描述.在这个题目里,情况稍微有些复杂. 当我们验证卡拉兹猜想的时候,为了避免重复计算,可以记录下递推过 ...

  9. 【SpringBoot】几种定时任务的实现方式

    SpringBoot 几种定时任务的实现方式 Wan QingHua 架构之路  定时任务实现的几种方式: Timer:这是java自带的java.util.Timer类,这个类允许你调度一个java ...

  10. 【编程思想】【设计模式】【其他模式】blackboard

    Python版 https://github.com/faif/python-patterns/blob/master/other/blackboard.py #!/usr/bin/env pytho ...