分类: LINUX
2012-07-30 13:56:24
EPOLL内核源代码实现原理分析
黄江伟
will.huang@aliyun-inc.com
epoll的实现主要依赖于一个迷你文件系统:eventpollfs。此文件系统通过初始化。在初始化的过程中,eventpollfs create两个slub分别是:和eppoll_entry。
epoll使用过程中有几个基本的函数分别是epoll_create,epoll_ctl,epoll_wait。涉及到四个重要的数据结构: struct eventpoll , struct epitem, struct epoll_event ,struct eppoll_entry。(作者:黄江伟,will.huang@aliyun-inc.com)
1、epoll_create和epoll_ctl
其中eventpoll是通过epoll_create生成,epoll_create传入一个size参数,size参数只要>0即可,没有任何意义。epoll_create调用函数sys_epoll_create1实现eventpoll的初始化。sys_epoll_create1通过ep_alloc生成一个eventpoll对象,并初始化eventpoll的三个等待队列,wait,poll_wait以及rdlist (ready的fd list)。同时还会初始化被监视fs的rbtree 根节点。
epollcreate在调用ep_alloc通过anon_inode_getfd创建一个名字为“[eventpoll]”的eventpollfs文件描述符号并将file->private_data指定为指向前面生成的eventpoll。这样就将eventpoll和文件id关联。最后返回文件描述符id。
通过epoll_create生成一个eventpoll后,可以通过epoll_ctl提供的相关操作对eventpoll进行ADD,MOD,DEL操作。epoll_ctl有四个参数,分别是:int epfd(需要操作的eventpoll), int op(操作类型), int fd(需要被监视的文件), struct epoll_event *event(被监视文件的相关event)。epoll_ctl首先通过epfd的private_data域获取需要操作的eventpoll,然后通过ep_find确认需要操作的fd是否已经在被监视的红黑树中(eventpoll->rbr)。然后根据op的类型分别作ADD(ep_insert),MOD(ep_modify),DEL(ep_remove)操作。
首先分析ep_insert,ep_insert有四个参数分别为: struct eventpoll *ep(需要操作的eventpoll), struct epoll_event *event(epoll_create传入的event参数,当然得从user空间拷贝过来), struct file *tfile(被监视的文件描述符), int fd(被监视的文件id)。ep_insert首先从slub中分配一个epitem的对象epi。并初始化epitem的三个list头指针,rdllink(指向eventpoll的rdlist),fllist指向(struct file的f_ep_links),pwqlist(指向包含此epitem的所有poll wait queue)。并将epitem的ep指针,指向传入的eventpoll,并通过传入参数event对ep内部变量event赋值。然后通过ep_set_ffd将目标文件和epitem关联。这样epitem本身就完成了和eventpoll以及被监视文件的关联。下面还需要做两个动作:将epitem插入目标文件的polllist并注册回调函数;将epitem插入eventpoll的rbtree。
为了完成第一个动作,还需要一个数据结构ep_pqueue帮忙,ep_pqueue主要包含两个变量一个是epitem还有一个是callback函数(ep_ptable_queue_proc)相关的一个数据结构poll_table,ep_pqueue主要完成epitem和callback函数的关联。然后通过目标文件的poll函数调用callback函数ep_ptable_queue_proc。Poll函数一般由设备驱动提供,以网络设备为例,他的poll函数为sock_poll然后根据sock类型调用不同的poll函数如:packet_poll。packet_poll在通过datagram_poll调用sock_poll_wait,最后在poll_wait实际调用callback函数(ep_ptable_queue_proc)。
ep_ptable_queue_proc函数完成epitem加入到特定文件的wait队列任务。ep_ptable_queue_proc有三个参数:struct file *file(目标文件), wait_queue_head_t *whead(目标文件的waitlist), poll_table *pt(前面生成的poll_table)。在函数中,引入了另外一个非常重要的数据结构。主要完成epitem和epitem事件发生时的callback(ep_poll_callback)函数之间的关联,并将上述两个数据结构包装成一个链表节点,挂载到目标文件file的waithead中。这两还得完成两个动作,首先将的whead指向目标文件的waitlist(传入的参数2),然后初始化base变量指向epitem,最后通过add_wait_queue将epoll_entry挂载到目标文件的waitlist。完成这个动作后,epoll_entry已经被挂载到waitlist,然后还有一个动作必须完成,就是将eppoll_entry挂载到epitem的pwqlist上面。现在还剩下一个动作,就是将epitem的fllink链接到目标文件的f_ep_links上,这部分工作将在poll函数返回后在ep_insert中完成。当然ep_insert除了完成这个动作外,还会完成前面提到的第二步,epitem插入eventpoll的rbtree。完成以上动作后,将还会判断当前插入的event是否刚好发生,如果是,那么做一个ready动作,将epitem加入到rdlist中,并对epoll上的wait队列调用wakeup。
到此为止基本完成了epoll_create以及epoll_ctl最重要的ADD函数的工作介绍。下面进入epoll_wait函数介绍。(作者:黄江伟,will.huang@aliyun-inc.com)
2、epoll_wait
epoll_wait有四个参数int epfd(被wait的epoll所关联的epollfs的fd), struct epoll_event __user * events(返回监视到的事件), int maxevents(每次return的events最大值), int timeout(最大wait时间)。epoll_wait首先会检测传入参数的合法性,包括maxevents有没有超过范围(0<=maxevents
ep_send_events通过对ready_list进行扫描,由于现在在对ready_list进行操作,这个时候必须保证rdlist数据的一致性,如果此时又有新的event ready,那么我们必须提供临时的存储空间,eventpoll提供了一个ovflist用来存储这种event。ep_send_events获取了rdlist后通过完成真正的转发工作。完成转发后,ep_send_events还需要去判断ovflist,如果ovflist中有events,那么还需要将这些events转移到rdlist中。
扫描rdlist从头上面拿出epitem,然后调用epollfs的poll函数(),判断拿出来的那个events是否真的已经ready(这部分比较难理解,没怎么看懂)。如果ready,那么将数据封装到uevent里面,同事这里还需要判断epitem的类型是否是 Triggered如果是,那么还需要把event再次插入队列尾部。(作者:黄江伟,will.huang@aliyun-inc.com)
3、ep_poll_callback
以上描述中还缺少关键的一环,就是如何在被监视文件发生event的时候,如何将epitem加入rdlist并唤醒调用epoll_wait进程。这个工作由ep_poll_callback函数完成。前面提到完成一个epitem和ep_poll_callback的关联,同时会被插入目标文件file的(private_data)waithead中。以scoket为例,当socket数据ready,终端会调用相应的接口函数比如rawv6_rcv_skb,此函数会调用sock_def_readable然后,通过sk_has_sleeper判断sk_sleep上是否有等待的进程,如果有那么通过wake_up_interruptible_sync_poll函数调用ep_poll_callback。
ep_poll_callback函数首先会判断是否rdlist正在被使用(通过ovflist是否等于),如果是那么将epitem插入ovflist。如果不是那么将epitem插入rdlist。然后调用wake_up函数唤醒epitem上wq的进程。这样就可以返回到epoll_wait的调用者,将他唤醒。(作者:黄江伟,will.huang@aliyun-inc.com)