[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的參考文檔
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
- 先檢查環節變量,是否設置為kqueue可用。
- 初始化一個kqueue的隊列。
- 初始化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);
}
收獲
- 看完了整體的代碼。感覺還是很神奇的。由不懂到懂經歷了好長的時間。
- 代碼上面沒有感覺到什么特別之處。