sunzixun 发表于 2013-1-31 02:31:46

<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);} elseerror = -EEXIST;break;case EPOLL_CTL_DEL:if (epi)error = ep_remove(ep, epi);elseerror = -ENOENT;break;case EPOLL_CTL_MOD:if (epi) {epds.events |= POLLERR | POLLHUP;error = ep_modify(ep, epi, &epds);} elseerror = -ENOENT;break;}mutex_unlock(&ep->mtx);/*驱动不支持poll 的话就要归还内核被召回的struct file 结构 2个*/error_tgt_fput:fput(tfile);/*这里仅仅归还 eventpoll , fput操作其实做了很多事情他不仅仅是减少了struct file 引用计数 ,这个和file_table 的清理机制相关, 后面专门用一文讲吧error_fput:danyfput(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_entrypwq给 贴到 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-->
页: [1]
查看完整版本: <Linux Kernel>eventepoll2