libevent源码学习(8):event_signal_map解析
目录
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解析的更多相关文章
- libevent源码学习
怎么快速学习开源库比如libevent? libevent分析 - sparkliang的专栏 - 博客频道 - CSDN.NET Libevent源码分析 - luotuo44的专栏 - 博客频道 ...
- libevent源码学习(10):min_heap数据结构解析
min_heap类型定义min_heap函数构造/析构函数及初始化判断event是否在堆顶判断两个event之间超时结构体的大小关系判断堆是否为空及堆大小返回堆顶event分配堆空间堆元素的上浮堆元素 ...
- libevent源码学习(9):事件event
目录在event之前需要知道的event_baseevent结构体创建/注册一个event向event_base中添加一个event设置event的优先级激活一个event删除一个event获取指定e ...
- libevent源码学习(7):event_io_map
event_io_map 哈希表操作函数 hashcode与equals函数 哈希表初始化 哈希表元素查找 哈希表扩容 哈希表元素插入 哈希表元素替换 哈希表元素删除 自定义条件删除元素 哈希表第一个 ...
- libevent源码学习(11):超时管理之min_heap
目录min_heap的定义向min_heap中添加eventmin_heap中event的激活以下源码均基于libevent-2.0.21-stable. 在前文中,分析了小顶堆min_h ...
- libevent源码学习(6):事件处理基础——event_base的创建
目录前言创建默认的event_baseevent_base的配置event_config结构体创建自定义event_base--event_base_new_with_config禁用(避免使用)某一 ...
- libevent源码学习(2):内存管理
目录 内存管理函数 函数声明 event-config.h 函数定义 event_mm_malloc_ event_mm_calloc_ event_mm_strdup_ event_mm_reall ...
- libevent源码学习(1):日志及错误处理
目录 错误处理函数 函数声明 __attribute__指令 函数定义 可变参数宏 _warn_helper函数 日志处理 event_log日志处理入口 日志处理回调函数指针log_fn 设置日志处 ...
- libevent源码学习(5):TAILQ_QUEUE解析
目录 前言 结点定义 链表初始化 链表查询及遍历 链表查询 链表遍历 插入结点 头插法 尾插法 前插法 后插法 删除结点 替换结点 总结 前言 在libevent中使用到了TAILQ数据结构,看了一下 ...
随机推荐
- 深入理解Redis 数据结构—简单动态字符串sds
Redis是用ANSI C语言编写的,它是一个高性能的key-value数据库,它可以作用在数据库.缓存和消息中间件.其中 Redis 键值对中的键都是 string 类型,而键值对中的值也是有 st ...
- Codeforces 1288F - Red-Blue Graph(上下界网络流)
Codeforces 题面传送门 & 洛谷题面传送门 好久没有写过上下界网络流了,先来一题再说( 首先先假设所有边都是蓝边,那么这样首先就有 \(b\times m\) 的花费,但是这样不一定 ...
- 一次forEach 中 await 的使用
forEach 和 await/async 的问题 最近在刷面试提的时候看见这样一道题 const list = [1, 2, 3] const square = num => { return ...
- PostgreSQL 数据库备份与还原
PostgreSQL 数据库备份与还原 目录 备份 还原 栗子 备份 PostgreSQL提供的一个工具pg_dump,逻辑导出数据,生成sql文件或其他格式文件,pg_dump是一个客户端工具,可以 ...
- matplotlib 画饼图
有个瑕疵,某一块儿比例过小时,文字会重叠. 1 def pizza(data,labs,title): 2 import matplotlib 3 import matplotlib.pyplot a ...
- Python中pymysql基本使用
Python中pymysql模块通过获取mysql数据库命令行游标执行数据库命令来进行数据库操作 优点:操作数据库语句所见即所得,执行了什么数据库语句都很清楚 缺点:操作繁琐,代码量多 1. pymy ...
- 43-Reverse Nodes in k-Group
Reverse Nodes in k-Group My Submissions QuestionEditorial Solution Total Accepted: 58690 Total Submi ...
- react native安装遇到的问题
最近学习react native 是在为全栈工程师而努力,看网上把react native说的各种好,忍不住学习了一把.总体感觉还可以,特别是可以开发android和ios这点非常厉害,刚开始入门需要 ...
- 关于latex简历几个非常有用的命令
大家知道latex是最好的排版系统,用来写论文,排版非常漂亮,用来做简历可以提升逼格,下面介绍几个有用的命令 几个有用的命令 section.cventry.cvitem.cvlistit ...
- Beautiful Soup解析库的安装和使用
Beautiful Soup是Python的一个HTML或XML的解析库,我们可以用它来方便地从网页中提取数据.它拥有强大的API和多样的解析方式.官方文档:https://www.crummy.co ...