相关结构体:
因为event_signal_map结构体实在太简单了,所以不像event_io_map那样,有一个专门的文件。由于没有专门的文件,那么只能从蛛丝马迹上探索这个event_signal_map结构了。
通过一些搜索,可以得到与event_signal_map相关联的一些结构体有下面这些:
//TAILQ_HEAD (event_list, event); event_struct.h
struct event_list
{
struct event *tqh_first;
struct event **tqh_last;
};
struct evmap_signal {
struct event_list events;
};
struct event_signal_map {
/* An array of evmap_io * or of evmap_signal *; empty entries are
* set to NULL. */
void **entries; //二级指针,evmap_signal*数组
int nentries; //元素个数
};
相对于event_io_map是一个哈希表,event_signal_map是一个数组。并且entries虽然是void**,但实际它是一个structevmap_signal指针数组。而evmap_signal成员只有一个TAILQ_HEAD (event_list, event);
由上面这些结构配合得到的结构如下所示:
从上图可以看到,event_signal_map结构体指向了一个evmap_signal指针数组,而evmap_signal结构体可以说就是一个TAILQ_QUEUE队列的队列头。通过TAILQ_QUEUE队列和图中的结构,event_signal_map就可以把event结构体都关联并管理起来。
上图中会有一个event结构体队列,原因和前一篇博文是一样的。因为同一个文件描述符fd或者信号值sig是可以多次调用event_new、event_add函数的。即存在一对多。这个队列就是把同一个fd或者sig的event连起来,方便管理。
操作函数:
虽然从上图看到event_signal_map结构是比较简单,现在还是要看一下与event_signal_map相关联的函数。先看一个内存分配函数。
//evmap.c文件
//slot是信号值sig,或者文件描述符fd.
//当sig或者fd >= map的nentries变量时就会调用此函数
static int //msize 等于 sizeof(struct evmap_signal *)
evmap_make_space(struct event_signal_map *map, int slot, int msize)
{
if (map->nentries <= slot) {
//posix标准中,信号的种类就只有32种。
//http://blog.youkuaiyun.com/luotuo44/article/details/16799607
//在Windows中,信号的种类就更少了,只有6种。
//http://msdn.microsoft.com/zh-cn/library/xdkz3x12.aspx
//所以一开始取32还是比较合理的
int nentries = map->nentries ? map->nentries : 32;
void **tmp;
//当slot是一个文件描述符时,就会大于32
while (nentries <= slot)
nentries <<= 1;
tmp = (void **)mm_realloc(map->entries, nentries * msize);
if (tmp == NULL)
return (-1);
//清零是很有必要的。因为tmp是二级指针,数组里面的元素是一个指针
memset(&tmp[map->nentries], 0,
(nentries - map->nentries) * msize);
map->nentries = nentries;
map->entries = tmp;
}
return (0);
}
在非Windows系统中,event_io_map被定义成event_signal_map,此时slot将是文件描述符fd。但对于这些遵循POSIX标准的OS来说,fd都是从0递增的比较小的值。所以代码中的while( nentrie <= slot ) nentries <<= 1; 并不会很大。对于slot为一个信号值,最大也就只有32。所以总得来说,nentries的值并不会太大。
从上面的代码也可以想到,对于参数slot,它要么是信号值sig,要么是文件描述符fd。而event_signal_map要求的数组长度一定要大于slot。那么之后给定一个sig或者fd,就可以直接通过下标操作快速定位了。这是因为一个sig或者fd就对应在数组中占有一个位置,并且sig或者fd的值等于其在数组位置的下标值。这种方式无论是管理还是代码复杂度都要比前一篇博文说到的哈希表要简单。
在Libevent中的应用:
同event_io_map一样,event_signal_map同样也是有event_signal_add函数的。不过后者比前者简单了很多。
//event.c文件
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还是GET_IO_SLOT_AND_CTOR,其作用
//都是在数组(哈希表也是一个数组)中找到fd中的一个结构。
//GET_SIGNAL_SLOT_AND_CTOR(ctx, map, sig, evmap_signal, evmap_signal_init,
//base->evsigsel->fdinfo_len);
do
{
//同event_io_map一样,同一个信号或者fd可以被多次event_new、event_add
//所以,当同一个信号或者fd被多次event_add后,entries[sig]就不会为NULL
if ((map)->entries[sig] == NULL)//第一次
{
//evmap_signal成员只有一个TAILQ_HEAD (event_list, event);
//可以说evmap_signal本身就是一个TAILQ_HEAD
//这个赋值操作很重要。
(map)->entries[sig] = mm_calloc(1, sizeof(struct evmap_signal)
+ base->evsigsel->fdinfo_len
);
if (EVUTIL_UNLIKELY((map)->entries[sig] == NULL))
return (-1);
//内部调用TAILQ_INIT(&entry->events);
(evmap_signal_init)((struct evmap_signal *)(map)->entries[sig]);
}
(ctx) = (struct evmap_signal *)((map)->entries[sig]);
} while (0);
...
//将所有有相同信号值的event连起来
TAILQ_INSERT_TAIL(&ctx->events, ev, ev_signal_next);
return (1);
}
同样,GET_SIGNAL_SLOT_AND_CTOR宏的作用就是让ctx指向structevmap_signal结构体中的TAILQ_HEAD。这样就可以使用TAILQ_INSERT_TAIL宏,把ev变量插入到队列中。如果有现成的struct evmap_signal就直接使用,没有的话就新建一个。
有一点要提出,虽然前一篇博文提到在非Windows系统中,event_io_map也被定义成了event_signal_map,但实际上他们并不会由链表连在一起。因为在event_base结构体中,分别有struct event_io_map变量io和event_signal_map变量sigmap。所有的io类型的event结构体会被放到io中,而信号类型的event则会被放到sigmap变量中。