libevent源码解析(三)event_base接口函数

一.前言

  本文将重点介绍event_base相关的几个接口函数,包括:

 *    event_base_new(), event_base_loop(),
 *    event_base_new_with_config()

二.函数介绍

(1)event_base_new()函数

  该函数功能如字面意思,即建立一个新的event_base,用于存放、管理事件。具体代码如下所示:

/*创建event_base*/
struct event_base *
event_base_new(void)
{

    struct event_base *base = NULL;
    /*创建一个event_config结构*/  
    struct event_config *cfg = event_config_new();

    /*用event_config为event_base赋值*/
    if (cfg) {
        base = event_base_new_with_config(cfg);
        event_config_free(cfg);
    }
    return base;
}

  由代码可见,这里主要用到了一个event_config结构体以及下来要介绍的event_base_new_with_config()函数进行event_base的建立,所以这些放在event_base_new_with_config()中一起介绍。

(2)event_base_new_with_config()函数

  首先看看刚刚提到的event_config结构体。Libevent是跨平台的Reactor,对于事件监听,其内部是使用多路IO复用函数。比较通用的多路IO复用函数是select和poll。而很多平台都提出了自己的高效多路IO复用函数,比如:epoll、devpoll、kqueue。Libevent对于这些多路IO复用函数都进行包装,供自己使用。event_config_avoid_method函数就是指出,避免使用指定的多路IO复用函数。其是通过字符串的方式指定的,即参数method。这个字符串将由队列元素event_config_entry的avoid_method成员变量存储(由于是指针,所以更准确来说是指向)。

   查看Libevent源码包里的文件,可以发现有诸如epoll.c、select.c、poll.c、devpoll.c、kqueue.c这些文件。打开这些文件就可以发现在文件内容的前面都会定义一个struct eventop类型变量。该结构体的第一个成员必然是一个字符串。这个字符串就描述了对应的多路IO复用函数的名称。所以是可以通过名称来禁用某种多路IO复用函数的。

/*队列元素的类型*/
struct event_config_entry {
    TAILQ_ENTRY(event_config_entry) next;

    const char *avoid_method;
};

/** Internal structure: describes the configuration we want for an event_base
 * that we're about to allocate. */
struct event_config {

    /*生成队列管理配置*/
    TAILQ_HEAD(event_configq, event_config_entry) entries;

    /*CPU数量,其作用是告诉event_config,系统中有多少个CPU,以便作一些对线程池作一些调整来获取更高的效率*/
    int n_cpus_hint;

    /*限制参数:最大分发事件间隔时间,最大回调次数,优先级限制*/
    struct timeval max_dispatch_interval;
    int max_dispatch_callbacks;
    int limit_callbacks_after_prio;

    /*指定多路IO复用函数应该满足哪些特征*/
    enum event_method_feature require_features;

    /*通过函数event_config_set_flag设置*/
    enum event_base_config_flag flags;
};

enum event_method_feature {  
    //支持边沿触发  
    EV_FEATURE_ET = 0x01,  
    //添加、删除、或者确定哪个事件激活这些动作的时间复杂度都为O(1)  
    //select、poll是不能满足这个特征的.epoll则满足  
    EV_FEATURE_O1 = 0x02,  
    //支持任意的文件描述符,而不能仅仅支持套接字  
    EV_FEATURE_FDS = 0x04  
};  

其中flags取:

  • EVENT_BASE_FLAG_NOLOCK:不要为event_base分配锁。设置这个选项可以为event_base节省一点加锁和解锁的时间,但是当多个线程访问event_base会变得不安全

  • EVENT_BASE_FLAG_IGNORE_ENV:选择多路IO复用函数时,不检测EVENT_*环境变量。使用这个标志要考虑清楚:因为这会使得用户更难调试程序与Libevent之间的交互

  • EVENT_BASE_FLAG_STARTUP_IOCP:仅用于Windows。这使得Libevent在启动时就启用任何必需的IOCP分发逻辑,而不是按需启用。如果设置了这个宏,那么evconn_listener_new和bufferevent_socket_new函数的内部将使用IOCP

  • EVENT_BASE_FLAG_NO_CACHE_TIME:在执行event_base_loop的时候没有cache时间。该函数的while循环会经常取系统时间,如果cache时间,那么就取cache的。如果没有的话,就只能通过系统提供的函数来获取系统时间。这将更耗时

  • EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST:告知Libevent,如果决定使用epoll这个多路IO复用函数,可以安全地使用更快的基于changelist 的多路IO复用函数:epoll-changelist多路IO复用可以在多路IO复用函数调用之间,同样的fd 多次修改其状态的情况下,避免不必要的系统调用。但是如果传递任何使用dup()或者其变体克隆的fd给Libevent,epoll-changelist多路IO复用函数会触发一个内核bug,导致不正确的结果。在不使用epoll这个多路IO复用函数的情况下,这个标志是没有效果的。也可以通过设置EVENT_EPOLL_USE_CHANGELIST 环境变量来打开epoll-changelist选项。

      下面是event_base_new_with_config()函数:

struct event_base *
event_base_new_with_config(const struct event_config *cfg)
{
    int i;
    struct event_base *base;
    int should_check_environment;

#ifndef EVENT__DISABLE_DEBUG_MODE
    event_debug_mode_too_late = 1;
#endif

    /* 之所以不用mm_malloc是因为mm_malloc并不会清零该内存区域。  
     * 而这个函数是会清零申请到的内存区域,这相当于被base初始化  
     */
    if ((base = mm_calloc(1, sizeof(struct event_base))) == NULL) {
        event_warn("%s: calloc", __func__);
        return NULL;
    }

    /*根据config的flags赋值*/
    if (cfg)
        base->flags = cfg->flags;

    /*检查环境时间:可能系统时间发生了变化*/
    should_check_environment =
        !(cfg && (cfg->flags & EVENT_BASE_FLAG_IGNORE_ENV));

    {
        struct timeval tmp;
        int precise_time =
            cfg && (cfg->flags & EVENT_BASE_FLAG_PRECISE_TIMER);
        int flags;
        if (should_check_environment && !precise_time) {
            precise_time = evutil_getenv_("EVENT_PRECISE_TIMER") != NULL;
            if (precise_time) {
                base->flags |= EVENT_BASE_FLAG_PRECISE_TIMER;
            }
        }
        flags = precise_time ? EV_MONOT_PRECISE : 0;
        evutil_configure_monotonic_time_(&base->monotonic_timer, flags);

        gettime(base, &tmp);
    }

    /*初始化需要用到的堆、pair、fd、哈希列表等*/
    min_heap_ctor_(&base->timeheap);

    base->sig.ev_signal_pair[0] = -1;
    base->sig.ev_signal_pair[1] = -1;
    base->th_notify_fd[0] = -1;
    base->th_notify_fd[1] = -1;

    TAILQ_INIT(&base->active_later_queue);

    evmap_io_initmap_(&base->io);
    evmap_signal_initmap_(&base->sigmap);
    event_changelist_init_(&base->changelist);

    base->evbase = NULL;

    /*根据cfg赋值*/
    if (cfg) {
        memcpy(&base->max_dispatch_time,
            &cfg->max_dispatch_interval, sizeof(struct timeval));
        base->limit_callbacks_after_prio =
            cfg->limit_callbacks_after_prio;
    } else {
        base->max_dispatch_time.tv_sec = -1;
        base->limit_callbacks_after_prio = 1;
    }
    if (cfg && cfg->max_dispatch_callbacks >= 0) {
        base->max_dispatch_callbacks = cfg->max_dispatch_callbacks;
    } else {
        base->max_dispatch_callbacks = INT_MAX;
    }
    if (base->max_dispatch_callbacks == INT_MAX &&
        base->max_dispatch_time.tv_sec == -1)
        base->limit_callbacks_after_prio = INT_MAX;


    /*选择IO复用结构体*/
    for (i = 0; eventops[i] && !base->evbase; i++) {
        if (cfg != NULL) {
            /* determine if this backend should be avoided */
            if (event_config_is_avoided_method(cfg,
                eventops[i]->name))
                continue;
            if ((eventops[i]->features & cfg->require_features)
                != cfg->require_features)
                continue;
        }

        /* also obey the environment variables */
        if (should_check_environment &&
            event_is_method_disabled(eventops[i]->name))
            continue;

        base->evsel = eventops[i];

        base->evbase = base->evsel->init(base);
    }

    /*检查event_base是否为空,若空则报错*/
    if (base->evbase == NULL) {
        event_warnx("%s: no event mechanism available",
            __func__);
        base->evsel = NULL;
        event_base_free(base);
        return NULL;
    }

    if (evutil_getenv_("EVENT_SHOW_METHOD"))
        event_msgx("libevent using: %s", base->evsel->name);

    /* allocate a single active event queue */
    if (event_base_priority_init(base, 1) < 0) {
        event_base_free(base);
        return NULL;
    }

    /* prepare for threading 
     * 测试evthread_lock_callbacks结构中的lock指针函数是否为NULL  
     * 即测试Libevent是否已经初始化为支持多线程模式。  
     * 由于一开始是用mm_calloc申请内存的,所以该内存区域的值为0  
     * 对于th_base_lock变量,目前的值为NULL.
     */

#if !defined(EVENT__DISABLE_THREAD_SUPPORT) && !defined(EVENT__DISABLE_DEBUG_MODE)
    event_debug_created_threadable_ctx_ = 1;
#endif

#ifndef EVENT__DISABLE_THREAD_SUPPORT
    if (EVTHREAD_LOCKING_ENABLED() &&
        (!cfg || !(cfg->flags & EVENT_BASE_FLAG_NOLOCK))) {
        int r;
        EVTHREAD_ALLOC_LOCK(base->th_base_lock, 0);
        EVTHREAD_ALLOC_COND(base->current_event_cond);
        r = evthread_make_base_notifiable(base);
        if (r<0) {
            event_warnx("%s: Unable to make base notifiable.", __func__);
            event_base_free(base);
            return NULL;
        }
    }
#endif

    /*检测是否开启IOCP*/
#ifdef _WIN32
    if (cfg && (cfg->flags & EVENT_BASE_FLAG_STARTUP_IOCP))
        event_base_start_iocp_(base, cfg->n_cpus_hint);
#endif

    return (base);
}

(3)event_base_loop()函数

  Libevent的事件主循环主要是通过event_base_loop ()函数完成的,即根据系统提供的事件多路分发机制执行事件循环,对已注册的就绪事件,调用注册事件的回调函数来处理事件。

/*事件主循环部分*/
int
event_base_loop(struct event_base *base, int flags)
{
    const struct eventop *evsel = base->evsel;
    struct timeval tv;
    struct timeval *tv_p;
    int res, done, retval = 0;

    /* Grab the lock.  We will release it inside evsel.dispatch, and again
     * as we invoke user callbacks. */
    EVBASE_ACQUIRE_LOCK(base, th_base_lock);

    /*异常检测*/
    if (base->running_loop) {
        event_warnx("%s: reentrant invocation.  Only one event_base_loop"
            " can run on each event_base at once.", __func__);
        EVBASE_RELEASE_LOCK(base, th_base_lock);
        return -1;
    }

    base->running_loop = 1;

    /*清空时间缓存*/
    clear_time_cache(base);

    if (base->sig.ev_signal_added && base->sig.ev_n_signals_added)
        evsig_set_base_(base);

    done = 0;

#ifndef EVENT__DISABLE_THREAD_SUPPORT
    base->th_owner_id = EVTHREAD_GET_ID();
#endif

    base->event_gotterm = base->event_break = 0;

    /*事件主循环*/
    while (!done) {
        base->event_continue = 0;
        base->n_deferreds_queued = 0;

        /* 查看是否需要跳出循环,程序可以调用event_loopexit_cb()设置event_gotterm标记  
         * 调用event_base_loopbreak()设置event_break标记  
         * Terminate the loop if we have been asked to 
         */
        if (base->event_gotterm) {
            break;
        }

        if (base->event_break) {
            break;
        }

        /*根据timer heap中事件的最小超时时间,计算系统I/O demultiplexer的最大等待时间 */
        tv_p = &tv;
        if (!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK)) {
            timeout_next(base, &tv_p);
        } else {
            /* 依然有未处理的就绪时间,就让I/O demultiplexer立即返回,不必等待  
             * 下面会提到,在libevent中,低优先级的就绪事件可能不能立即被处理
             * if we have active events, we just poll new events
             * without waiting.
             */
            evutil_timerclear(&tv);
        }

        /* 如果当前没有注册事件,就退出 If we have no events, we just exit */
        if (0==(flags&EVLOOP_NO_EXIT_ON_EMPTY) &&
            !event_haveevents(base) && !N_ACTIVE_CALLBACKS(base)) {
            event_debug(("%s: no events registered.", __func__));
            retval = 1;
            goto done;
        }

        event_queue_make_later_events_active(base);

        clear_time_cache(base);

        /* 调用系统I/O demultiplexer等待就绪I/O events,可能是epoll_wait,或者select等;  
        // 在evsel->dispatch()中,会把就绪signal event、I/O event插入到激活链表中  */
        res = evsel->dispatch(base, tv_p);

        if (res == -1) {
            event_debug(("%s: dispatch returned unsuccessfully.",
                __func__));
            retval = -1;
            goto done;
        }

        update_time_cache(base);

        timeout_process(base);

        /* 调用event_process_active()处理激活链表中的就绪event,调用其回调函数执行事件处理  
         * 该函数会寻找最高优先级(priority值越小优先级越高)的激活事件链表,  
         * 然后处理链表中的所有就绪事件;  
         * 因此低优先级的就绪事件可能得不到及时处理; 
         */
        if (N_ACTIVE_CALLBACKS(base)) {
            int n = event_process_active(base);
            if ((flags & EVLOOP_ONCE)
                && N_ACTIVE_CALLBACKS(base) == 0
                && n != 0)
                done = 1;
        } else if (flags & EVLOOP_NONBLOCK)
            done = 1;
    }
    event_debug(("%s: asked to terminate loop.", __func__));

    /*循环结束,清空时间缓存 */
done:
    clear_time_cache(base);
    base->running_loop = 0;

    EVBASE_RELEASE_LOCK(base, th_base_lock);

    return (retval);
}

三.小结

  本文分析了event_base的接口函数,通过了解这些接口函数,我们学习了event_base的功能、libevent的事件循环过程等。

猜你喜欢

转载自blog.csdn.net/u013354486/article/details/80789436