redis0.1源碼解析之事件驅動

redis的事件驅動模塊負責處理文件和定時器兩種任務。
下面是幾個函數指針

typedef void aeFileProc(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask);
typedef int aeTimeProc(struct aeEventLoop *eventLoop, long long id, void *clientData);
typedef void aeEventFinalizerProc(struct aeEventLoop *eventLoop, void *clientData);

下面是文件相關的結構體

 struct aeFileEvent {
    int fd;
    int mask; /* one of AE_(READABLE|WRITABLE|EXCEPTION) */
    aeFileProc *fileProc;
    aeEventFinalizerProc *finalizerProc;
    void *clientData;
    struct aeFileEvent *next;
} aeFileEvent;

下面是定時器結構體

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

下面是事件循環的核心結構體

struct aeEventLoop {
    // 定時器id,每創建一個定時器結構體,就加一
    long long timeEventNextId;
    // 兩個鏈表
    aeFileEvent *fileEventHead;
    aeTimeEvent *timeEventHead;
    int stop;
} aeEventLoop;

下面先看一下一些基礎函數,然後再分析具體流程。

1 創建一個事件循環結構體

aeEventLoop *aeCreateEventLoop(void) {
    aeEventLoop *eventLoop;
    eventLoop = zmalloc(sizeof(*eventLoop));
    if (!eventLoop) return NULL;
    eventLoop->fileEventHead = NULL;
    eventLoop->timeEventHead = NULL;
    eventLoop->timeEventNextId = 0;
    eventLoop->stop = 0;
    return eventLoop;
}

2 文件相關函數

2.1 新建一個文件相關的結構體

int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
        aeFileProc *proc, void *clientData,
        aeEventFinalizerProc *finalizerProc)
{
    aeFileEvent *fe;

    fe = zmalloc(sizeof(*fe));
    if (fe == NULL) return AE_ERR;
    fe->fd = fd;
    fe->mask = mask;
    fe->fileProc = proc;
    fe->finalizerProc = finalizerProc;
    fe->clientData = clientData;
    fe->next = eventLoop->fileEventHead;
    eventLoop->fileEventHead = fe;
    return AE_OK;
}

2.2 刪除一個文件相關的結構體

// 刪除某個節點(fd和mask等於入參的節點)
void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask)
{
    aeFileEvent *fe, *prev = NULL;

    fe = eventLoop->fileEventHead;
    while(fe) {
        if (fe->fd == fd && fe->mask == mask) {
            // 說明待刪除的節點是第一個節點,直接修改頭節點的指針
            if (prev == NULL)
                eventLoop->fileEventHead = fe->next;
            else
                // 修改prev節點的next指針指向當前刪除節點的下一個節點
                prev->next = fe->next;
            // 鉤子函數
            if (fe->finalizerProc)
                fe->finalizerProc(eventLoop, fe->clientData);
            // 釋放待刪除節點的內存
            zfree(fe);
            return;
        }
        // 記錄上一個節點,當找到待刪除節點時,修改prev指針的next指針(如果prev非空)爲待刪除節點的下一個節點
        prev = fe;
        fe = fe->next;
    }
}

3 定時器相關函數

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;
    // 頭插法插入eventLoop的timeEventHead隊列
    te->next = eventLoop->timeEventHead;
    eventLoop->timeEventHead = te;
    return id;
}

3.1 時間相關的函數

// 獲取當前時間,秒和毫秒
static void aeGetTime(long *seconds, long *milliseconds)
{
    struct timeval tv;

    gettimeofday(&tv, NULL);
    *seconds = tv.tv_sec;
    *milliseconds = tv.tv_usec/1000;
}

static void aeAddMillisecondsToNow(long long milliseconds, long *sec, long *ms) {
    long cur_sec, cur_ms, when_sec, when_ms;
    // 獲取
    aeGetTime(&cur_sec, &cur_ms);
    // 絕對時間,秒數
    when_sec = cur_sec + milliseconds/1000;
    // 絕對時間,毫秒數
    when_ms = cur_ms + milliseconds%1000;
    // 大於一秒則進位到秒中
    if (when_ms >= 1000) {
        when_sec ++;
        when_ms -= 1000;
    }
    // 返回絕對時間的秒和毫秒
    *sec = when_sec;
    *ms = when_ms;
}

3.2 刪除一個定時器結構體(參考刪除文件相關數據結構的函數)

// 刪除一個timeEvent節點
int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id)
{
    aeTimeEvent *te, *prev = NULL;

    te = eventLoop->timeEventHead;
    while(te) {
        if (te->id == id) {
            if (prev == NULL)
                eventLoop->timeEventHead = te->next;
            else
                prev->next = te->next;
            if (te->finalizerProc)
                te->finalizerProc(eventLoop, te->clientData);
            zfree(te);
            return AE_OK;
        }
        prev = te;
        te = te->next;
    }
    return AE_ERR; /* NO event with the specified ID found */
}

3.3 查找最快到期的定時器節點

// 找出最快到期的節點
static aeTimeEvent *aeSearchNearestTimer(aeEventLoop *eventLoop)
{
    aeTimeEvent *te = eventLoop->timeEventHead;
    aeTimeEvent *nearest = NULL;

    while(te) {
        /*
            nearest記錄當前最快到期的節點,初始化爲NULL
            1 nearest爲空,把當前節點作爲最小值
            2 when_sec小的作爲最小值
            3 when_sec一樣的情況下,when_ms小者爲最小值
        */
        if (!nearest || te->when_sec < nearest->when_sec ||
                (te->when_sec == nearest->when_sec &&
                 te->when_ms < nearest->when_ms))
            nearest = te;
        te = te->next;
    }
    return nearest;
}

最後來看一下事件處理的邏輯,入口函數是

void aeMain(aeEventLoop *eventLoop)
{
    eventLoop->stop = 0;
    while (!eventLoop->stop)
        aeProcessEvents(eventLoop, AE_ALL_EVENTS);
}

該函數由redis初始化時,main函數調用。這個版本使用的多路複用函數是select

int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
    int maxfd = 0, numfd = 0, processed = 0;
    fd_set rfds, wfds, efds;
    aeFileEvent *fe = eventLoop->fileEventHead;
    aeTimeEvent *te;
    long long maxId;
    AE_NOTUSED(flags);

    // 兩種類型的事件都不需要處理
    if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0;
	// 初始化select對應的結構體,讀,寫,異常三種事件
    FD_ZERO(&rfds);
    FD_ZERO(&wfds);
    FD_ZERO(&efds);

    // 處理文件事件
    if (flags & AE_FILE_EVENTS) {
        while (fe != NULL) {
            // 根據需要處理的事件,設置對應的變量對應的位
            if (fe->mask & AE_READABLE) FD_SET(fe->fd, &rfds);
            if (fe->mask & AE_WRITABLE) FD_SET(fe->fd, &wfds);
            if (fe->mask & AE_EXCEPTION) FD_SET(fe->fd, &efds);
            // 記錄最大文件描述符select的時候需要用
            if (maxfd < fe->fd) maxfd = fe->fd;
            // 標記是否有文件事件
            numfd++;
            fe = fe->next;
        }
    }
    // 有文件事件需要處理,或者有time事件並且沒有設置AE_DONT_WAIT(設置的話就不會進入select定時阻塞)標記
    if (numfd || ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
        int retval;
        aeTimeEvent *shortest = NULL;
        /*
            struct timeval {
                long    tv_sec;         // seconds 
                long    tv_usec;        // and microseconds 
            };
        */
        struct timeval tv, *tvp;
        // 有time事件需要處理,並且沒有設置AE_DONT_WAIT標記,則select可能會定時阻塞(如果有time節點的話)
        if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
            // 找出最快到期的節點
            shortest = aeSearchNearestTimer(eventLoop);
        // 有待到期的time節點
        if (shortest) {
            long now_sec, now_ms;
			// 獲取當前時間
            aeGetTime(&now_sec, &now_ms);
            tvp = &tv;
            // 算出相對時間,秒數
            tvp->tv_sec = shortest->when_sec - now_sec;
            // 不夠,需要借位
            if (shortest->when_ms < now_ms) {
                // 微秒
                tvp->tv_usec = ((shortest->when_ms+1000) - now_ms)*1000;
                // 借一位,減一
                tvp->tv_sec --;
            } else {
                // 乘以1000,即微秒
                tvp->tv_usec = (shortest->when_ms - now_ms)*1000;
            }
        } else {
            // 沒有到期的time節點
            // 設置了AE_DONT_WAIT,則不會阻塞在select
            if (flags & AE_DONT_WAIT) {
                tv.tv_sec = tv.tv_usec = 0;
                tvp = &tv;
            } else {
                // 一直阻塞直到有事件發生
                tvp = NULL; /* wait forever */
            }
        }
        
        retval = select(maxfd+1, &rfds, &wfds, &efds, tvp);
        if (retval > 0) {
            fe = eventLoop->fileEventHead;
            while(fe != NULL) {
                int fd = (int) fe->fd;
                // 有感興趣的事件發生
                if ((fe->mask & AE_READABLE && FD_ISSET(fd, &rfds)) ||
                    (fe->mask & AE_WRITABLE && FD_ISSET(fd, &wfds)) ||
                    (fe->mask & AE_EXCEPTION && FD_ISSET(fd, &efds)))
                {
                    int mask = 0;
                    // 記錄發生了哪些感興趣的事件
                    if (fe->mask & AE_READABLE && FD_ISSET(fd, &rfds))
                        mask |= AE_READABLE;
                    if (fe->mask & AE_WRITABLE && FD_ISSET(fd, &wfds))
                        mask |= AE_WRITABLE;
                    if (fe->mask & AE_EXCEPTION && FD_ISSET(fd, &efds))
                        mask |= AE_EXCEPTION;
                    // 執行回調
                    fe->fileProc(eventLoop, fe->fd, fe->clientData, mask);
                    processed++;
                    /* After an event is processed our file event list
                     * may no longer be the same, so what we do
                     * is to clear the bit for this file descriptor and
                     * restart again from the head. */
                    /*
                        執行完回調後,文件事件隊列可能發生了變化,
                        重新開始遍歷
                    */
                    fe = eventLoop->fileEventHead;
                    // 清除該文件描述符
                    FD_CLR(fd, &rfds);
                    FD_CLR(fd, &wfds);
                    FD_CLR(fd, &efds);
                } else {
                    fe = fe->next;
                }
            }
        }
    }
    // 處理time事件
    if (flags & AE_TIME_EVENTS) {
        te = eventLoop->timeEventHead;
        // 先保存這次需要處理的最大id,防止在time回調了不斷給隊列新增節點,導致死循環
        maxId = eventLoop->timeEventNextId-1;
        while(te) {
            long now_sec, now_ms;
            long long id;
            // 在本次回調裏新增的節點,跳過
            if (te->id > maxId) {
                te = te->next;
                continue;
            }
            // 獲取當前時間
            aeGetTime(&now_sec, &now_ms);
            // 到期了
            if (now_sec > te->when_sec ||
                (now_sec == te->when_sec && now_ms >= te->when_ms))
            {
                int retval;

                id = te->id;
                // 執行回調
                retval = te->timeProc(eventLoop, id, te->clientData);
               
                // 是否需要繼續註冊事件,是則修改超時時間,否則刪除該節點
                if (retval != AE_NOMORE) {
                    aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms);
                } else {
                    aeDeleteTimeEvent(eventLoop, id);
                }
                te = eventLoop->timeEventHead;
            } else {
                te = te->next;
            }
        }
    }
    // 處理的事件個數
    return processed; /* return the number of processed file/time events */
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章