目录

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. HelloWorld与java运行机制

    HelloWorld 新建文件夹存放代码 新建一个java文件 文件后缀为.java Hello.java 注意文件拓展名改为java文件 编写代码 public class Hello{ #类名 p ...

  2. Codeforces 986E - Prince's Problem(树上前缀和)

    题面传送门 题意: 有一棵 \(n\) 个节点的树,点上有点权 \(a_i\),\(q\) 组询问,每次询问给出 \(u,v,w\),要求: \(\prod\limits_{x\in P(u,v)}\ ...

  3. 自助分析工具Power BI的简介和应用

    作为一名资深的IT技术人,特别喜欢学习和尝试新技术,也勇于接受挑战,勇于创新,不仅能发现问题,更要解决实际的疑难杂症,闲暇时光也乐于分享一些技术干货.记得2017年的时候,华章出版社的编辑通过网上找到 ...

  4. sigma网格中水平压力梯度误差及其修正

    1.水平梯度误差产生 sigma坐标系下,笛卡尔坐标内水平梯度项对应形式为 \[\begin{equation} \left. \frac{\partial }{\partial x} \right| ...

  5. 系统发育树邻接法(NJ)和非加权组平均法(UPGMA)之比较

    目录 1.原理的区别 2.实操比较 UPGMA NJ法 保存树文件 更深理解 1.原理的区别 主要区别在于,非加权组平均法(UPGMA)是基于平均链接方法的聚集层次聚类方法,而邻接法(NJ)是基于最小 ...

  6. Boussinesq 近似及静压假定,内外模分离方法(附录A)

    0.Formulation of the RANS equations [1] 不可压缩流体控制方程 \[\begin{array}{l l} \frac{\partial u}{\partial x ...

  7. C7的开机自启动设置

    CentOS 7的服务systemctl脚本存放在:/usr/lib/systemd/,有系统(system)和用户(user)之分 系统服务放在/usr/lib/systemd/system [Un ...

  8. Docker Swarm的命令

    初始化swarm manager并制定网卡地址docker swarm init --advertise-addr 192.168.10.117 强制删除集群docker swarm leave -- ...

  9. 网站性能调优实战-学相伴KuangStudy

    面对并发我们是如何优化KuangStudy网站性能的? 每个项目都会随着用户和数据的增长调整架构,来面对未来的问题,我们也不例外,在1月5号我们平台正式公测后,引起了很多观众的热烈反响,仅仅4天,注册 ...

  10. Label -- 跳出循环的思路

    let num = 0 ; outPoint: //label for (let i = 0; i < 10; i++) { for ( let j = 0; j < 10; j++) { ...