kqueue in libevent

[TOC]
kqueue 是 FreeBSD 上的一種的多路復用機制。它是針對傳統的 select/poll 處理大量的文件描述符性能較低效而開發出來的。注冊一批描述符到 kqueue 以后,當其中的描述符狀態發生變化時,kqueue 將一次性通知應用程序哪些描述符可讀、可寫或出錯了。

kqueue知識點說明

kqueue體系只有三樣東西:struct kevent結構體,EV_SET宏以及kevent函數。即 kqueue()、kevent() 兩個系統調用和 struct kevent 結構。

kevent

struct kevent { 
   uintptr_t ident;    /* 事件 ID */ 
   short   filter;    /* 事件過濾器 */ 
    /*
    添加到kqueue中:EV_ADD, 從kqueue中刪除:EV_DELETE, 這兩種是主要的行為
    一次性事件:EV_ONESHOT, 此事件是或操作, 指定了該事件, kevent()返回后, 事件會從kqueue中刪除
    更新事件: EV_CLEAR,此事件是或操作, 手冊上的解釋是,當事件通知給用戶后,事件的狀態會被重置。可以用在類似      于epoll的ET模式,也可以用在描述符有時會出錯的情況。
    其他事件: EOF事件:EV_EOF, 錯誤事件:EV_ERROR(返回值)
    */
   u_short  flags;    /* 行為標識 */ 
    
   u_int   fflags;    /* 過濾器標識值 */ 
   intptr_t data;     /* 過濾器數據 */ 
   void   *udata;    /* 應用透傳數據 */ 
 }; 
在一個 kqueue 中,{ident, filter} 確定一個唯一的事件。

kevent函數

/* kevent 提供向內核注冊反注冊事件和返回就緒事件或錯誤事件: 
kq: kqueue 的文件描述符。 
changelist: 要注冊 / 反注冊的事件數組; 
nchanges: changelist 的元素個數。 
eventlist: 滿足條件的通知事件數組; 
nevents: eventlist 的元素個數。 
timeout: 等待事件到來時的超時時間,0,立刻返回;NULL,一直等待;有一個具體值,等待 timespec 時間值。 返回值:可用事件的個數。*/
int kevent(int kq, const struct kevent *changelist, int nchanges, 
        struct kevent *eventlist, int nevents, 
        const struct timespec *timeout);
kqueue的參考文檔

APUE:KQUEUE / FreeBSD

IBM kqueue

kqueue.c

struct kqop {
    struct kevent *changes;//有事件發生的隊列
    int nchanges;//發生事情的個數。這樣可以遍歷事件隊列
    struct kevent *events;//監控隊列
    int nevents;//監控隊列長度
    int kq;// kq隊列
} kqop;

所有的方法

void *kq_init   (void);
int kq_add  (void *, struct event *);
int kq_del  (void *, struct event *);
int kq_recalc   (void *, int);
int kq_dispatch (void *, struct timeval *); 

將該類注冊為eventop的一個實例。

struct eventop kqops = {
    "kqueue",
    kq_init,
    kq_add,
    kq_del,
    kq_recalc,
    kq_dispatch
};

kq_init

  1. 先檢查環節變量,是否設置為kqueue可用。
  2. 初始化一個kqueue的隊列。
  3. 初始化kevent的事件隊列。
void *
kq_init(void)
{
    int kq;

    /* Disable kqueue when this environment variable is set */
    if (getenv("EVENT_NOKQUEUE"))
        return (NULL);

    memset(&kqop, 0, sizeof(kqop));

    /* Initalize the kernel queue */
    
    if ((kq = kqueue()) == -1) {
        log_error("kqueue");
        return (NULL);
    }

    kqop.kq = kq;

    /* Initalize fields */
    kqop.changes = malloc(NEVENT * sizeof(struct kevent));
    if (kqop.changes == NULL)
        return (NULL);
    kqop.events = malloc(NEVENT * sizeof(struct kevent));
    if (kqop.events == NULL) {
        free (kqop.changes);
        return (NULL);
    }
    kqop.nevents = NEVENT;

    return (&kqop);
}

kq_recal

這個類,不像select。發生一次事件后,需要重新注冊事件。所以直接返回0。

int
kq_recalc(void *arg, int max)
{
    return (0);
}

kq_add

int
kq_add(void *arg, struct event *ev)
{
    struct kqop *kqop = arg;//獲取this指針。
    struct kevent kev;//待添加事件

    //檢查是否為信號事件。
    if (ev->ev_events & EV_SIGNAL) {
        int nsignal = EVENT_SIGNAL(ev);//獲取句柄

        memset(&kev, 0, sizeof(kev));
        kev.ident = nsignal;//identifier for this event
        kev.filter = EVFILT_SIGNAL;// 有很多,EVFILT_READ,EVFILT_WRITE,EVFILT_TIMER等
        kev.flags = EV_ADD;//指定事件操作類型,比如EV_ADD,EV_ENABLE, EV_DELETE等 
        if (!(ev->ev_events & EV_PERSIST))
            kev.flags |= EV_ONESHOT;
        kev.udata = ev;//將ev當做參數了。
        
        if (kq_insert(kqop, &kev) == -1)
            return (-1);
        //注冊信號行為。該信號函數是個空事件。可能留著以后用吧。
        if (signal(nsignal, kq_sighandler) == SIG_ERR)
            return (-1);

        ev->ev_flags |= EVLIST_X_KQINKERNEL;//給libevent的event的ev_flags添加
        return (0);
    }

    if (ev->ev_events & EV_READ) {
        memset(&kev, 0, sizeof(kev));
        kev.ident = ev->ev_fd;
        kev.filter = EVFILT_READ;
        kev.flags = EV_ADD;
        if (!(ev->ev_events & EV_PERSIST))
            kev.flags |= EV_ONESHOT;
        kev.udata = ev;
        
        if (kq_insert(kqop, &kev) == -1)
            return (-1);

        ev->ev_flags |= EVLIST_X_KQINKERNEL;
    }

    if (ev->ev_events & EV_WRITE) {
        memset(&kev, 0, sizeof(kev));
        kev.ident = ev->ev_fd;
        kev.filter = EVFILT_WRITE;
        kev.flags = EV_ADD;
        if (!(ev->ev_events & EV_PERSIST))
            kev.flags |= EV_ONESHOT;
        kev.udata = ev;
        
        if (kq_insert(kqop, &kev) == -1)
            return (-1);

        ev->ev_flags |= EVLIST_X_KQINKERNEL;
    }

    return (0);
}

kq_insert

int
kq_insert(struct kqop *kqop, struct kevent *kev)
{
    int nevents = kqop->nevents;

    if (kqop->nchanges == nevents) {
        struct kevent *newchange;
        struct kevent *newresult;
        //有事件過來,翻倍增加。
        nevents *= 2;

        newchange = realloc(kqop->changes,
                    nevents * sizeof(struct kevent));
        if (newchange == NULL) {
            log_error(__FUNCTION__": malloc");
            return (-1);
        }
        kqop->changes = newchange;
        //申請兩個kevent。kqueue。一個kevent隊列用來注冊。一個kevent用來報告那些事件發生了。
        /* 覺得還是奇怪。不擔心申請到同一片嗎?*/
        newresult = realloc(kqop->changes,
                    nevents * sizeof(struct kevent));

        /*
         * If we fail, we don't have to worry about freeing,
         * the next realloc will pick it up.
         */
        if (newresult == NULL) {
            log_error(__FUNCTION__": malloc");
            return (-1);
        }
        kqop->events = newchange;

        kqop->nevents = nevents;
    }
    /* 壓數據到最后*/
    memcpy(&kqop->changes[kqop->nchanges++], kev, sizeof(struct kevent));

    LOG_DBG((LOG_MISC, 70, __FUNCTION__": fd %d %s%s",
         kev->ident, 
         kev->filter == EVFILT_READ ? "EVFILT_READ" : "EVFILT_WRITE",
         kev->flags == EV_DELETE ? " (del)" : ""));

    return (0);
}

kq_del

int
kq_del(void *arg, struct event *ev)
{
    struct kqop *kqop = arg;
    struct kevent kev;
    //檢查是不是kq的事件
    if (!(ev->ev_flags & EVLIST_X_KQINKERNEL))
        return (0);

    if (ev->ev_events & EV_SIGNAL) {
        int nsignal = EVENT_SIGNAL(ev);

        memset(&kev, 0, sizeof(kev));
        kev.ident = (int)signal;
        kev.filter = EVFILT_SIGNAL;
        kev.flags = EV_DELETE;//刪除事件。
        //刪除事件,也需要傳入kqueue,等待內核刪除。
        if (kq_insert(kqop, &kev) == -1)
            return (-1);
        //刪除信號事件
        if (signal(nsignal, SIG_DFL) == SIG_ERR)
            return (-1);
        //將信號位置0.
        ev->ev_flags &= ~EVLIST_X_KQINKERNEL;
        return (0);
    }

    if (ev->ev_events & EV_READ) {
        memset(&kev, 0, sizeof(kev));
        kev.ident = ev->ev_fd;
        kev.filter = EVFILT_READ;
        kev.flags = EV_DELETE;
        
        if (kq_insert(kqop, &kev) == -1)
            return (-1);

        ev->ev_flags &= ~EVLIST_X_KQINKERNEL;
    }

    if (ev->ev_events & EV_WRITE) {
        memset(&kev, 0, sizeof(kev));
        kev.ident = ev->ev_fd;
        kev.filter = EVFILT_WRITE;
        kev.flags = EV_DELETE;
        
        if (kq_insert(kqop, &kev) == -1)
            return (-1);

        ev->ev_flags &= ~EVLIST_X_KQINKERNEL;
    }

    return (0);
}

kq_dispatch

最重要的事件處理函數。

int
kq_dispatch(void *arg, struct timeval *tv)
{
    struct kqop *kqop = arg;
    struct kevent *changes = kqop->changes;
    struct kevent *events = kqop->events;
    struct event *ev;
    struct timespec ts;
    int i, res;

    TIMEVAL_TO_TIMESPEC(tv, &ts);//將毫秒級的時間,轉為納秒級

    res = kevent(kqop->kq, changes, kqop->nchanges,
             events, kqop->nevents, &ts);
    kqop->nchanges = 0;//這邊把事件數目置0了。 todo
    if (res == -1) {
        if (errno != EINTR) {
            log_error("kevent");
            return (-1);
        }

        return (0);
    }

    LOG_DBG((LOG_MISC, 80, __FUNCTION__": kevent reports %d", res));

    //循環處理每個事件。根據flags來判斷是發生了什么事件。
    for (i = 0; i < res; i++) {
        int which = 0;

        if (events[i].flags & EV_ERROR) {
            /* 
             * Error messages that can happen, when a delete fails.
             *   EBADF happens when the file discriptor has been
             *   closed,
             *   ENOENT when the file discriptor was closed and
             *   then reopened.
             * An error is also indicated when a callback deletes
             * an event we are still processing.  In that case
             * the data field is set to ENOENT.
             */
            if (events[i].data == EBADF ||
                events[i].data == ENOENT)
                continue;
            return (-1);
        }
        //前面add的時候,這邊傳的是ev
        ev = events[i].udata;
        //如果是EV_READ  EV_WRITE   EV_SIGNAL事件,就會跳過。
        if (events[i].filter == EVFILT_READ) {
            which |= EV_READ;
        } else if (events[i].filter == EVFILT_WRITE) {
            which |= EV_WRITE;
        } else if (events[i].filter == EVFILT_SIGNAL) {
            which |= EV_SIGNAL;
        } else
            events[i].filter = 0;

        if (!which)
            continue;
        /*只處理time事件和EV_PERSIST持久事件。
        將該事件添加到活動隊列中。非信號事件只觸發一次。
        并將該事件的ev_flags置為EVLIST_ACTIVE
        */ 
        event_active(ev, which,
            ev->ev_events & EV_SIGNAL ? events[i].data : 1);
    }

    for (i = 0; i < res; i++) {
        /* XXX */
        int ncalls, evres;

        if (events[i].flags & EV_ERROR || events[i].filter == NULL)
            continue;

        ev = events[i].udata;
        if (ev->ev_events & EV_PERSIST)
            continue;

        ncalls = 0;
        if (ev->ev_flags & EVLIST_ACTIVE) {
            ncalls = ev->ev_ncalls;
            evres = ev->ev_res;
        }
        ev->ev_flags &= ~EVLIST_X_KQINKERNEL;
        //非持久事件就刪掉了。
        event_del(ev);
        /* 
        激活事件
        */ 
        if (ncalls)
            event_active(ev, evres, ncalls);
    }

    return (0);
}

收獲

  1. 看完了整體的代碼。感覺還是很神奇的。由不懂到懂經歷了好長的時間。
  2. 代碼上面沒有感覺到什么特別之處。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容