epoll 优点
函数原型:
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev);
int epoll_wait(int epfd, struct epoll_event *evlist, int maxevents; int timeout);
epoll 的三个主要优点:
- 只需要注册一次感兴趣的描述符,不需要每次调用都向内核空间传递;每次返回时,只需要返回就绪的描述符。
- 检查就绪的描述符时,使用回调函数,避免低效的遍历。
- 支持水平触发和边缘触发。
优点一 的实现原理
只需要注册一次感兴趣的描述符,不需要每次调用都向内核空间传递,每次返回时,只需要返回就绪的描述符。
内核中 eventpoll 实例的定义。
struct eventpoll {
/* sys_epoll_wait() 的等待队列 */
wait_queue_head_t wq;
/* file->poll() 的等待队列 */
wait_queue_head_t poll_wait;
/* 就绪的文件描述符列表 */
struct list_head rdllist;
/* 使用红黑树保存目标描述符 */
struct rb_root rbr;
...
};
创建一个文件结构来保存 eventpoll 实例,该文件只包含一个匿名的 inode。以后所有描述符都注册到这个 eventpoll 实例中。这样在调用时不传递目标描述符。
SYSCALL_DEFINE1(epoll_create1, int, flags)
{
int error;
struct eventpoll *ep = NULL;
/* 创建内部数据结构 ("struct eventpoll"). */
error = ep_alloc(&ep);
if (error < 0)
return error;
/*
* 创建一个只包含一个匿名inode的文件结构,即 eventpoll file,
* 该文件包含 eventpoll 实例,并指定相应的文件操作函数
*/
error = anon_inode_getfd("[eventpoll]", &eventpoll_fops, ep,
O_RDWR | (flags & O_CLOEXEC));
if (error < 0)
ep_free(ep);
return error;
}
内核中使用红黑树保存目标描述符,这样在插入,查找,删除的时候只用 log(n) 的时间复杂度,更高效。
SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,
struct epoll_event __user *, event)
{
epi = ep_find(ep, tfile, fd);
error = -EINVAL;
switch (op) {
case EPOLL_CTL_ADD:
if (!epi) {
epds.events |= POLLERR | POLLHUP;
error = ep_insert(ep, &epds, tfile, fd);
}
break;
case EPOLL_CTL_DEL:
if (epi)
error = ep_remove(ep, epi);
break;
case EPOLL_CTL_MOD:
if (epi) {
epds.events |= POLLERR | POLLHUP;
error = ep_modify(ep, epi, &epds);
}
break;
}
return error;
}
优点二的实现原理
避免了类似于 select 和 poll 中的轮询遍历,如果有就绪的描述符就立即返回;如果没有则休眠,让出CPU,一旦有描述符就绪,就能立即被唤醒并返回结果。
SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,
int, maxevents, int, timeout)
{
...
/* Time to fish for events ... */
error = ep_poll(ep, events, maxevents, timeout);
return error;
}
static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
int maxevents, long timeout)
{
retry:
spin_lock_irqsave(&ep->lock, flags);
res = 0;
if (list_empty(&ep->rdllist)) {
/*
* 如果没有任何就绪的描述符,就在此睡眠,
* 可被ep_poll_callback()->wake_up_locked(&ep->wq) 唤醒
* 唤醒后轮询遍历就绪列表
*/
init_waitqueue_entry(&wait, current);
__add_wait_queue_exclusive(&ep->wq, &wait);
for (;;) {
/*
* 设置当前进程为可中断,
* 以便可以被 ep_poll_callback()
* ->ep_poll_safewake(&ep->poll_wait) 唤醒
*/
set_current_state(TASK_INTERRUPTIBLE);
if (!list_empty(&ep->rdllist) || !jtimeout)
break;
if (signal_pending(current)) {
res = -EINTR;
break;
}
spin_unlock_irqrestore(&ep->lock, flags);
jtimeout = schedule_timeout(jtimeout);
spin_lock_irqsave(&ep->lock, flags);
}
__remove_wait_queue(&ep->wq, &wait);
set_current_state(TASK_RUNNING);
}
spin_unlock_irqrestore(&ep->lock, flags);
/*
* 试图向用户空间传递就绪的事件
* 如果没有就绪的事件,或者未超时,就再次循环尝试
*/
if (!res && eavail &&
!(res = ep_send_events(ep, events, maxevents)) && jtimeout)
goto retry;
return res;
}
回调函数实现如下,如果有描述符就绪,就唤醒 eventpoll 等待队列和 文件 poll_wait 等待队列。
static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
spin_lock_irqsave(&ep->lock, flags);
/*
* 在 readylist 为空时,epoll_wait() 函数会睡眠于此,
* 因此唤醒 eventpoll wait list,唤醒以后便会轮询 readylist
*/
if (waitqueue_active(&ep->wq))
wake_up_locked(&ep->wq);
if (waitqueue_active(&ep->poll_wait))
pwake++;
spin_unlock_irqrestore(&ep->lock, flags);
/*
* 在调用 ep_insert() 时,会将目标描述符加入 poll_wait 等待队列,
* 因此也需要唤醒 poll_wait list,
* 唤醒以后如果有描述符就绪,就将其加入就绪队列
*/
if (pwake)
ep_poll_safewake(&ep->poll_wait);
return 1;
}
优点三的实现原理
首先检查是否设置 EPOLLONESHOT
标志位:
- 如果没有设置,则将其再次加入就绪列表,以待下一次 epoll_wait() 检查,因此可以做到在就绪周期内,每次 epoll_wait() 都能返回,也就是水平触发。
- 如果设置了,就从就绪列表删除,只触发一次,也就是边缘触发。
static int ep_send_events_proc(struct eventpoll *ep, struct list_head *head, void *priv)
{
for (eventcnt = 0, uevent = esed->events;
!list_empty(head) && eventcnt < esed->maxevents;) {
epi = list_first_entry(head, struct epitem, rdllink);
list_del_init(&epi->rdllink);
revents = epi->ffd.file->f_op->poll(epi->ffd.file, NULL) &
epi->event.events;
if (revents) {
if (__put_user(revents, &uevent->events) ||
__put_user(epi->event.data, &uevent->data)) {
list_add(&epi->rdllink, head);
return eventcnt ? eventcnt : -EFAULT;
}
eventcnt++;
uevent++;
/* 检查是否设置 EPOLLONESHOT 标志位 */
if (epi->event.events & EPOLLONESHOT)
epi->event.events &= EP_PRIVATE_BITS;
else if (!(epi->event.events & EPOLLET)) {
/*
* 如果该文件设置了边缘触发模式,需要重新将其加入就绪列表,
* 以代下一次 epoll_wait() 的检查
*/
list_add_tail(&epi->rdllink, &ep->rdllist);
}
}
}
return eventcnt;
}