libeventソースコードの解析 - イベントループ

Libeventは最近、学ぶために彼らの知識を要約する記事を書き、ソースコードを読みます。Libeventは、最新の安定版を使用することが好ましく、そして難しさを軽減するためにソースコードを読む必要があり、私はそれが、パッチ-1.4ブランチで、バージョン1.4を選びました。この記事ではまだあなたが私の記事を参照することができます疑問持っている原子炉のモデル、知って、基本的なUnixのネットワークプログラミングを読み取る必要がある一般的なサーバーモデル理論とその実践を

libeventファイル構造

この記事へのLibeventファイル構造Libeventリファレンスマニュアル:予選がより明確に説明し、ここでは簡単な説明。

イベントの和のevent_base

event_baseイベントとコアlibeventですが、また、私たちは主にイベントの2種類の構造を中心に、コアを探検したいと展開event_base、イベントはイベントの構造を定義し、イベントループがevent_baseフレームワークであり、これら二つの構造はで定義されていますevent.hとイベントinternal.hをファイル。イベントがevent.cにAPIを削除、イベントの初期化、イベント登録を定義する、イベントはまた、ループフレームイベントベースの関連APIを含みます。

evbuffer和bufferevent

evbuffer buffereventと書き込みバッファlibevent問題のプロセスは、二つの構造は、ヘッダファイルevent.hで定義されており、関連するAPIのAPIは、関連する文書やevbuffer.c buffer.cとファイルに定義されています。buffereventバッファ管理はevbufferポインタに2つ、読み出しバッファ、書き込みバッファが含まれている構造です。evbufferは、基礎となるIOを扱っています。また、私は言及する必要があり、単一のキャッシュメモリのあまりを消費しないようにする高及び低水位、高水位を設定した読み取りおよび書き込みキャッシュバッファゾーンのbuffereventがあり、低水位は、コールバック関数の呼び出しを削減することです時間と効率を向上させます。

IO多重化システム

libeventは異なる方法で異なるプラットフォームでマルチチャンネルIOを実装し、クロスプラットフォームのネットワークライブラリで、それも同じプラットフォームで多くの方法で実施することができる、libeventは、他の方法をkqueueの選択、投票、ファイルディスクリプタをサポートしています。

有用

utilのモジュールであること等ロギング機能、処理時間の関数としての一般的な方法のいくつか

イベント

さらに時限イベントを含む、イベントの読み書きに加えて、さらに抽象化イベントをlibevent、またはさらに処理する信号イベントに変換します。最初のイベントの構造を見てください。

  1. チェーンイベント登録およびアクティベーションイベントを保存するlibevent、ev_nextが登録されたすべてのイベントのリストであり、ev_active_nextは、イベントのチェーンを活性化され、ev_signal_next信号がイベントのリストです。最小ヒープは非常に効率的な方法であるとタイムイベントヒープは、ヒープのトップの準備ができていない場合にのみ、あなたが、その後、確か回準備ができていないの後ろに、ヒープすべてのイベント、タイムイベントのトップを決定する必要があり、最小で管理します。

  2. 各イベントは、イベントのイベントループにev_baseポイントが配置され、イベントをスケジュールするために、循環event_baseを持つことになります。

  3. ev_eventsは、次のような状況であってもよいし、イベントの種類に興味のあるイベントを示します。

// 时间事件
#define EV_TIMEOUT  0x01
// 可读事件
#define EV_READ     0x02
// 可写事件
#define EV_WRITE    0x04
// 信号
#define EV_SIGNAL   0x08
// 标识是否为永久事件。非永久事件激活一次后,就会从注册队列中删除,如果想继续监听该事件,需要再次加入事件队列。而永久事件则激活后不会从注册事件中删除,除非自己手动删除。
#define EV_PERSIST  0x10    /* Persistant event */
复制代码
  1. イベントは、タイムイベントであれば、それはこのイベントev_timeout長いタイムアウトです。

  2. libeventのイベントが優先順位を使用することができ、高い優先度のイベントは、イベントの優先順位がev_priで、常に最初の応答です。

  3. ev_callbackは、イベントがトリガされたときの処理のためにコールバック関数を呼び出して、イベントに対応するコールバック関数です。

struct event {
    /*
    ** libevent 用双向链表来保存注册的所有事件,包括IO事件,信号事件。
    ** ev_next 存储了该事件在事件链表中的位置
    ** 另外,libevent 还用另一个链表来存储激活的事件,通过遍历激活的事件链表来分发任务
    ** ev_active_next 存储了该事件在激活事件链表中的位置
    ** 类似,ev_signal_next 就是该事件在信号事件链表中的位置
    */
	TAILQ_ENTRY (event) ev_next;
	TAILQ_ENTRY (event) ev_active_next;
	TAILQ_ENTRY (event) ev_signal_next;
    /* libevent 用最小堆来管理超时时间,min_heap_idx 保存堆顶的 index */
	unsigned int min_heap_idx;	/* for managing timeouts */

    /* event_base 是整个事件循环的核心,每个 event 都处在一个 event_base 中,ev_base 保存这个结构体的指针 */
	struct event_base *ev_base;
    /* 对于 IO 事件,ev_fd 是绑定的文件描述符,对于 signal 事件,ev_fd 是绑定的信号 */
	int ev_fd;
    /* 要处理的事件类型, */
	short ev_events;
    /* 事件就绪执行时,调用ev_callback的次数,通常为1 */
	short ev_ncalls;
	short *ev_pncalls;	/* Allows deletes in callback */
    /* 事件超时的时间长度 */
	struct timeval ev_timeout;
    /* 优先级 */
	int ev_pri;		/* smaller numbers are higher priority */
    /* 响应事件时调用的callback函数 */
	void (*ev_callback)(int, short, void *arg);
	void *ev_arg;

	int ev_res;		/* result passed to event callback */
    /* 表示事件所处的状态 */
	int ev_flags;
};
复制代码

event_set、event_add、event_del:イベントを処理するための3つの主要なAPIがあります

event_set event_setイベントオブジェクトを初期化するために使用されます

void event_set(struct event *ev, int fd, short events,
      void (*callback)(int, short, void *), void *arg)
{
    /* Take the current base - caller needs to set the real base later */
    /* current_base 是一个全局变量,ev_base 会默认指向这个变量,
    ** 之后 ev_base 也可以通过 event_base_set 设置指向指定的 event_base 
    ** 特别是对于一个进程中有多个 event_base 的情况下,需要绑定到指定的 event_base 上*/
    ev->ev_base = current_base;

    ev->ev_callback = callback;
    ev->ev_arg = arg;
    ev->ev_fd = fd;
    ev->ev_events = events;
    ev->ev_res = 0;
    ev->ev_flags = EVLIST_INIT;
    ev->ev_ncalls = 0;
    ev->ev_pncalls = NULL;

    min_heap_elem_init(ev);

    /* by default, we put new events into the middle priority */
    /* 设定默认优先级为最大优先级的一半 */
    if(current_base)
        ev->ev_pri = current_base->nactivequeues/2;
}
复制代码

event_add event_addは読んでのイベント、イベントのイベントリスナーがまたは信号に書いた場合、それは最初のIO多重化システムに追加されます、イベント登録イベントキューに追加して、登録されたイベントのリストに追加されます。tv引き数がNULLでない場合、それはイベント最小ヒープの時間にイベントを登録します。

int event_add(struct event *ev, const struct timeval *tv)
{
    // 要注册的evbase
    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 */
    }

    /* ev_events 监听的事件类型为读写或者信号 而且 该事件没有被注册过,也不在激活队列里 */
    if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&
        !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {
        /* 将事件注册到 IO 多路复用中 */
        res = evsel->add(evbase, ev);
        if (res != -1)
            /* 注册成功后将事件加入到 event_base 的事件链表中 */
            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);
}
复制代码

イベントループ

コアevent_baseイベントのスケジュール、イベントinternal.hを中event_base定義ヘッダファイルを紹介するイベントの紹介。

まず、event_base EVSELのメンバーがあり、関数ポインタメンバーは、IO多重化資源、eventop次の構造を保持します。

struct eventop {
    const char *name;
    void *(*init)(struct event_base *);
    int (*add)(void *, struct event *);
    int (*del)(void *, struct event *);
    int (*dispatch)(struct event_base *, void *, struct timeval *);
    void (*dealloc)(struct event_base *, void *);
    /* set if we need to reinitialize the event base */
    int need_reinit;
};
复制代码

各IO多重化は、初期化を達成しているために、追加、デル、派遣いくつかの方法、initは初期化され、追加イベントを追加することで、デルは、イベントを削除することで、diapatchは、イベントを待機するように活性化され、活性化処理し、イベント。event_baseのevbaseのメンバーはこのメンバーは、IO多重化資源を保持して、もあります。例えば、最初のパラメータには、機能を追加し、これはevbaseを渡すことです*無効、*無効です。実際には、このアプローチは、それがオブジェクト指向言語はとても複雑にする必要はありませんであれば、関数ポインタCによって多状態を達成することです。しかし、この方法では、私たちが学ぶことができるC多型で実装されています。

event_base activequeuesのメンバーはこれがポインタへのポインタであることを説明する必要があります。キューを活性化することは、リンクリスト内の同じ優先度の活性化イベントを優先させる前に、その後、別の優先度キューのヘッドノードの活性化の数がキューを形成することになると述べました。このように、ポインタへのポインタがあります。

struct event_base {
    /* eventop 对象指针,决定了使用哪种IO多路复用资源 
    ** 但是 eventop 实际上只保存了函数指针,最后资源的句柄是保存在 evbase 中。
    ** 比如要使用 epoll,那么就应该有一个 epoll 的文件描述符,eventop 中只保存了epoll相关的add,del等函数
    ** epoll 的文件描述符是保存在 evbase 中的,因此调用的形式就是 evsel->add(evbase, ev);
    */
    const struct eventop *evsel;
    void *evbase;
    /* event base 上所有事件的数量包括注册事件和激活事件
    ** 在 event_queue_insert 函数中加 1 */
    int event_count;        /* counts number of total events */
    /* event base 上被激活的事件的数量 */
    int event_count_active; /* counts number of active events */

    int event_gotterm;      /* Set to terminate loop */
    int event_break;        /* Set to terminate loop immediately */

    /* active event management */
    /* libevent 支持事件的优先级,对于激活的事件,不同优先级的事件存储在不同的链表中 
    ** 然后再用一个链表把这些链表串起来
    */
    struct event_list **activequeues;
    /* 事件可以设定的最大优先级 */
    int nactivequeues;

    /* signal handling info */
    struct evsignal_info sig;
    /* 保存所有注册事件的链表 */
    struct event_list eventqueue;
    /* 上一次进行事件循环的时间 */
    struct timeval event_tv;
    /* 管理时间事件的小顶堆 */
    struct min_heap timeheap;

    struct timeval tv_cache;
};
复制代码

event_base主要な利害関係者について、次のような機能です:event_base_new、event_base_free、event_base_loop

それぞれevent_base_newとevent_base_free、event_baseリソースを割り当てられ、event_baseリソースを解放されて、それがよりよく理解されています。まずevent_base_new event_baseによって作成され、その後、別のイベントを作成し、event_base_loop開始イベントで最後のサイクルでevent_base登録してください。あなたがイベントループを終了したい場合は、event_base_loopbreakやevent_loopexit_cbを呼び出すことができます。

コアイベントループがディスパッチ関数呼び出しIOが多重化がディスパッチ関数を呼び出す前には、まず、今から最新の時間イベントを計算することに留意すべきであるが、どのくらいの期間、その後、派遣のブロッキング時間として、この時間差、今回のイベントは意志タイムリーな応答は、あまりにも多くの時間を待つには長すぎるIO多重化でブロックされることはありませんすることができます。これは、次のようにRedisのイベントループが実装され、一般的に行われています。

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;

    if (base->sig.ev_signal_added)
        evsignal_base = base;
    done = 0;
    while (!done) {
        /* Terminate the loop if we have been asked to */
        /* 调用 event_loopexit_cb 跳出循环,为什么搞了两个函数? */
        if (base->event_gotterm) {
            base->event_gotterm = 0;
            break;
        }

        /* 调用 event_base_loopbreak 函数跳出循环 */
        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)) {
            timeout_next(base, &tv_p);
        } else {
            /* 
             * if we have active events, we just poll new events
             * without waiting.
             */
            /* 如果有激活事件,将阻塞时间设置为 0 */
            evutil_timerclear(&tv);
        }
        
        /* If we have no events, we just exit */
        /* 如果已经没有事件了,则退出循环 */
        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;
        /* 调用 IO 多路复用函数等待事件就绪,就绪的信号事件和IO事件会被插入到激活链表中 */
        res = evsel->dispatch(base, evbase, tv_p);

        if (res == -1)
            return (-1);
        /* 写时间缓存 */
        gettime(base, &base->tv_cache);
        /* 检查heap中的时间事件,将就绪的事件从heap中删除并插入到激活队列中 */
        timeout_process(base);
        /* 如果有激活的信号事件和IO时间,则处理 */
        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);
}
复制代码

概要

コアlibeventのイベントループは、それらの上に記載されており、場所の実装の詳細については、私のgithubの上の対応する中国人のコメントを与えています。また、libeventのイベントループを記述した非常に直感的な図がある、と私は少し、修正掲示し、ネットワーク上で共有するために、もう一度戻って描きました。

libeventのイベントループ

おすすめ

転載: juejin.im/post/5d8cc14af265da5b9f7c52d5