(八)事件主循环

前言

上一小节我们介绍了事件是如何注册/注销的,在本小节中,我们将进一步探讨事件从未注册到处理的整个过程,即事件的主循环。

事件主循环

事件的主循环主要是通过event_base_loop完成的。我们下面先来看看这个函数,再进行总结。

event_base_loop

int
event_base_loop(struct event_base *base, int flags)
{
    const struct eventop *evsel = base->evsel;
    void *evbase = base->evbase;
    struct timeval tv;
    struct timeval *tv_p;
    int res, done;

    /* clear time cache */
    base->tv_cache.tv_sec = 0;
    //如果有信号事件,则将base赋给专门处理signal的evsignal_base(global var)
    if (base->sig.ev_signal_added)
        evsignal_base = base;
    done = 0;  //标识位
    while (!done) {
        /* Terminate the loop if we have been asked to */
        //如果设置了退出标识位,则退出循环
        if (base->event_gotterm) {
            base->event_gotterm = 0;
            break;
        }

        if (base->event_break) {
            base->event_break = 0;
            break;
        }

        /* You cannot use this interface for multi-threaded apps */
        while (event_gotsig) {
            event_gotsig = 0;
            if (event_sigcb) {
                res = (*event_sigcb)();
                if (res == -1) {
                    errno = EINTR;
                    return (-1);
                }
            }
        }
        //校正系统时间
        timeout_correct(base, &tv);

        tv_p = &tv;
        if (!base->event_count_active && !(flags & EVLOOP_NONBLOCK)) {
        /* 没有激活的事件并且是非阻塞时,根据小根堆的最小超时时间(即堆顶),计算I/O多路复用最近的等待时间 */
            timeout_next(base, &tv_p);
        } else {
            /*
             * if we have active events, we just poll new events
             * without waiting.
             */
            //有激活事件,则将tv赋为0(即立即返回,不必等待)
            evutil_timerclear(&tv);
        }

        /* If we have no events, we just exit */
        //没有注册的事件,直接退出了。该函数原型如下,很简单
        /*
          int event_haveevents(struct event_base *base)
          {
            return (base->event_count > 0)
          }
        */
        if (!event_haveevents(base)) {
            event_debug(("%s: no events registered.", __func__));
            return (1);
        }

        /* update last old time */
        gettime(base, &base->event_tv);

        /* clear time cache */
        base->tv_cache.tv_sec = 0;
        //核心语句,调用的是某种具体的多路I/O机制的等待函数,如epoll中的epoll_wait。其中还会调用`event_active`等函数将事件插入到链表中。
        res = evsel->dispatch(base, evbase, tv_p);

        if (res == -1)
            return (-1);
        //将当前的时间缓存设为当前时间
        gettime(base, &base->tv_cache);
        //将激活的定时事件从小根堆上移出,插入到激活链表中
        timeout_process(base);

        //如果有激活事件,则调用`event_process_active`去处理。处理之后,如果已经没有激活事件了并且设置了只执行一次的标识,就将done置1,或者设置为非阻塞,也将done置1。否则会一直循环
        if (base->event_count_active) {
            event_process_active(base);
            if (!base->event_count_active && (flags & EVLOOP_ONCE))
                done = 1;
        } else if (flags & EVLOOP_NONBLOCK)
            done = 1;
    }

    /* clear time cache */
    base->tv_cache.tv_sec = 0;

    event_debug(("%s: asked to terminate loop.", __func__));
    return (0);
}

我们简单的看了一下event_base_loop函数。默认情况下,它会一直运行到没有注册的事件为止,并且重复检查是否有激活事件可处理。

接下来梳理一下它的过程。

  1. 首先判断退出循环的标记是否置位(event_gotterm和event_break),如果置位,直接退出循环
  2. 校正系统时间(将tv赋值)
  3. 根据最小超时时间,计算最近的等待时间,如果有未处理的激活事件或者设置了非阻塞标志,则无需等待(定时时间设为0)
  4. 如果没有注册事件,则退出;否则调用多路I/O机制的等待函数
  5. 之后便检测小根堆中是否有超时事件,如果有,将其从小根堆移入激活事件链表中
  6. 接下来,如果有激活事件,则进行处理。并且如果要求执行到当前激活链表中没有事件可以执行就退出或者设置的是非阻塞,则下次就退出循环。

可以看到,要想退出主循环,除了done设置为1之外,还可以通过设置event_gottermevent_break标识位来实现,之前在第3小节的时候没讲,接下来我们就来看看设置这两个标识位的函数,明白这两个标识位的区别。

event_base_loopbreak

int
event_base_loopbreak(struct event_base *event_base)
{
    if (event_base == NULL)
        return (-1);

    event_base->event_break = 1;
    return (0);
}

很简单的一个设置标识位的函数,就置1这么简单。
和它相联系的还有一个函数。

int
event_loopbreak(void)
{
    return (event_base_loopbreak(current_base));
}

可以看到只是做了一层简单的封装。

event_base_loopexit

int
event_base_loopexit(struct event_base *event_base, const struct timeval *tv)
{
    return (event_base_once(event_base, -1, EV_TIMEOUT, event_loopexit_cb,
            event_base, tv));
}

这里牵扯到了另一个函数event_base_once,既然如此,那我们就先看看event_base_once,由于它涉及到了一个新的结构体event_once,我们先来了解一下这个结构体。

struct event_once

strut event_once {
        struct event ev;

        void (*cb)(int, short, void *);
        void *arg;
};

可以看到里面除了有一个event之外,还涉及到回调函数指针及它的参数。我们接着往下看。

event_base_once

/* Schedules an event once */
int
event_base_once(struct event_base *base, int fd, short events,
    void (*callback)(int, short, void *), void *arg, const struct timeval *tv)
{
    struct event_once *eonce;
    struct timeval etv;
    int res;

    /* We cannot support signals that just fire once */
    if (events & EV_SIGNAL)
        return (-1);
    //给event_once申请内存
    if ((eonce = calloc(1, sizeof(struct event_once))) == NULL)
        return (-1);
    //将函数指针及参数根据传入的参数赋值
    eonce->cb = callback;
    eonce->arg = arg;
    //初始化event,分成定时事件或I/O事件
    if (events == EV_TIMEOUT) {
        if (tv == NULL) {
            evutil_timerclear(&etv);
            tv = &etv;
        }

        evtimer_set(&eonce->ev, event_once_cb, eonce);
    } else if (events & (EV_READ|EV_WRITE)) {
        events &= EV_READ|EV_WRITE;
        //我们之前在第6小节讲过event_set,其实就是给event设置一些初始值。如果忘记了可以回看一下。
        event_set(&eonce->ev, fd, events, event_once_cb, eonce);
    } else {
        /* Bad event combination */
        free(eonce);
        return (-1);
    }
    //指定该事件注册到的event_base以及修改对应的优先级,这个也在第6小节讲过
    res = event_base_set(base, &eonce->ev);
    //如果成功了,则将其注册;失败了则释放资源
    if (res == 0)
        res = event_add(&eonce->ev, tv);
    if (res != 0) {
        free(eonce);
        return (res);
    }

    return (0);
}

不知道有一点你注意到没有,那就是调用evtimer_set以及event_set函数时,倒数第二个参数传入的函数指针是event_once_cb,对应的参数传入的是event_once结构体。

event_once_cb其实是早就准备好的一个函数,代码如下:

static void
event_once_cb(int fd, short events, void *arg)
{
  struct event_once *eonce = arg;
  (*eonce->cb)(fd, events, eonce->arg);
  free(eonce);
}

它内部调用了struct event_once结构体内部的函数指针指向的函数,并且将该结构体释放了。
再次回到开始的函数event_base_loopexit

int
event_base_loopexit(struct event_base *event_base, const struct timeval *tv)
{
    return (event_base_once(event_base, -1, EV_TIMEOUT, event_loopexit_cb,
            event_base, tv));
}

struct event_once结构体中函数指针指向的就是event_loopexit_cb。可以看出event_base_once函数的作用就是事件被激活调度一次后就删除。

event_base_loopexit达到的效果就是,该事件被激活后,先回调event_once_cb函数,event_once_cb函数里面再回调eonce中函数指针指向的函数(event_loopexit_cd),将event_base_loopexit设置为1。

小结

在本节中,我们先了解了事件主循环event_base_loop的整个过程。再通过event_base_loopexit函数了解了event_base_once的工作流程。接下来的一个小节,我们将把event.c剩余的一些主要函数分析一下。

你可能感兴趣的:(Libevent,深入Libevent源码)