1. 程式人生 > >libevent原始碼分析(6)--2.1.8--建立和釋放libevent控制代碼event_base的相關函式

libevent原始碼分析(6)--2.1.8--建立和釋放libevent控制代碼event_base的相關函式

一、event_base_new

建立預設的event_base

**
 * Create and return a new event_base to use with the rest of Libevent.
 *
 * @return a new event_base on success, or NULL on failure.
 *
 * @see event_base_free(), event_base_new_with_config()
 */
// 建立並返回新的event_base物件--libevent控制代碼
// 成功則返回新的event_base物件,失敗則返回NULL
// 相關檢視event_base_free(),event_base_new_with_config
struct event_base *
event_base_new(void)
{
    struct event_base *base = NULL;
     // 呼叫event_config_new函式建立預設的struct event_config物件,具體可以參考
     // libevent原始碼分析(5)--2.1.8--libevent配置資訊物件struct event_config的申請和釋放函式分析
    struct event_config *cfg = event_config_new();
    if (cfg) {
          // 根據輸入引數建立libevent控制代碼-struct event_base物件
        base = event_base_new_with_config(cfg);
          // 建立物件完畢後,無論成功與否,都需要釋放配置資訊物件struct event_config
        event_config_free(cfg);
    }
    return base;
}

、event_base_new_with_config

使用配置資訊建立event_base

/**
  Initialize the event API.
  Use event_base_new_with_config() to initialize a new event base, taking
  the specified configuration under consideration.  The configuration object
  can currently be used to avoid certain event notification mechanisms.
  @param cfg the event configuration object
  @return an initialized event_base that can be used to registering events,
     or NULL if no event base can be created with the requested event_config.
  @see event_base_new(), event_base_free(), event_init(), event_assign()
*/
// 初始化libevent 的API
// 使用本函式初始化新的event_base,可以使用配置物件來遮蔽某些特定的
// 事件通知機制
// cfg:事件配置物件
// 返回初始化後的event_base物件,可以用來註冊事件;失敗則返回NULL
// 檢視相關函式: event_base_new(), event_base_free(), event_init(), event_assign()

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_calloc,申請struct event_base物件
     // 並對每個域都賦初值1
    if ((base = mm_calloc(1, sizeof(struct event_base))) == NULL) {
        event_warn("%s: calloc", __func__);
        return NULL;
    }

     // 使用配置物件的event_base工作模式,即:
     // 多執行緒呼叫是不安全的,單執行緒非阻塞模式
    // EVENT_BASE_FLAG_NOLOCK = 0x01,
     // 忽略檢查EVENT_*等環境變數
    // EVENT_BASE_FLAG_IGNORE_ENV = 0x02,
     // 只用於windows
    // EVENT_BASE_FLAG_STARTUP_IOCP = 0x04,
     // 不使用快取的時間,每次回撥都會獲取系統時間
    // EVENT_BASE_FLAG_NO_CACHE_TIME = 0x08,
     // 如果使用epoll方法,則使用epoll內部的changelist
    // EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST = 0x10,
     // 使用更精確的時間,但是可能效能會降低
    // EVENT_BASE_FLAG_PRECISE_TIMER = 0x20
    if (cfg)
        base->flags = cfg->flags;

     // 獲取cfg中event_base工作模式是否包含EVENT_BASE_FLAG_IGNORE_ENV
     // 即檢視是否忽略檢查EVENT_*等環境變數
     // 預設情況下,cfg->flags = EVENT_BASE_FLAG_NOBLOCK,所以是沒有設定忽略環境變數,
     // 因此should_check_enviroment = 1,是應該檢查環境變數的
    should_check_environment =
        !(cfg && (cfg->flags & EVENT_BASE_FLAG_IGNORE_ENV));

    {
        struct timeval tmp;
          // 獲取cfg中event_base工作模式中是否包含使用精確時間的設定
          // EVENT_BASE_FLAG_PRECISE_TIMER含義看上文
          // 如果使用精確時間,則precise_time為1,否則為0
          // 預設配置下,cfg->flags=EVENT_BASE_FLAG_NOBLOCK,所以precise_time = 0
        int precise_time =
            cfg && (cfg->flags & EVENT_BASE_FLAG_PRECISE_TIMER);
        int flags;
          // 如果檢查EVENT_*等環境變數並且不使用精確時間,
          // 則需要檢查編譯時的環境變數中是否打開了使用精確時間的模式;
          // 這一段檢查的目的是說,雖然配置結構體struct event_config中沒有指定
          // event_base使用精確時間的模式,但是libevent提供編譯時使用環境變數來控制
          // 使用精確時間的模式,所以如果開啟檢查環境變數的開關,則需要檢查是否在編譯時
          // 打開了使用精確時間的模式。例如在CMakeList中就有有關開啟選項
         // CMakeLists.txt:1150:                            "${BACKEND_ENV_VARS};EVENT_PRECISE_TIMER=1")
          // CMakeLists.txt:1156:                                 //"${BACKEND_ENV_VARS};EVENT_EPOLL_USE_CHANGELIST=yes;EVENT_PRECISE_TIMER=1")

          // 預設情況下,檢查環境變數並且precise_time=0,所以需要執行這個if分支
          // 執行結果之後,precise_time=1,base->flags = cfg->flags | EVENT_BASE_FLAG_PRECISE_TIMER
          // 即base->flags = EVENT_BASE_FLAG_NOBLOCK | EVENT_BASE_FLAG_PRECISE_TIMER = 0x21
        if (should_check_environment && !precise_time) {
          // evutil_getenv_實際上是getenv函式的封裝,用來獲取環境變數資訊
          // 檢視上面CMakeList中資訊,可以看出實際上預設是開啟的
            precise_time = evutil_getenv_("EVENT_PRECISE_TIMER") != NULL;
            base->flags |= EVENT_BASE_FLAG_PRECISE_TIMER;
        }
          // 根據precise_time的標誌資訊,確認是否使用MONOT_PRECISE模式
          // linux下實際上一般是呼叫clock_gettime實現
          // 預設情況下,precise_time=1,所以flags = EV_MONOT_PRECISE
          // 所以預設情況下,使用EV_MONOT_PRECISE模式配置event_base的monotonic_time
        flags = precise_time ? EV_MONOT_PRECISE : 0;
          // 後面會分析這個函式
        evutil_configure_monotonic_time_(&base->monotonic_timer, flags);
          // 根據base獲取當前的時間
          // 如果base中有快取的時間,則將快取的時間賦給tmp,然後返回即可;
          // 如果base中沒有快取的時間,則使用clock_gettime獲取當前系統的monotonic時間;
          // 否則根據上次更新系統時間的時間點、更新間隔、以及當前使用clock_gettime等函式獲取當前的系統時間檢視是否
          // 需要更新base->tv_clock_diff以及base->last_updated_clock_diff
          // 後面會分析這個函式
        gettime(base, &tmp);
    }

     // 建立timer的最小堆
    min_heap_ctor_(&base->timeheap);

     // 內部訊號通知的管道,0讀1寫
    base->sig.ev_signal_pair[0] = -1;
    base->sig.ev_signal_pair[1] = -1;
     // 內部執行緒通知的檔案描述符,0讀1寫
    base->th_notify_fd[0] = -1;
    base->th_notify_fd[1] = -1;

     // 初始化下一次啟用的佇列
    TAILQ_INIT(&base->active_later_queue);

     // 初始化IO事件和檔案描述符的對映
     // 如果實在win32下,則使用hash table,一些巨集定義實現,具體可以檢視event-internal.h中EVMAP_USE_HT這個編譯選項
     // 如果實在其他環境下,#define event_io_map event_signal_map
     // 則base->io名為struct event_io_map,實際上是struct event_signal_map
     // 因此linux下實際上呼叫的是evmap_signal_initmap_,後面會大體分析一下這個函式
    evmap_io_initmap_(&base->io);
     // 初始化訊號和檔案描述符的對映
     // 後面會分析這個函式
    evmap_signal_initmap_(&base->sigmap);
     // 初始化變化事件列表
     // 後面會分析這個函式
    event_changelist_init_(&base->changelist);

     // 在沒有初始化後臺方法之前,後臺方法必需的資料資訊為空
    base->evbase = NULL;

     // 配置event loop的檢查回撥函式的間隔資訊,預設情況下,是不檢查的,即一旦event_base確認當前
     // 優先順序是最高優先順序佇列,則會一直把該優先順序佇列中同優先順序事件都執行完再檢查是否有更高優先順序
     // 事件發生,在執行過程中,即使有高優先順序事件觸發,event_base也不會中斷去執行;
     // 配置之後的優點是:如果配置可以支援優先順序搶佔,提高高優先順序事件執行效率,
     // 配置之後的缺點是:但是一旦配置,每次執行完回撥函式都需要檢查,則會輕微降低事件吞吐量:
     // max_dispatch_callbacks:event_base在兩次檢查新事件之間的執行回撥函式個數的最大個數,
     // max_dispatch_interval: event_base在兩次檢查新事件之間消耗的最大時間。
     // limit_callbacks_after_prio:是隻有優先順序數字>=limit_callbacks_after_prio的事件觸發時,才會強迫
     // event_base去檢查是否有更高優先順序事件發生,低於這個優先順序的事件觸發時,不會檢查。

     // 如果配置資訊物件struct event_config存在,則依據配置資訊配置,
     // 否則,賦給預設初始值
    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 {
          // max_dispatch_time.tv_sec=-1即不進行此項檢查
          // limit_callbacks_after_prio=1是指>=1時都需要檢查,最高優先順序為0,
          // 即除了最高優先順序事件執行時不需要檢查之外,其他優先順序都需要檢查
        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;

     // 遍歷靜態全域性變數eventops,對選擇的後臺方法進行初始化
     // eventops定義:
     // libevent原始碼分析(3)--2.1.8--結構體struct event_base和struct eventop
     // 我自己測試結果是,預設選擇使用epoll方法,而測試環境支援的後臺方法包括
     // epoll,poll,select
     // 下面看一下,程式是怎樣選出epoll方法,跳過poll和select的
    for (i = 0; eventops[i] && !base->evbase; i++) {
        if (cfg != NULL) {
            /* determine if this backend should be avoided */
               // 如果方法已經遮蔽,則跳過去,繼續遍歷下一個方法
               // 預設情況下,是不遮蔽任何後臺方法,除非通過編譯選項控制或者使用API遮蔽
            if (event_config_is_avoided_method(cfg,
                eventops[i]->name))
                continue;
               // 如果後臺方法的工作模式特徵和配置的工作模式不同,則跳過去
               // 檢視libevent原始碼中epoll.c、poll.c、select.c檔案相應靜態全域性變數的方法定義,
               // 發現預設情況下,各個後臺方法的特徵如下:
               // epoll方法的特徵是 :EV_FEATURE_ET|EV_FEATURE_O1|EV_FEATURE_EARLY_CLOSE
               // poll方法的特徵是:EV_FEATURE_FDS
               // select方法的特徵是:EV_FEATURE_FDS
               // 預設情況下,cfg中require_features是EV_FEATURE_ET,所以poll和select都會跳過去,只有epoll執行初始化
            if ((eventops[i]->features & cfg->require_features)
                != cfg->require_features)
                continue;
        }

        /* also obey the environment variables */
          如果檢查環境變數,並發現OS環境不支援的話,也會跳過去
        if (should_check_environment &&
            event_is_method_disabled(eventops[i]->name))
            continue;
        // 下面兩步正確情況下只會選擇一個後臺方法,也只會執行一次
        // 儲存後臺方法控制代碼,實際是靜態全域性變數陣列成員,具體定義在每種方法檔案中定義
        base->evsel = eventops[i];

        // 呼叫相應後臺方法的初始化函式進行初始化,這個就用到結構體
          // struct eventop中定義的init,具體方法的init實現需要檢視每種方法自己的定義
          // 以epoll為例,epoll相關實現都在epoll.c中
         // 後面會分析epoll的初始化函式
        base->evbase = base->evsel->init(base);
    }

     // 如果遍歷一遍沒有發現合適的後臺方法,就報錯退出,退出前釋放資源
    if (base->evbase == NULL) {
        event_warnx("%s: no event mechanism available",
            __func__);
        base->evsel = NULL;
          // 後面會分析這個函式
        event_base_free(base);
        return NULL;
    }

     // 獲取環境變數EVENT_SHOW_METHOD,是否列印輸出選擇的後臺方法名字
    if (evutil_getenv_("EVENT_SHOW_METHOD"))
        event_msgx("libevent using: %s", base->evsel->name);

    /* allocate a single active event queue */
     // 分配優先順序佇列成員個數,前面分配event_base時會填入初始值1,
     // 此處分配的優先順序佇列成員個數為1,則直接返回了
    if (event_base_priority_init(base, 1) < 0) {
        event_base_free(base);
        return NULL;
    }

    /* prepare for threading */

#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

#ifdef _WIN32
    if (cfg && (cfg->flags & EVENT_BASE_FLAG_STARTUP_IOCP))
        event_base_start_iocp_(base, cfg->n_cpus_hint);
#endif

     // 注意:其他沒有顯式初始化的域都被前面分配空間時預設初始化為1
    return (base);
}




三、event_base_free

釋放event_base物件

/**
  Deallocate all memory associated with an event_base, and free the base.
  Note that this function will not close any fds or free any memory passed
  to event_new as the argument to callback.
  If there are any pending finalizer callbacks, this function will invoke
  them.
  @param eb an event_base to be freed
 */
// 釋放event_base附屬的所有空間,並釋放base;
// 注意:這個函式不會關閉任何fds或者釋放在執行event_new時任何傳遞給callback的引數
// 如果未決的關閉型別的回撥,本函式會喚醒這些回撥
// eb:待釋放的event_base
void
event_base_free(struct event_base *base)
{
     // 實際上時呼叫內部釋放函式釋放的。
    event_base_free_(base, 1);
}


四、evutil_configure_monotonic_time_ 配置單調遞增的時間,預設情況下,會使用EV_MONOTONIC模式獲取系統時間,並將event_base的monotonic_clock模式設定為EV_MONOTONIC; monotonic時間是單調遞增的時間,不受系統修改時間影響,對於計算兩個時間點之間的時間比較精確; real時間是系統時間,受系統時間影響 // libevent支援多種作業系統模式,以下只看Linux下的
#if defined(HAVE_POSIX_MONOTONIC)
/* =====
   The POSIX clock_gettime() interface provides a few ways to get at a
   monotonic clock.  CLOCK_MONOTONIC is most widely supported.  Linux also
   provides a CLOCK_MONOTONIC_COARSE with accuracy of about 1-4 msec.
   On all platforms I'm aware of, CLOCK_MONOTONIC really is monotonic.
   Platforms don't agree about whether it should jump on a sleep/resume.
 */
// POSIX clock_gettime介面提供獲得monotonic時間的方式。CLOCK_MONOTONIC基本上都是支援的;
// linux也提供CLOCK_MONOTONIC_COARSE模式,大約1-4毫秒的準確性。
// 所有平臺上,CLOCK_MONOTONIC實際上是單調遞增的。

int
evutil_configure_monotonic_time_(struct evutil_monotonic_timer *base,
    int flags)
{
    /* CLOCK_MONOTONIC exists on FreeBSD, Linux, and Solaris.  You need to
     * check for it at runtime, because some older kernel versions won't
     * have it working. */
     // CLOCK_MONOTONIC在FreeBSD,Linux,Solaris一般是支援的。你需要執行時檢查,因為
     // 一些老的核心版本可能不支援

// 如果定義了CLOCK_MONOTONIC_COARSE編譯選項,則檢查event_base工作模式是否選擇了EV_MONOT_PRECISE;
// 如果選擇了EV_MONOT_PRECISE,則設定標誌位precise,表明選擇使用準確的時間模式;
// 預設情況下,flags採用的是EV_MONOT_PRECISE,所以precise=EV_MONOT_PRECISE=1

#ifdef CLOCK_MONOTONIC_COARSE
    const int precise = flags & EV_MONOT_PRECISE;
#endif

     // 設定fallback標誌位,檢視是否為EV_MONOT_FALLBACK=2模式
     // 預設情況下,flags採用的是EV_MONOT_PRECISE,所以fallback為0
    const int fallback = flags & EV_MONOT_FALLBACK;
    struct timespec    ts;

#ifdef CLOCK_MONOTONIC_COARSE
    if (CLOCK_MONOTONIC_COARSE < 0) {
        /* Technically speaking, nothing keeps CLOCK_* from being
         * negative (as far as I know). This check and the one below
         * make sure that it's safe for us to use -1 as an "unset"
         * value. */
        event_errx(1,"I didn't expect CLOCK_MONOTONIC_COARSE to be < 0");
    }

     // 如果既沒有選擇EV_MONOT_PRECISE模式,也沒有選擇EV_MONOT_FALLBACK模式,則使用
     // CLOCK_MONOTONIC_COARSE獲取當前系統時間
     // 預設情況下選擇的是EV_MONOT_PRECISE,所以不走此分支
    if (! precise && ! fallback) {
        if (clock_gettime(CLOCK_MONOTONIC_COARSE, &ts) == 0) {
            base->monotonic_clock = CLOCK_MONOTONIC_COARSE;
            return 0;
        }
    }
#endif

     // 如果沒有選擇EV_MONOT_FALLBACK,則以CLOCK_MONOTONIC模式獲取系統時間,並將
     // event_base的monotonic_clock模式設定為CLOCK_MONOTONIC;
     // 預設情況下,選擇的是EV_MONOT_PRECISE,所以此分支執行,
     // 此函式最終的結果是獲取CLOCK_MONOTONIC模式的時間,並將event_base的時鐘模式設定為
     // CLOCK_MONOTONIC模式
    if (!fallback && clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
        base->monotonic_clock = CLOCK_MONOTONIC;
        return 0;
    }

    if (CLOCK_MONOTONIC < 0) {
        event_errx(1,"I didn't expect CLOCK_MONOTONIC to be < 0");
    }

    base->monotonic_clock = -1;
    return 0;
}


五、gettime函式 此函式主要用來獲取當前event_base中快取的時間,並設定使用系統時間更新event_base快取時間的時間間隔,並獲取當前系統的monotonic時間和當前系統的real時間之間差值並存儲在event_base中
/** Set 'tp' to the current time according to 'base'.  We must hold the lock
 * on 'base'.  If there is a cached time, return it.  Otherwise, use
 * clock_gettime or gettimeofday as appropriate to find out the right time.
 * Return 0 on success, -1 on failure.
 */
 // 將tp設定為base的當前時間。必需在base上加鎖;如果有快取的時間,可以反回快取的時間。
 // 否則,需要呼叫clock_gettime或者gettimeofday獲取合適的時間;
static int
gettime(struct event_base *base, struct timeval *tp)
{
    EVENT_BASE_ASSERT_LOCKED(base);

     // 首先檢視base中是否有快取的時間,如果有,直接使用快取時間,然後返回即可
    if (base->tv_cache.tv_sec) {
        *tp = base->tv_cache;
        return (0);
    }

     // 使用monotonic_timer獲取當前時間,二選一:
     // CLOCK_MONOTONIC_COARSE 和 CLOCK_MONOTONIC
     // 預設情況下是 CLOCK_MONOTONIC模式
    if (evutil_gettime_monotonic_(&base->monotonic_timer, tp) == -1) {
        return -1;
    }

     // 檢視是否需要更新快取的時間
     // 如果上次更新時間的時間點距離當前時間點的間隔超過CLOCK_SYNC_INTERVAL,則需要更新
    if (base->last_updated_clock_diff + CLOCK_SYNC_INTERVAL
        < tp->tv_sec) {
        struct timeval tv;
          // 使用gettimeofday獲取當前系統real時間
        evutil_gettimeofday(&tv,NULL);
          // 將當前系統real時間和monotonic時間做差,存到base中
        evutil_timersub(&tv, tp, &base->tv_clock_diff);
          // 儲存當前更新的時間點
        base->last_updated_clock_diff = tp->tv_sec;
    }

    return 0;
}

六、evmap_io_initmap_函式和evmap_signal_initmap_函式

當不是在win32環境下時,struct event_signal_map和struct event_io_map是相同的

void
evmap_io_initmap_(struct event_io_map* ctx)
{
    evmap_signal_initmap_(ctx);
}

初始化struct event_signal_map
void
evmap_signal_initmap_(struct event_signal_map *ctx)
{
    ctx->nentries = 0;
    ctx->entries = NULL;
}

下面看struct event_signal_map定義
/* Used to map signal numbers to a list of events.  If EVMAP_USE_HT is not
   defined, this structure is also used as event_io_map, which maps fds to a
   list of events.
*/
// 用來儲存訊號數字和一系列事件之間的對映。如果EVMAP_USE_HT沒有定義,即不是在win32下,
// struct event_io_map和struct event_signal_map一致,event_io_map是將fds和事件對映到一塊
struct event_signal_map {
    /* An array of evmap_io * or of evmap_signal *; empty entries are
     * set to NULL. */
     // 存放的是evmap_io和evmap_signal物件的指標。空entries設定為NULL
    void **entries;
    /* The number of entries available in entries */
     // 可用專案個數
    int nentries;
};

七、初始化變化列表

void
event_changelist_init_(struct event_changelist *changelist)
{
    changelist->changes = NULL;
    changelist->changes_size = 0;
    changelist->n_changes = 0;
}

結構體定義:
/* List of 'changes' since the last call to eventop.dispatch.  Only maintained
 * if the backend is using changesets. */
// 列舉自從上一次eventop.dispatch呼叫之後的改變列表。只有在後臺使用改變集合時才會維護這個列表,否則不維護?
struct event_changelist {
    struct event_change *changes;
    int n_changes;
    int changes_size;
};


八、epoll方法的初始化分析 epoll.c中,epoll後臺方法定義: 參考前文中在event.c中定義的靜態全域性變數陣列:eventops, eventops中儲存就是這個epollops。 此處需要注意的是內部訊號通知機制的實現,是通過管道傳遞外部訊號的,為何不能像IO事件繫結檔案描述符? 因為訊號是全域性的,無法真對某個事件進行繫結,只能通過函式捕捉訊號,然後通過管道通知event_base來實現。
const struct eventop epollops = {
    "epoll",
    epoll_init,
    epoll_nochangelist_add,
    epoll_nochangelist_del,
    epoll_dispatch,
    epoll_dealloc,
    1, /* need reinit */
    EV_FEATURE_ET|EV_FEATURE_O1|EV_FEATURE_EARLY_CLOSE,
    0
};

struct epollop結構體為:

struct epollop {
     // epoll內部定義的結構體,需要檢視epoll原始碼
    struct epoll_event *events;
     // epoll中當前註冊的事件總數
    int nevents;
     // epoll返回的控制代碼
    int epfd;
#ifdef USING_TIMERFD
    int timerfd;
#endif
};

epoll_init函式為:
static void *
epoll_init(struct event_base *base)
{
    int epfd = -1;
    struct epollop *epollop;

    // epoll已經提供新的建立API,epoll_create1,可以設定建立模式,
     // 可以通過編譯選項進行配置是否支援
#ifdef EVENT__HAVE_EPOLL_CREATE1
    /* First, try the shiny new epoll_create1 interface, if we have it. */
    epfd = epoll_create1(EPOLL_CLOEXEC);
#endif
    if (epfd == -1) {
        /* Initialize the kernel queue using the old interface.  (The
        size field is ignored   since 2.6.8.) */
         // 如果核心沒有支援epoll_create1,只能使用epoll_create建立
        if ((epfd = epoll_create(32000)) == -1) {
            if (errno != ENOSYS)
                event_warn("epoll_create");
            return (NULL);
        }
        evutil_make_socket_closeonexec(epfd);
    }

     // 分配struct epollop物件,並賦初值1
    if (!(epollop = mm_calloc(1, sizeof(struct epollop)))) {
        close(epfd);
        return (NULL);
    }

     // 儲存epoll控制代碼描述符
    epollop->epfd = epfd;

    /* Initialize fields */
     // 初始化epoll控制代碼可以處理的事件最大個數以及當前註冊事件數的初始值
    epollop->events = mm_calloc(INITIAL_NEVENT, sizeof(struct epoll_event));
    if (epollop->events == NULL) {
        mm_free(epollop);
        close(epfd);
        return (NULL);
    }
    epollop->nevents = INITIAL_NEVENT;

     // 如果libevent工作模式是啟用epoll的changelist方式,則後臺方法變為epollops_changelist
     // 預設情況下是不選擇的。
    if ((base->flags & EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST) != 0 ||
        ((base->flags & EVENT_BASE_FLAG_IGNORE_ENV) == 0 &&
        evutil_getenv_("EVENT_EPOLL_USE_CHANGELIST") != NULL)) {

        base->evsel = &epollops_changelist;
    }

#ifdef USING_TIMERFD
    /*
      The epoll interface ordinarily gives us one-millisecond precision,
      so on Linux it makes perfect sense to use the CLOCK_MONOTONIC_COARSE
      timer.  But when the user has set the new PRECISE_TIMER flag for an
      event_base, we can try to use timerfd to give them finer granularity.
    */
     // epoll介面通常的精確度是1微秒,因此在Linux環境上,通過使用CLOCK_MONOTONIC_COARSE計時器
     // 可以獲得完美的體驗。但是當用戶使用新的PRECISE_TIMER模式時,可以使用timerfd獲取更細的時間粒度。
     // timerfd是Linux為使用者提供的定時器介面,基於檔案描述符,通過檔案描述符的可讀事件進行超時通知,timerfd、
     // eventfd、signalfd配合epoll使用,可以構造出零輪詢的程式,但是程式沒有處理的事件時,程式是被阻塞的;
     //  timerfd的精度要比uspleep高
    if ((base->flags & EVENT_BASE_FLAG_PRECISE_TIMER) &&
        base->monotonic_timer.monotonic_clock == CLOCK_MONOTONIC) {
        int fd;
        fd = epollop->timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC);
        if (epollop->timerfd >= 0) {
            struct epoll_event epev;
            memset(&epev, 0, sizeof(epev));
            epev.data.fd = epollop->timerfd;
            epev.events = EPOLLIN;
            if (epoll_ctl(epollop->epfd, EPOLL_CTL_ADD, fd, &epev) < 0) {
                event_warn("epoll_ctl(timerfd)");
                close(fd);
                epollop->timerfd = -1;
            }
        } else {
            if (errno != EINVAL && errno != ENOSYS) {
                /* These errors probably mean that we were
                 * compiled with timerfd/TFD_* support, but
                 * we're running on a kernel that lacks those.
                 */
                event_warn("timerfd_create");
            }
            epollop->timerfd = -1;
        }
    } else {
        epollop->timerfd = -1;
    }
#endif
     // 初始化訊號通知的管道
     // 當設定訊號事件時,由於訊號捕捉是針對全域性來說,所以此處訊號捕捉函式是如何通知event_base的呢?
     // 答案是通過內部管道來實現的,一旦訊號捕捉函式捕捉到訊號,則將相應訊號通過管道傳遞給event_base,
     // 然後event_base根據訊號值將相應的回撥事件加入啟用事件佇列,等待event_loop的回撥。
     // 下文有分析此函式
    evsig_init_(base);

    return (epollop);
}