event 注册和注销

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Function_Dou/article/details/87891357

本节我们依旧来分析event的操作, 接着上节没有讲完的关于event的注册和注销两个功能的实现.

event 注册

依旧来看一个event 注册函数的原型

int event_add(struct event *ev, const struct timeval *tv);

相比event_set函数event_add真的很干净.

参数

  • ev : 将要注册的event事件. 其实实际注册的是ev->ev_base.
  • tv : 设置超时时间. 如果tv != NULL则会同时注册定时事件, 将ev添加到timer堆上.

函数源码event.c

函数真的很长, 不慌, 我们慢慢的分析(基本在注释中).

// 设置事件的定时. 实质 : 注册事件
// 1. 如果设置定时, 则先在小根堆上申请一个注册的空间
// 2. 如果事件已经处于定时队列, 则将其从定时队列中移除
// 3. 如果事件已经在就绪队列中, 则将其从就绪队列中移除
// 4. 重新设置定时时间, 并将其插入到定时队列中
int
event_add(struct event *ev, const struct timeval *tv)
{
	struct event_base *base = ev->ev_base;	// 指向要注册的 ev_base
	const struct eventop *evsel = base->evsel;
	void *evbase = base->evbase;	// base 所使用的IO成员
	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 */
	}
  
	// 该事件是处于读/写/信号并且没有处于就绪队列中
	if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&
	    !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {
		// 添加处理函数. 信号也是在这里被添加的, 都是统一的添加接口
		res = evsel->add(evbase, ev);
		if (res != -1)
			// 将设置的事件插入到注册链表中
			event_queue_insert(base, ev, EVLIST_INSERTED);
	}

	/* 
	 * we should change the timout state only if the previous event
	 * addition succeeded.
	 */
	// 设置了超时时间
	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
			 */
			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);
}

从29行可以看出来libevent的精妙之处 : 如果申请到空间, 则下面的操作就能够正常执行, 如果不能就直接退出, 这里保证了定时的注册是一个原子操作.

现在来分析event_add的执行过程 :

  1. 如果设置了定时, 则在小根堆上分配一个空间, 以便之后的注册.
  2. 如果事件已经设置了I/O或者信号标志, 并且事件并没有被初始化或者没有在就绪队列中, 则将事件插入到就绪队列中(就是有可能该事件并没有被初始化也可以调用注册函数哦).
  3. 如果设置了定时, 现在判断它是否已经在定时队列或者在就绪队列中了,如果是, 则从队列中删除在重新设置定时并且插入到定时队列中.(就是可能事件之前已经设置了定时时间, 但是可能不满意可以反悔重新设置定时时间的)

相信整个函数的流程清楚了, 但是其中一些出现过几次的函数(event_queue_insertevent_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 */
		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++;

	// 设置该标志位, 表示该事件已经被插入到该队列中
	ev->ev_flags |= queue;
	switch (queue) {
	case EVLIST_INSERTED:
		TAILQ_INSERT_TAIL(&base->eventqueue, ev, ev_next);
		break;
	case EVLIST_ACTIVE:
		base->event_count_active++; // 队列的就绪个数 + 1
		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);
	}
}

其实理解还是很容易的.

  1. 如果事件已经被注册了则什么也不做的返回
  2. 如果事件并没有被初始化, 则将base的注册数 +1
  3. 将事件加入到指定队列中. (TAILQ_INSERT_TAIL 之后分析源码.)

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);

	// 注册次数 - 1
	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--; // 队列的就绪个数 - 1
		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);
	}
}

过程与上面一样.

  1. 如果事件并没有在指定队列上则报错
  2. base上的注册次数 -1
  3. 重新设置ev事件的flags标志. 并从指定队列中删除注册事件.

event 的注销

event 注销其实就很简单了. 从所有队列中删除指定事件.

// 从所有队列中删除指定事件
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);
	// ev注册的event_base和eventop指针
	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 */
	// 将事件的回调次数设为0
	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);
		return (evsel->del(evbase, ev));    // 同时取消监听
	}

	return (0);
}
  1. 如果事件已经就绪, 则将就绪调用回调函数的次数设置为 0
  2. 从事件所在的链表中删除, 一个事件以可能同时处在多个事件队列上.
  3. 如果事件处于EVLIST_INSERTED状态的话, 最后还应该将它的监听也删除. 如: 从epoll的监听中删除, 从绑定的信号中删除.

总结

篇幅可能有点长, 但是大多都是代码, 所以慢慢分析的话难度也不大. 其实最重要的就是搞清楚eventevent_base每个成员对应的哪个功能, 其他都是判断事件状态, 加入某个队列或从某个队列中删除. 接下来我们分析event重要的主循环.

猜你喜欢

转载自blog.csdn.net/Function_Dou/article/details/87891357