参考自:http://blog.csdn.net/21aspnet/article/details/2627662?reload#reply 里面有源码的详细剖析。
POLL实现剖析:
sys_poll()首先设置关键数据结构struct poll_wqueues中的回调函数poll_wait(用来将current加入到每个fd对应的等待队列中)。接着kmalloc()获得存储pollfd结构数组的空间(通常是一页poll_list包含若干pollfd),讲用户pollfd数组复制到内核空间(瓶颈一:参数传递和页分配)。
接着调用do_poll()循环检测fd事件就绪标志,直到有fd就绪,超时或者信号到来。第一次循环将current加入到所有fd相关等待队列中(除非超时为0),检测是否退出循环(fd事件就绪标志,直到有fd就绪,超时或者信号到来),否则等待(设置超时时间,当传给poll的timeout为-1时,这里是永久等待)再继续循环。后面的循环负责检测事件的就绪标志,不再重复将current加入到等待队列中。
上面的具体调用过程是:do_poll() 对每一poll_list调用 do_pollfd() ,后者对每一fd调用 file->f_op->poll() ,file->f_op->poll()检测事件标志并可能通过上面设置的回调函数把current加入到待队列中(传入的回调函数为NULL则不处理,对应上面的第二次及以后的循环)。(瓶颈二:需要遍历查看是否有就绪描述符)
回调函数poll_wait()具体是为每个fd初始化struct poll_table_entry结构,同过此结构中的wait字段将current加入到等待队列中。
以下是struct poll_wqueues table 相关结构。(poll_table_page和poll_table_entry是包含关系)
EPOLL实现剖析:
为了避免上述的瓶颈一:参数传递和页分配,epoll采取在内核空间保留参数的方法,具体是采取红黑树结构来添加/删除/修改操作的epoll描述符事件(通过epoll_ctl())。
为了避免上述的瓶颈二:需要遍历查看是否有就绪描述符,采取唤醒回调机制,即当事件就绪时自动回调函数将事件添加到就绪链表中并唤醒进程,事件驱动模型。而不是进程再去遍历查看描述符就绪事件。
具体剖析如下:
epoll采取了文件系统接口,这也体现了UNIX一切皆文件的特点。所以epoll以模块的形式注册eventepollfs特殊文件系统,另外创建两个slab告诉缓存: struct epitem(红黑树元素) struct eppoll_entry(用于事件等待,类似poll的poll_table_entry)
sys_epoll_create(int size): 创建一个新的epoll,创建相应的file和inode,创建新的struct eventpoll并把地址
赋给file->private_data。
sys_epoll_ctl(int epfd, int op, int fd, struct epoll_event __user *event):
从红黑树中(eventpoll的rb_root是红黑树的根)添加/删除/修改操作的epoll描述符事件。
sys_epoll_wait(int epfd, struct epoll_event __user *events, int maxevents, int timeout):
循环检测就绪链表(eventpoll的rdlist)是否为空,如果不为空,超时到达或者信号到来,则返回,否则睡眠直到超时到达或有检测事件发生,继续循环。
核心的唤醒回调是通过调用epoll_ctl插入元素的时候,通过init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);设置了唤醒回调函数,该函数ep_poll_callback在事件到达时执行,将对应的描述符epi加入到就绪链表rdlist中。
对于ET边缘触发的处理,是sys_epoll_wait后半部分处理的,具体处理分下面三个函数:
ep_collect_ready_items(ep, &txlist, maxevents):将ep的rdlist中的元素移到txlist中。
ep_send_events(ep, &txlist, events):调用file->f_op->poll()检测事件,如果关注事件到来则向用户空间参数写入事件信息,即写入epoll_event数组。
ep_reinject_items(ep, &txlist):如果描述符没有设置EPOLLET且事件是就绪的,那么将该描述符对应的epi就绪加入到就绪链表rdlist中。
如果设置了ET,那么用户缓存就绪信息,即高速模式。