epoll原理简介( 二 )


static void ep_ptable_queue_proc(struct file *file,wait_queue_head_t *whead, poll_table*pt){ structepitem *epi = ep_item_from_epqueue(pt); structeppoll_entry *pwq;if (epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) { init_waitqueue_func_entry(&pwq->wait, ep_poll_callback); pwq->whead = whead; pwq->base = epi; add_wait_queue(whead, &pwq->wait); list_add_tail(&pwq->llink, &epi->pwqlist); epi->nwait++; } else { epi->nwait = -1; }}ep_ptable_queue_proc()函数的作用就是把epitem结构添加到文件的等待队列中,根据上面的代码可知,当等待队列被唤醒的时候,将会调用ep_poll_callback()函数 。而ep_poll_callback()函数的作用就是把epitem结构放置到eventpoll结构的rdllist队列中 。我们前面分析过,rdllist就是就绪的文件队列 。ep_poll_callback()函数最终会调用wake_up_locked(&ep->wq)唤醒进程 。简化后的代码如下:
static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key){ ... // 把epitem添加到rdllist队列中 if(!ep_is_linked(&epi->rdllink)) list_add_tail(&epi->rdllink,&ep->rdllist);// 唤醒进程 if (waitqueue_active(&ep->wq)) wake_up_locked(&ep->wq); ... return 1;}ep_insert()函数最后一个操作就是调用ep_rbtree_insert()把epitem结构添加的eventpoll结构的红黑树中 。如下图:

epoll原理简介

文章插图
 
 
使用红黑树管理epitem结构的目的是可以根据文件句柄fd快速查找到对应的epitem结构 。红黑树是一棵平衡二叉树,时间复杂度为O(logN) 。
添加文件句柄到epoll之后,就可以调用epoll_wait()函数开始监听文件 。epoll_wait()会调用内核的sys_epoll_wait()函数,而sys_epoll_wait()最终会调用ep_poll()函数,代码如下:
static int ep_poll(struct eventpoll *ep, struct epoll_event __user*events, int maxevents, long timeout){ ... if (list_empty(&ep->rdllist)) { init_waitqueue_entry(&wait,current); wait.flags |= WQ_FLAG_EXCLUSIVE; __add_wait_queue(&ep->wq,&wait);for (;;) { 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); } ... if (!res && eavail && !(res = ep_send_events(ep, events, maxevents))&& jtimeout) goto retry;return res;}ep_poll()函数所做的事情很简单,就是把当前进程设置为可中断睡眠状态,然后添加eventpoll结构的等待队列中,最后调用schedule_timeout()让出CPU 。这样当前进程就会进入睡眠状态,当进程醒来的时候会判断eventpoll结构的rdllist队列是否为空,然后不为空就调用ep_send_events()函数把可读写的文件拷贝到用户态的events数组中 。
那么什么时候当前进程会被唤醒呢?在分析ep_insert()函数的时候,我们提及过当文件状态发生改变时会调用ep_poll_callback()函数,而ep_poll_callback()函数会把就绪的文件添加到rdllist队列,并且就会把当前进程唤醒 。
 
总结本文主要分析了epoll的实现原理,可以知道,epoll并不会对所有文件进行扫描(而select和poll会对所以文件进行扫描),而是使用事件的方式把就绪的文件收集起来,所以epoll的效率非常高 。
当然本文还有一些epoll的细节并没有介绍到,例如水平触发和边缘触发等,有兴趣可以自己研究代码 。
 

【epoll原理简介】


推荐阅读