Chinaunix首页 | 论坛 | 博客
  • 博客访问: 51376
  • 博文数量: 19
  • 博客积分: 425
  • 博客等级: 下士
  • 技术积分: 238
  • 用 户 组: 普通用户
  • 注册时间: 2011-10-08 15:35
文章分类

全部博文(19)

文章存档

2012年(2)

2011年(17)

我的朋友

分类: LINUX

2012-07-30 13:56:24

EPOLL内核源代码实现原理分析

黄江伟

will.huang@aliyun-inc.com

epoll的实现主要依赖于一个迷你文件系统:eventpollfs。此文件系统通过eventpoll_init初始化。在初始化的过程中,eventpollfs create两个slub分别是:epitemeppoll_entry

epoll使用过程中有几个基本的函数分别是epoll_createepoll_ctlepoll_wait。涉及到四个重要的数据结构: struct eventpoll struct epitem struct epoll_event struct eppoll_entry(作者:黄江伟,will.huang@aliyun-inc.com)

1epoll_createepoll_ctl

其中eventpoll是通过epoll_create生成,epoll_create传入一个size参数,size参数只要>0即可,没有任何意义。epoll_create调用函数sys_epoll_create1实现eventpoll的初始化。sys_epoll_create1通过ep_alloc生成一个eventpoll对象,并初始化eventpoll的三个等待队列,waitpoll_wait以及rdlist readyfd list)。同时还会初始化被监视fsrbtree 根节点。

epollcreate在调用ep_alloc通过anon_inode_getfd创建一个名字为“[eventpoll]”的eventpollfs文件描述符号并将file->private_data指定为指向前面生成的eventpoll。这样就将eventpoll和文件id关联。最后返回文件描述符id

通过epoll_create生成一个eventpoll后,可以通过epoll_ctl提供的相关操作对eventpoll进行ADDMODDEL操作。epoll_ctl有四个参数,分别是:int epfd(需要操作的eventpoll, int op(操作类型), int fd(需要被监视的文件), struct epoll_event *event(被监视文件的相关event)。epoll_ctl首先通过epfdprivate_data域获取需要操作的eventpoll,然后通过ep_find确认需要操作的fd是否已经在被监视的红黑树中(eventpoll->rbr)。然后根据op的类型分别作ADDep_insert),MODep_modify),DELep_remove)操作。

首先分析ep_insertep_insert有四个参数分别为: struct eventpoll *ep(需要操作的eventpoll, struct epoll_event *eventepoll_create传入的event参数,当然得从user空间拷贝过来), struct file *tfile(被监视的文件描述符), int fd(被监视的文件id)。ep_insert首先从slub中分配一个epitem的对象epi。并初始化epitem的三个list头指针,rdllink(指向eventpollrdlist),fllist指向(struct filef_ep_links),pwqlist(指向包含此epitem的所有poll wait queue)。并将epitemep指针,指向传入的eventpoll,并通过传入参数eventep内部变量event赋值。然后通过ep_set_ffd将目标文件和epitem关联。这样epitem本身就完成了和eventpoll以及被监视文件的关联。下面还需要做两个动作:将epitem插入目标文件的polllist并注册回调函数;将epitem插入eventpollrbtree

为了完成第一个动作,还需要一个数据结构ep_pqueue帮忙,ep_pqueue主要包含两个变量一个是epitem还有一个是callback函数(ep_ptable_queue_proc)相关的一个数据结构poll_tableep_pqueue主要完成epitemcallback函数的关联。然后通过目标文件的poll函数调用callback函数ep_ptable_queue_procPoll函数一般由设备驱动提供,以网络设备为例,他的poll函数为sock_poll然后根据sock类型调用不同的poll函数如:packet_pollpacket_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)。在函数中,引入了另外一个非常重要的数据结构eppoll_entryeppoll_entry主要完成epitemepitem事件发生时的callbackep_poll_callback)函数之间的关联,并将上述两个数据结构包装成一个链表节点,挂载到目标文件filewaithead中。这两还得完成两个动作,首先将eppoll_entrywhead指向目标文件的waitlist(传入的参数2),然后初始化base变量指向epitem,最后通过add_wait_queueepoll_entry挂载到目标文件的waitlist。完成这个动作后,epoll_entry已经被挂载到waitlist,然后还有一个动作必须完成,就是将eppoll_entry挂载到epitempwqlist上面。现在还剩下一个动作,就是将epitemfllink链接到目标文件的f_ep_links上,这部分工作将在poll函数返回后在ep_insert中完成。当然ep_insert除了完成这个动作外,还会完成前面提到的第二步,epitem插入eventpollrbtree。完成以上动作后,将还会判断当前插入的event是否刚好发生,如果是,那么做一个ready动作,将epitem加入到rdlist中,并对epoll上的wait队列调用wakeup

到此为止基本完成了epoll_create以及epoll_ctl最重要的ADD函数的工作介绍。下面进入epoll_wait函数介绍。(作者:黄江伟,will.huang@aliyun-inc.com)

2epoll_wait

epoll_wait有四个参数int epfd(被waitepoll所关联的epollfsfd, struct epoll_event __user * events(返回监视到的事件), int maxevents(每次returnevents最大值), int timeout(最大wait时间)。epoll_wait首先会检测传入参数的合法性,包括maxevents有没有超过范围(0<=maxevents(INT_MAX / sizeof(struct epoll_event))));events指向的空间是否可写;epfd是否合法等。参数合法性检测都通过后,将通过epfd获取锁依赖的struct file,然后通过file->private_data获取eventpoll。获取epoll后调用ep_poll函数完成真正的epoll_wait工作。ep_poll函数也是四个参数和epoll_wait唯一的差别就是第一参数是前面获取的eventpoll指针。ep_poll首先根据timeout的值判断是否是无限等待,如果不是将timeoutms)转换为jiffs。然后判断eventpollrdlist是否为空,如果为空,那么将current进程通过一个waitquene entry加入eventpollwaitlistwq)。并将task的状态改为TASK_INTERRUPTIBLE;并通过schedule_timeout让出处理器。如果rdlist非空,那么通过ep_send_eventsevent转发到userspace

ep_send_events通过ep_scan_ready_listready_list进行扫描,由于现在在对ready_list进行操作,这个时候必须保证rdlist数据的一致性,如果此时又有新的event ready,那么我们必须提供临时的存储空间,eventpoll提供了一个ovflist用来存储这种eventep_send_events获取了rdlist后通过ep_send_events_proc完成真正的转发工作。完成转发后,ep_send_events还需要去判断ovflist,如果ovflist中有events,那么还需要将这些events转移到rdlist中。

ep_send_events_proc扫描rdlist从头上面拿出epitem,然后调用epollfspoll函数(ep_eventpoll_poll),判断拿出来的那个events是否真的已经ready(这部分比较难理解,没怎么看懂)。如果ready,那么将数据封装到uevent里面,同事这里还需要判断epitem的类型是否是Level Triggered如果是,那么还需要把event再次插入队列尾部。(作者:黄江伟,will.huang@aliyun-inc.com)

3ep_poll_callback

以上描述中还缺少关键的一环,就是如何在被监视文件发生event的时候,如何将epitem加入rdlist并唤醒调用epoll_wait进程。这个工作由ep_poll_callback函数完成。前面提到eppoll_entry完成一个epitemep_poll_callback的关联,同时eppoll_entry会被插入目标文件file的(private_datawaithead中。以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是否等于EP_UNACTIVE_PTR),如果是那么将epitem插入ovflist。如果不是那么将epitem插入rdlist。然后调用wake_up函数唤醒epitemwq的进程。这样就可以返回到epoll_wait的调用者,将他唤醒。(作者:黄江伟,will.huang@aliyun-inc.com)

阅读(2677) | 评论(1) | 转发(5) |
给主人留下些什么吧!~~

firocu2013-08-22 22:42:02

绝赞!毫无疑问,这是我读过关于epoll实现解读的最精彩的文章,这种精彩来自于作者坚实的技术与理论功底。