EPOLL 内核实现

epoll是由一组系统调用组成。
     int epoll_create(int size);
     int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
     int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
     select/poll的缺点在于:
     1.每次调用时要重复地从用户态读入参数。
     2.每次调用时要重复地扫描文件描述符。
     3.每次在调用开始时,要把当前进程放入各个文件描述符的等待队列。在调用结束后,又把进程从各个等待队列中删除。
     在实际应用中,select/poll监视的文件描述符可能会非常多,如果每次只是返回一小部分,那么,这种情况下select/poll

显得不够高效。epoll的设计思路,是把select/poll单个的操作拆分为1个epoll_create+多个epoll_ctrl+一个epoll_wait。

epoll机制实现了自己特有的文件系统eventpoll filesystem

epoll_create创建一个属于该文件系统的文件,然后返回其文件描述符。

 

struct eventpoll 保存了epoll文件节点的扩展信息,该结构保存于file结构体的private_data域中,每个epoll_create创建的epoll

描述符都分配一个该结构体。该结构的各个成员的定义如下,注释也很详细。

 

而通过epoll_ctl接口加入该epoll描述符监听的套接字则属于socket filesystem,这点一定要注意。每个添加的待监听(这里监听

和listen调用不同)都对应于一个epitem结构体,该结构体已红黑树的结构组织,eventpoll结构中保存了树的根节点(rbr成员)。

同时有监听事件到来的套接字的该结构以双向链表组织起来,链表头也保存在eventpoll中(rdllist成员)。

 

epoll_create的调用很简单,就是创建一个epollevent的文件,并返回文件描述符。

epoll_ctl用来添加,删除以及修改监听项。

同样,代码很清楚。先来看看添加流程

init_poll_funcptr函数注册poll table回调函数。然后程序的下一步是调用tfile的poll函数,并且poll函数的第2个参数为poll table,

这是epoll机制中唯一对监听套接字调用poll时第2个参数不为NULL的时机。ep_ptable_queue_proc函数的作用是注册等待函数

并添加到指定的等待队列,所以在第一次调用后,该信息已经存在了,无需在poll函数中再次调用了。

 

那么该poll函数到底是怎样的呢,这就要看我们在传入到epoll_ctl前创建的套接字的类型(socket调用)。对于创建的tcp套接字

来说,可以按照创建流程找到其对应得函数是tcp_poll。

tcp_poll的主要功能为:

  1. 如果poll table回调函数存在(ep_ptable_queue_proc),则调用它来等待。注意这只限第一次调用,在后面的poll中都无需此步
  2. 判断事件的到达。(根据tcp的相关成员)

tcp_poll注册到的等待队列是sock成员的sk_sleep,等待队列在对应的IO事件中被唤醒。当等待队列被唤醒时会调用相应的等待回调函数

,前面看到我们注册的是函数ep_poll_callback。该函数可能在中断上下文中调用。

 

注意这里有2中队列,一种是在epoll_wait调用中使用的eventpoll的等待队列,用于判断是否有监听套接字可用,一种是对应于每个套接字

的等待队列sk_sleep,用于判断每个监听套接字上事件,该队列唤醒后调用ep_poll_callback,在该函数中又调用wakeup函数来唤醒前一种

队列,来通知epoll_wait调用进程。

该函数是在epoll_wait中调用的等待函数,其等待被ep_poll_callback唤醒,然后调用ep_send_events来把到达事件copy到用户空间,然后

epoll_wait才返回。

 

最后我们来看看ep_poll_callback函数和ep_send_events函数的同步,因为他们都要操作ready queue。

eventpoll中巧妙地设置了2种类型的锁,一个是mtx,是个mutex类型,是对该描述符操作的基本同步锁,可以睡眠;所以又存在了另外一个

锁,lock,它是一个spinlock类型,不允许睡眠,所以用在ep_poll_callback中,注意mtx不能用于此。

注意由于ep_poll_callback函数中会涉及到对eventpoll的ovflist和rdllist成员的访问,所以在任意其它地方要访问时都要先加mxt,在加lock锁。

 

由于中断的到来时异步的,为了方便,先看ep_send_events函数。

该函数的注释也很清晰,不过我们从总体上分析下。

 

首先函数加mtx锁,这时必须的。

然后得工作是要读取ready queue,但是中断会写这个成员,所以要加spinlock;但是接下来的工作会sleep,所以在整个loop都加spinlock显然

会阻塞ep_poll_callback函数,从而阻塞中断,这是个很不好的行为,也不可取。于是epoll中在eventpoll中设置了另一个成员ovflist。在读取ready

queue前,我们设置该成员为NULL,然后就可以释放spinlock了。为什么这样可行呢,因为对应的,在ep_poll_callback中,获取spinlock后,对于

到达的事件并不总是放入ready queue,而是先判断ovflist是否为EP_UNACTIVE_PTR。

 

所以在此期间,到达的事件放入了ovflist中。当loop结束后,函数接着遍历该list,添加到ready queue中,最后设置ovflist为EP_UNACTIVE_PTR,

这样下次中断中的事件可以放入ready queue了。最后判断是否有其他epoll_wait调用被阻塞,则唤醒。

 

 

 

从源代码中,可以看出epoll的几大优点:

  1. 用户传入的信息保存在内核中了,无需每次传入
  2. 事件监听机制不在是 整个监听队列,而是每个监听套接字在有事件到达时通过等待回调函数异步通知epoll,然后再返回给用户。

同时epoll中的同步机制也是一个内核编程的设计经典,值得深入理解。


epoll描述

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值