前言
在本小节中,我们将看到event是如何注册到event_base上的,以及如何注销,如何激活等操作,最后还会梳理一下事件的状态是如何变化的。
event_add
int
event_add(struct event *ev, const struct timeval *tv)
{
struct event_base *base = ev->ev_base;
const struct eventop *evsel = base->evsel;
void *evbase = base->evbase;
int res = 0;
event_debug((
"event_add: event: %p, %s%s%scall %p",
ev,
ev->ev_events & EV_READ ? "EV_READ " : " ",
ev->ev_events & EV_WRITE ? "EV_WRITE " : " ",
tv ? "EV_TIMEOUT " : " ",
ev->ev_callback));
assert(!(ev->ev_flags & ~EVLIST_ALL));
/*
* prepare for timeout insertion further below, if we get a
* failure on any step, we should not change any state.
*/
//如果是个定时事件,在管理定时事件的小根堆上预留一个位置。因为如果有位置,定时事件就不会注册失败。如果没有位置了,那么在这里就先退出了,不会影响到其他地方。原子操作。
if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {
if (min_heap_reserve(&base->timeheap,
1 + min_heap_size(&base->timeheap)) == -1)
return (-1); /* ENOMEM == errno */
}
//如果事件未注册或激活并且是读/写/信号事件则注册事件(实质上就是调用相关多路I/O复用机制的add函数,比如epoll,则是epoll_ctl)
if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&
!(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {
res = evsel->add(evbase, ev);
//注册成功后,将event插入到注册链表中
if (res != -1)
event_queue_insert(base, ev, EVLIST_INSERTED);
}
/*
* we should change the timout state only if the previous event
* addition succeeded.
*/
//添加定时事件(所以之前才说事件之间可以进行或操作,这是因为一个I/O事件或信号事件也可能是定时事件)
if (res != -1 && tv != NULL) {
struct timeval now;
/*
* we already reserved memory above for the case where we
* are not replacing an exisiting timeout.
*/
//如果已经在定时事件中,那么移除,添加新的
if (ev->ev_flags & EVLIST_TIMEOUT)
event_queue_remove(base, ev, EVLIST_TIMEOUT);
/* Check if it is active due to a timeout. Rescheduling
* this timeout before the callback can be executed
* removes it from the active list. */
//检测当前事件的状态是激活态并且激活的事件类型是定时事件(意思就是是因为超时了,然后该事件被激活的)
//如果是,先从激活事件链表中移出再重新设置定时事件;如果不是,则在其还未超时之前将其插入到管理定时事件的小根堆中
if ((ev->ev_flags & EVLIST_ACTIVE) &&
(ev->ev_res & EV_TIMEOUT)) {
/* See if we are just active executing this
* event in a loop
*/
//将调用的次数设置为0
if (ev->ev_ncalls && ev->ev_pncalls) {
/* Abort loop */
*ev->ev_pncalls = 0;
}
//执行到此处证明该事件已经是因为超时而被激活过的了,将其移出
event_queue_remove(base, ev, EVLIST_ACTIVE);
}
//计算超时的时间,并插入到小根堆中
gettime(base, &now);
evutil_timeradd(&now, tv, &ev->ev_timeout);
event_debug((
"event_add: timeout in %ld seconds, call %p",
tv->tv_sec, ev->ev_callback));
event_queue_insert(base, ev, EVLIST_TIMEOUT);
}
return (res);
}
这个注册event的函数大体上的逻辑还是比较清晰。需要关注的是:
- 定时事件比较特殊,需要先在小根堆上预留一个位置,如果失败了,接下来也不用继续了
- 当定时事件已经在小根堆是直接移除后再添加,这是因为定时的时间可能不同,那么处于小根堆的位置也不同
- 当该定时事件已经因为超时而被激活了,则从管理激活事件的链表中移出再插入到管理超时事件的小根堆中
- 注册事件的实质就是让对应的多路I/O机制进行监听,并将其插入到相应的事件链表/小根堆中。
event_del
int
event_del(struct event *ev)
{
struct event_base *base;
const struct eventop *evsel;
void *evbase;
event_debug(("event_del: %p, callback %p",
ev, ev->ev_callback));
/* An event without a base has not been added */
if (ev->ev_base == NULL)
return (-1);
base = ev->ev_base;
evsel = base->evsel;
evbase = base->evbase;
assert(!(ev->ev_flags & ~EVLIST_ALL));
/* See if we are just active executing this event in a loop */
if (ev->ev_ncalls && ev->ev_pncalls) {
/* Abort loop */
*ev->ev_pncalls = 0;
}
if (ev->ev_flags & EVLIST_TIMEOUT)
event_queue_remove(base, ev, EVLIST_TIMEOUT);
if (ev->ev_flags & EVLIST_ACTIVE)
event_queue_remove(base, ev, EVLIST_ACTIVE);
if (ev->ev_flags & EVLIST_INSERTED) {
event_queue_remove(base, ev, EVLIST_INSERTED);
//如果是I/O或signal事件,由于是对应的多路I/O机制在监听,所以注销的时候,也应该取消监听
return (evsel->del(evbase, ev));
}
return (0);
}
这个函数没什么好讲的,无非就是将调用回调函数次数清0并且将该事件从管理各种事件的链表/小根堆上移出,不过如果是已注册的I/O或信号事件,最后一步需要用到对应的多路I/O机制的取消监听的函数。
接下来我们可以来看看event_queue_insert和event_queue_remove这两个函数,因为前面两个函数都有调用。
event_queue_insert
void
event_queue_insert(struct event_base *base, struct event *ev, int queue)
{
if (ev->ev_flags & queue) {
/* Double insertion is possible for active events */
//这是为了避免重复的事件。因为虽然一个事件可以有多种类型,如同时是I/O和定时事件,但是该事件处于激活状态是绝对唯一的。
if (queue & EVLIST_ACTIVE)
return;
event_errx(1, "%s: %p(fd %d) already on queue %x", __func__,
ev, ev->ev_fd, queue);
}
//事件的数量+1
if (~ev->ev_flags & EVLIST_INTERNAL)
base->event_count++;
//接下来就是根据queue设置的事件类型不同而插入到不同的链表/小根堆中了
ev->ev_flags |= queue;
switch (queue) {
//I/O或signal事件,加入到已注册事件链表中
case EVLIST_INSERTED:
TAILQ_INSERT_TAIL(&base->eventqueue, ev, ev_next);
break;
//已经激活的事件,加入激活事件链表中
case EVLIST_ACTIVE:
//已激活事件数+1
base->event_count_active++;
TAILQ_INSERT_TAIL(base->activequeues[ev->ev_pri],
ev,ev_active_next);
break;
//定时事件,插入到小根堆中
case EVLIST_TIMEOUT: {
min_heap_push(&base->timeheap, ev);
break;
}
default:
event_errx(1, "%s: unknown queue %x", __func__, queue);
}
}
event_queue_insert函数的逻辑也很清晰,就是根据事件类型的不同插入到不同的管理事件的数据结构中。里面主要调用了TAILQ_INSERT_TAIL和min_heap_push这两个函数,它们的功能你应该也能猜到,所以我打算后面才来讲它们。
event_queue_remove
void
event_queue_remove(struct event_base *base, struct event *ev, int queue)
{
if (!(ev->ev_flags & queue))
event_errx(1, "%s: %p(fd %d) not on queue %x", __func__,
ev, ev->ev_fd, queue);
if (~ev->ev_flags & EVLIST_INTERNAL)
base->event_count--;
ev->ev_flags &= ~queue;
switch (queue) {
case EVLIST_INSERTED:
TAILQ_REMOVE(&base->eventqueue, ev, ev_next);
break;
case EVLIST_ACTIVE:
base->event_count_active--;
TAILQ_REMOVE(base->activequeues[ev->ev_pri],
ev, ev_active_next);
break;
case EVLIST_TIMEOUT:
min_heap_erase(&base->timeheap, ev);
break;
default:
event_errx(1, "%s: unknown queue %x", __func__, queue);
}
}
这个函数的逻辑和insert大致相同,但是实现的功能相反。
小结
本节我们学到了如何注册/注销一个event,并且看到了事件链表/小根堆的实际应用,我相信此时你对libevent的大致框架已经有一点感觉了。一个事件从初始化,再到被注册到event_base上,根据事件类型的不同存放到不同的管理事件的数据结构上,此时事件处于已经注册但处于未决态,接着等待事件被激活,最后根据优先级被调度。
接下来我们就进入到主循环,看看一个事件到底是如何经过这些过程的。
2461



被折叠的 条评论
为什么被折叠?



