<Linux Kernel>eventepoll2
下面来看看 epoll 的操作函数 epoll_ctl
SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd, struct epoll_event __user *, event) { int error; struct file *file, *tfile; struct eventpoll *ep; struct epitem *epi; struct epoll_event epds; error = -EFAULT; /*很简单 如果有事件过来了, 首先把用户空间的 struct epoll_event结构保存到内核空间*/ if (ep_op_has_event(op) && copy_from_user(&epds, event, sizeof(struct epoll_event))) goto error_return; /* Get the "struct file *" for the eventpoll file */ error = -EBADF; /*把当前这个 epfd 对应的epoll 从当前进程描述符的fdtable里面给摘出来 :用rcu 扣出来并且增加引用*/ file = fget(epfd); if (!file) goto error_return; /*好吧, 再把你需要操作的那个 file fd也给扣出来 */ tfile = fget(fd); if (!tfile) goto error_fput; /* The target file descriptor must support poll */ error = -EPERM; /*该检查检查驱动到底支持 poll 不, 要知道epoll 就是 EnhancePoll 。。日*/ if (!tfile->f_op || !tfile->f_op->poll) goto error_tgt_fput; /* * We have to check that the file structure underneath the file descriptor * the user passed to us _is_ an eventpoll file. And also we do not permit * adding an epoll file descriptor inside itself. */ /*这个说得很明白 ,意思也是想防止死锁。 但是很糟糕,在11 年3月份的kernel 邮件列表里 还是提到了一个bug , 这个后面会说*/ error = -EINVAL; if (file == tfile || !is_file_epoll(file)) goto error_tgt_fput; /* * 好了,终于说召回了 我们的epoll_event */ ep = file->private_data; /*召回了就用 ,加锁 干活!*/ mutex_lock(&ep->mtx); /*这里就开始肆无忌惮的到 (以 ep->rbr.rb_node 指向为根节点的)那棵RBTree里面找到这个要操作的fd对应的那个位置结构 struct epitem. */ epi = ep_find(ep, tfile, fd); error = -EINVAL; switch (op) { case EPOLL_CTL_ADD: if (!epi) { /*这里可以看到, 如果之前没有的话 增加会导致一个 POLLERR | POLLHUP事件*/ epds.events |= POLLERR | POLLHUP;/ /*插入 很多位置: struct epitem. ,这个后面会讲。下面的操作就很容易明白了*/ error = ep_insert(ep, &epds, tfile, fd); } else error = -EEXIST; break; case EPOLL_CTL_DEL: if (epi) error = ep_remove(ep, epi); else error = -ENOENT; break; case EPOLL_CTL_MOD: if (epi) { epds.events |= POLLERR | POLLHUP; error = ep_modify(ep, epi, &epds); } else error = -ENOENT; break; } mutex_unlock(&ep->mtx); /*驱动不支持poll 的话 就要归还内核被召回的struct file 结构 2个*/ error_tgt_fput: fput(tfile); /*这里仅仅归还 eventpoll , fput操作其实做了很多事情他不仅仅是减少了struct file 引用计数 ,这个和file_table 的清理机制相关, 后面专门用一文讲吧 error_fput:dany fput(file); error_return: return error; }
好了,下面来看有趣的的 ep_insert
在看ep_inert之前 ,我们先来回忆一下 poll
我们都知道在linux 的VFS模型中 struct file_operations 有一个 伟大的 poll 函数指针
unsigned int (*poll) (struct file *, struct poll_table_struct *);
举个例子 , 就看比较常见的 scsi 吧
static unsigned int tgt_poll(struct file * file, struct poll_table_struct *wait)
{
//....
/*tgt_poll_wait 就是该设备的等待队列, 首先要知道 poll_wait 不会阻塞 ,
*然后其实就是去调用 struct poll_table 注册的 qproc 函数指针*/
poll_wait(file, &tgt_poll_wait, wait);
spin_lock_irqsave(&ring->tr_lock, flags);
idx = ring->tr_idx ? ring->tr_idx - 1 : TGT_MAX_EVENTS - 1;
ev = tgt_head_event(ring, idx);
if (ev->hdr.status)/*设置上层用的标志位*/
mask |= POLLIN | POLLRDNORM;
spin_unlock_irqrestore(&ring->tr_lock, flags);
return mask;
}
其实poll 就干了一件事 ,就是返回当前file fd的状态 . select 比较傻,他把集合里面所有位都遍历里了一遍(虽然也就 __FD_SETSIZE个) ACE 提供了几种解决方法,其中比较好的就是ACE_Select_Reactor 这个以后的分析, 好像用到的方法引用了一篇论文 (ACE 其实就是很多论文的实现...)
好 下面就开始将比较有趣的ep_insert
static int ep_insert(struct eventpoll *ep, struct epoll_event *event,
struct file *tfile, int fd) { int error, revents, pwake = 0; unsigned long flags; struct epitem *epi; struct ep_pqueue epq; /*给当前的 epoll fd增加一个修改者计数 当然不能也不会很多*/ if (unlikely(atomic_read(&ep->user->epoll_watches) >= max_user_watches)) return -ENOSPC; /*分配一个新的 struct epitem 位置结构 ,给这个要插入的file fd*/ if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL))) return -ENOMEM; /* 初始化 就绪链表节点; 要挂到struct file 结构 epoll链表的节点; 要连接到 */ INIT_LIST_HEAD(&epi->rdllink); INIT_LIST_HEAD(&epi->fllink); INIT_LIST_HEAD(&epi->pwqlist); epi->ep = ep;/*保留相互引用*/ /*初始化 struct epoll_filefd 当他可以用RBTree的节点*/ ep_set_ffd(&epi->ffd, tfile, fd); /*指向用户传入的 epoll_event 内核拷贝*/ epi->event = *event; epi->nwait = 0; epi->next = EP_UNACTIVE_PTR; /* Initialize the poll table using the queue callback */ epq.epi = epi; /*从上面的poll 回顾应该看出 , 这里其实是对 poll_wait 里面的回调函数进行注册, *ep_ptable_queue_proc 这个关键函数 , 下面专门分析*/ init_poll_funcptr(&epq.pt, ep_ptable_queue_proc); /* * Attach the item to the poll hooks and get current event bits. * We can safely use the file* here because its usage count has * been increased by the caller of this function. Note that after * this operation completes, the poll callback can start hitting * the new item. */ /*这里干了一件很有趣的事情 : 把不仅仅把刚才注册的函数立刻调用, 让poll_wait 的操作立刻生效 ,还能返回当前的状态 , */ revents = tfile->f_op->poll(tfile, &epq.pt); /*看看是不是在poll_wait 回调的安装方法中出现问题, 假设是资源问题*/ error = -ENOMEM; if (epi->nwait < 0) goto error_unregister; /* 记得用file fd自己的锁, 把 struct epitm 结构加到file 自己的epoll 监控队列里面*/ spin_lock(&tfile->f_lock); list_add_tail(&epi->fllink, &tfile->f_ep_links); spin_unlock(&tfile->f_lock); /*插入到RBTree中*/ ep_rbtree_insert(ep, epi); /* We have to drop the new item inside our item list to keep track of it */ spin_lock_irqsave(&ep->lock, flags); /*如果当前的stuct file 已经 有需要的事件发生了 , 可 epi 还没有被连接到ready链表 */ /* If the file is already "ready" we drop it inside the ready list */ if ((revents & event->events) && !ep_is_linked(&epi->rdllink)) { /*把它加到 "全局"的 eventpoll结构中-> 就绪file fd链表*/ list_add_tail(&epi->rdllink, &ep->rdllist); /*唤醒 eventpoll (上层) 和 poll (底层) 的等待队列 后面还会看到类似的地方 *几乎在每次 解中断恢复自选锁之前都会去检查一下 , 比如ep_insert ep_modify scan_ready_list等*/ if (waitqueue_active(&ep->wq)) wake_up_locked(&ep->wq); if (waitqueue_active(&ep->poll_wait)) pwake++; } spin_unlock_irqrestore(&ep->lock, flags); atomic_inc(&ep->user->epoll_watches); /* 安全的(使用了递归计数)唤醒底层的poll 队列 */ if (pwake) ep_poll_safewake(&ep->poll_wait); return 0; /*做一些清理工作 */ error_unregister: ep_unregister_pollwait(ep, epi); spin_lock_irqsave(&ep->lock, flags); if (ep_is_linked(&epi->rdllink)) list_del_init(&epi->rdllink); spin_unlock_irqrestore(&ep->lock, flags); kmem_cache_free(epi_cache, epi); return error; } 好了 ,现在来看看 init_poll_funcptr(&epq.pt, ep_ptable_queue_proc); 里面的 ep_ptable_queue_proc 也就是 poll_wait 的实际操作 说之前 我们先来看一个 重要的数据结构 对于每一个加入到epoll 检测集合里面的file fd 都会有一个这样的 entry 项 , 了解他的重要性了吧 struct eppoll_entry { /* 指向 struct epitem 结构中的 poll 等待队列头 */ struct list_head llink; /* 红星闪闪向太阳 不解释... */ struct epitem *base; /*用来链贴到 底层等待队列的项*/ wait_queue_t wait; /* 指向上面说的那个队列*/ wait_queue_head_t *whead; }; static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead, poll_table *pt) { /*找到包含这个 poll_table 的 ep_pqueue 结构 , 然后利用它 去指向 ep_pqueue中包含的另一个 结构 就是位置结构体: struct epitem *epi;*/ struct epitem *epi = ep_item_from_epqueue(pt); /*下面主要就是初始化一下 eppoll_entry 这个结构, 然后把它贴到内核需要检测的队列和链表上*/ struct eppoll_entry *pwq; /*从内核的 slab 系统中分配到一个 eppoll_entry 结构体*/ if (epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) { /*初始化struct eppoll_entry 中的等待队列, 并给它注册一个 ep_poll_callback */ init_waitqueue_func_entry(&pwq->wait, ep_poll_callback); pwq->whead = whead; pwq->base = epi; /*加入到当前 底层需要检测的whead 等待队列中*/ add_wait_queue(whead, &pwq->wait); /*把这个 struct eppoll_entry pwq 给 贴到 struct epitem epi 的pwqlist 链表末尾 , 增加要检测的 entry 数目*/ list_add_tail(&pwq->llink, &epi->pwqlist); epi->nwait++; } else { /* We have to signal that an error occurred */ epi->nwait = -1; } }
<!--EndFragment-->