Redisソース分析(20)---aeイベント駆動


イベント駆动という名词が频繁に出てきて、とても大きく闻こえますが、今日はRedis内部の駆动モデルを研究してみました.一つae.cメインプログラムは、4つのイベントタイプのファイルを加えて、Redisがこれらのイベントをどのように処理しているかを徹底的に明らかにします.Redisのイベント処理では、epoll、select、kqueue、evportが使用されています.evportはよく知られていないかもしれません.前の3つは非常に一般的なイベントで、libeventのイベントネットワークライブラリにも登場しています.著者らは,このイベント駆動モデルを書く際に,単純な多重化のために設計された小さな処理モデルにすぎないと述べた.
/* A simple event-driven programming library. Originally I wrote this code
 * for the Jim's event-loop (Jim is a Tcl interpreter) but later translated
 * it in form of a library for easy reuse.
 *
 * ae               ,       ,         
なので複雑ではありません.イベント駆動モデル全体を理解する前に、定義されたイベント構造体をいくつか理解し、イベントタイプは合計2つ1つのFileEvent、TimeEvent:
/* File event structure */
/*         */
typedef struct aeFileEvent {
	//            1 
    int mask; /* one of AE_(READABLE|WRITABLE) */
    //   
    aeFileProc *rfileProc;
    //   
    aeFileProc *wfileProc;
    //     
    void *clientData;
} aeFileEvent;

/* Time event structure */
/*         */
typedef struct aeTimeEvent {
	//    id
    long long id; /* time event identifier. */
    //    
    long when_sec; /* seconds */
    //    
    long when_ms; /* milliseconds */
    //          
    aeTimeProc *timeProc;
    //             
    aeEventFinalizerProc *finalizerProc;
    //     
    void *clientData;
    //             
    struct aeTimeEvent *next;
} aeTimeEvent;

/* A fired event */
/* fired   ,               */
typedef struct aeFiredEvent {
	//     id
    int fd;
    int mask;
} aeFiredEvent;
FireEventは、処理するファイルEventをマークするために使用されます.
これらのイベントは、aeEventLoopの構造内に存在します.
/* State of an event based program */
typedef struct aeEventLoop {
	//             
    int maxfd;   /* highest file descriptor currently registered */
    int setsize; /* max number of file descriptors tracked */
    //       id
    long long timeEventNextId;
    time_t lastTime;     /* Used to detect system clock skew */
    //3     
    aeFileEvent *events; /* Registered events */
    aeFiredEvent *fired; /* Fired events */
    aeTimeEvent *timeEventHead;
    //       
    int stop;
    //      event API   ,  epoll,select   
    void *apidata; /* This is used for polling API specific data */
    aeBeforeSleepProc *beforesleep;
} aeEventLoop;
各イベントの内部には、対応する処理関数が定義され、関数を変数として構造体に存在する.次はae.cのいくつかのAPIの構成:
/* Prototypes */
aeEventLoop *aeCreateEventLoop(int setsize); /*   aeEventLoop,   fileEvent Fired      setSize  */
void aeDeleteEventLoop(aeEventLoop *eventLoop); /*   EventLoop,             */
void aeStop(aeEventLoop *eventLoop); /*   eventLoop       1 */
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
        aeFileProc *proc, void *clientData); /*  eventLoop        */
void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask); /*        */
int aeGetFileEvents(aeEventLoop *eventLoop, int fd); //       id,       ,         
long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
        aeTimeProc *proc, void *clientData,
        aeEventFinalizerProc *finalizerProc); /*  eventLoop       ,                    */
int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id); //    id,      ,       
int aeProcessEvents(aeEventLoop *eventLoop, int flags); /*   eventLoop         */
int aeWait(int fd, int mask, long long milliseconds); /*        */
void aeMain(aeEventLoop *eventLoop); /* ae        */
char *aeGetApiName(void);
void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep); /*   eventLoop                 */
int aeGetSetSize(aeEventLoop *eventLoop); /*   eventLoop    */
int aeResizeSetSize(aeEventLoop *eventLoop, int setsize); /* EventLoop       */
は、ファイル、時間イベントの追加、修正など、eventLoopの内部での修正にほかならない.最も主要な、最も核心的な方法を見てみましょう.
/* ae        */
void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
    //  eventLoop  stop     1,     
    while (!eventLoop->stop) {
    	//  eventLoop                
        if (eventLoop->beforesleep != NULL)
            eventLoop->beforesleep(eventLoop);
        //while       evetLoop   
        aeProcessEvents(eventLoop, AE_ALL_EVENTS);
    }
}
理屈は簡単で、whileループ、eventLoopのすべてのタイプのイベントを処理し、processEvents()コードの一部を切り取ります.
 numevents = aeApiPoll(eventLoop, tvp);
        for (j = 0; j < numevents; j++) {
            aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
            int mask = eventLoop->fired[j].mask;
            int fd = eventLoop->fired[j].fd;
            int rfired = 0;

	    /* note the fe->mask & mask & ... code: maybe an already processed
             * event removed an element that fired and we still didn't
             * processed, so we check if the event is still valid. */
            if (fe->mask & mask & AE_READABLE) {
                rfired = 1;
                //           ae   ,            
                fe->rfileProc(eventLoop,fd,fe->clientData,mask);
            }
            if (fe->mask & mask & AE_WRITABLE) {
                if (!rfired || fe->wfileProc != fe->rfileProc)
                    fe->wfileProc(eventLoop,fd,fe->clientData,mask);
            }
            processed++;
        }
    }
aeで作成された時間イベントは、現在の時間を基準に作成されます.
/*  eventLoop       ,                    */
long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
        aeTimeProc *proc, void *clientData,
        aeEventFinalizerProc *finalizerProc)
{
    long long id = eventLoop->timeEventNextId++;
    aeTimeEvent *te;

    te = zmalloc(sizeof(*te));
    if (te == NULL) return AE_ERR;
    te->id = id;
    aeAddMillisecondsToNow(milliseconds,&te->when_sec,&te->when_ms);
    te->timeProc = proc;
    te->finalizerProc = finalizerProc;
    te->clientData = clientData;
    //     timeEvent   
    te->next = eventLoop->timeEventHead;
    eventLoop->timeEventHead = te;
    
    //           id
    return id;
}
次に、イベントAPIライブラリを呼び出す方法について説明します.まず、epoll、poll、select、kqueu、evportとは何かを盛大に紹介します.これらはすべてイベントモデルです.
selectイベントのモデル
(1)注目イベントの記述子セット(fd_set)を作成し、1つの記述子について上のリード(read)、ライト(write)、例外(exception)イベントに注目できるので、通常、3つのfd_を作成するsetは、リードイベントに注目する記述子を収集するために使用され、ライトイベントに注目する記述子を収集するために使用され、異常イベントに注目する記述子のセットを収集するために使用される.(2)全fd_をポーリングsetの各fdは、対応するイベントが発生しているかどうかをチェックし、存在している場合は処理します.pollと上記の違いは、ファイル記述子を多重化することであり、1つのファイルに対して3つのファイル記述子セットをポーリングする必要があるが、pollは1つしか必要とせず、効率が高いepollはpollのアップグレードバージョンであり、記述子リストをカーネルに渡し、イベントが発生すると、カーネルはイベントが発生した記述子リストをプロセスに通知し、記述子リスト全体をポーリングすることを回避する.効率の大幅な向上
evportは、evportがオブジェクトの特定のeventをEvent portに関連付けることを意味します.
3つのイベントモデルの原理を理解した後、aeを見てみましょう.cはRedisでどのように呼び出されたのか、
//      event API   ,  epoll,select   
    void *apidata; /* This is used for polling API specific data */
は上の属性で、上の4つのイベントの中で、それぞれ3つのファイルに対応して、それぞれae_です.poll.c,ae_select.c、しかし、彼らのAPI構造は似ています.私はその中の1つの例を挙げて、epollの例を挙げて、まずこのイベントの特定の構造体があります.
typedef struct aeApiState {
    int epfd;
    struct epoll_event *events;
} aeApiState;
には、共通のパターンのテンプレート方法もあります.
static int aeApiCreate(aeEventLoop *eventLoop)
static int aeApiResize(aeEventLoop *eventLoop, int setsize)
static void aeApiFree(aeEventLoop *eventLoop)
static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask)
static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask)
static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp)
static char *aeApiName(void)
作成時にeventloopのAPI dataに値を割り当てます.
 state->epfd = epoll_create(1024); /* 1024 is just a hint for the kernel */
    if (state->epfd == -1) {
        zfree(state->events);
        zfree(state);
        return -1;
    }
    //   state      eventLoop API data 
    eventLoop->apidata = state;
    return 0;
イベントのpollメソッドを取り出すとき、これらのメソッドの1つの区分点です.
static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
    aeApiState *state = eventLoop->apidata;
    int retval, numevents = 0;

    retval = epoll_wait(state->epfd,state->events,eventLoop->setsize,
            tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);
    if (retval > 0) {
.....

selectでのpollメソッドは次のとおりです.
static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
    aeApiState *state = eventLoop->apidata;
    int retval, j, numevents = 0;

    memcpy(&state->_rfds,&state->rfds,sizeof(fd_set));
    memcpy(&state->_wfds,&state->wfds,sizeof(fd_set));

    retval = select(eventLoop->maxfd+1,
                &state->_rfds,&state->_wfds,NULL,tvp);
......
は、最後に、state内のイベントとeventLoopとの間の変換に基づいて動作する.eventLoopへのメッセージ,stateへのメッセージ,内部の処理により最終的なイベント結果が得られる.呼び出しは簡単です.