回顾epoll重要结构体所包含的成员:
eventpoll:自旋锁、互斥锁、wq sys_epoll_wait()的等待队列、poll_wait file->poll()的等待队列、rdlist
就绪文件描述符列表、rbrRB树的根节点、ovflist在ep_poll_callback()被调用时且epoll_wait()
已经返回,则程序可能循坏获取events,内核将此刻发生的 events的 epitem用ep_ovflist()连接
保存起来,在下一次epoll_wait()时返回给用户、 创建eventpoll描述符的用户
epitem: epitem表示一个被监听的fd(一个文件)
rbn链接到红黑树、rdllink链接到rdlist、epitem *next链接到ovflist、poll_filefd ffd监听的fd信息
nwait附加到poll的活动等待队列数(即当前fd,插入到几个等待队列)、pwqlist保存被监视的文件
的等待队列、 eventpoll *ep该epitem属于那个eventpoll、list_head fllink fd对应的struct file、
epoll_event event 感兴趣事件及fd
eppoll_entry:list_head llink链接被监视的fd的epitem、wait_queue_t wait用于将epitem链接到等待队列
wait_queue_head_t *whead等待队列的头,链接“wait”等待队列项,即该fd对应的等待队列
struct file :仅显示主要内容其它内容忽略(fd的文件信息) const struct file_operations *f_op 文件类型操作
void *private_data 保存eventpoll信息、struct list_head f_ep_links接口
清理epoll内部某些文件间的关系:
epitem、struct file、epoll_filefd的关系:一个fd对应一个epitem、epitem中存在一个epoll_filefd ffd 使epitem和epoll_filefd连接起来,file中存在一个struct file *file struct file中保存着监听fd的文件结构等详细信息,及epoll_filefd起到一个连接作用,使得被监听的文件和自己的fd关联起来。(epfd是在epoll_create()时创建的eventpollfd,他保存着epoll_create()调用ep_alloc()创建的eventpoll(主体,保存着BR树的根))
eventpoll、eppoll_entry:每调用一次epoll_create()都会在eventpollfd的struct file的private_data中保存一个新创建的eventpoll,eventpoll包含着rbrRB树的根节点,根节点挂载着所有的epitem,eppoll_entry是等待队列所使用的结构体,它包含着指向epitem的指针base,llink(一个epitem对应一个文件,一个文件可能存在多个事件,将他们连接起来),等待队列的头,以及将自身加入等待队列的 wait,eppoll_entry主要完成epitem和epitem事件发生时的callback函数之间的关联。
epoll到底有几个队列。。。。?
队列1:wq sys_epoll_wait()的等待队列,用于epoll_pwait()时候,判定epfd-file->eventpoll->rdlist是否为空,如果为空并且epoll_pwait()为阻塞调用,那么将当前进程将被挂到epfd-file->eventpoll->wq中,并且当前进程进入阻塞等待,直到rdlist非空的时候唤起
队列2:poll_wait 首先将eppoll_entry的whead指向fd的设备等待队列, 然后初始化eppoll_entry的base变量指向epitem, 最后通过add_wait_queue将epoll_entry挂载到fd的设备等待队列上。 当在设备硬件数据到来时,硬件中断处理函数中会唤醒该等待队列上等待的进程时,会调用唤醒函数ep_poll_callback(当fd上出发事件后,将epitem中的rdllink节点加入到rdlist中)
列表1:rdlist 就绪事件列表,如果就绪事件列表不为空过ep_send_events将event转发到用户空间。
列表2:ovflist ovflist在ep_poll_callback()被调用时且epoll_wait()已经返回,则程序可能循坏获取events,内核将此刻发生的 events的 epitem用ep_ovflist()连接保存起来随后放入rdlist,在下一次epoll_wait()时返回给用户
贯通epoll各个函数的调用流程:
1、epoll_create():判断若size>=0,调用epoll_create1()
epoll_create1():通过调用ep_alloc()生成一个eventpoll对象,并初始化,随后返回epoll_create1(),epoll_create1(),再次调用anon_inode_getfd()匿名创建一个fd(eventpollfd),eventpollfd本身并不存在一个真正与之对应的文件,因此内核需要给eventpollfd创建一个"虚拟"的文件,并为之分配真正的struct file结构,并且分配真正的fd;随后把调用ep_alloc()生成的struct epollevent对象作为一个私有数据保存在fd的struct file的private指针中。即可通过fd找到struct file,可通过struct file的private_data域找到eventpoll。最后返回文件描述符fd,作为所有epoll系统调用的第一个参数(epfd),及内核事件表。
2、epoll_ctl():函数实现eventpoll文件的控制器接口,该接口允许在兴趣集内插入/删除/更改文件描述符。
epoll_ctl():通过epfd的private_data域获取需要操作的eventpoll,然后通过ep_find()确认需要操作的fd是否已经存在于被监视的RB树中(eventpoll->rbr)。然后根据op的类型进行ADD(ep_insert())、DEL(ep_remove())、MOD(epoll_modify())操作。
ep_find(): file = fget(epfd); ep = file->private_data; epi = ep_find(ep, tfile, fd); 可以发现ep_find()的第一个参数即为 epoll_create1()在结束时返回的文件描述符fd(epfd),一个由epoll_reate()新创建epfd,系统会给它维护一个真正的epoll文件,该文件带有一个struct eventpoll结构,这个结构上拥有一个BR树,而这个BR树就是每次epoll_ctl时fd存放的位置,即每一个epitem结点的位置,也就是ep_find()查找fd的位置,整个BR树相当于内核事件表,BR树上的结点为一个个的fd。
ep_insert();必须在保持“mtx”加锁的情况下调用。
查看是否达到最大监听数量,随后申请一个epitem空间并初始化,rdllink指向rdlist,fllist指向f_ep_links(链接被监视的fd对应的struct file),pwqlist指向poll_wait_queue(保存被监视的文件的等待队列),将需要监听的文件fd和它的file结构写入,将epitem插入eventpoll的RB树。回调poll_table(),即指定ep_table_queue_proc()为回调函数,将epitem添加到poll的监视队列,随后调用f_op->poll()时会被调用ep_ptable_queue_proc(),即在epoll主动poll某个fd时,用来将epitem与指定的fd关联起来,关联的办法即使用等待队列,随后调用poll(),poll()调用ep_eventpoll_poll(),ep_eventpoll_poll()调用poll_wait()。
ep_table_queue_proc():
ep_poll_callback():
poll():系统函数,一般由设备驱动提供。poll()的内核实现放置于文章末尾
poll_wait():实现eventpoll文件的事件等待接口。验证用户传递的区域是否可写,内核对应用程序采取的策略是绝对不信任,所以内核跟应用程序之间的数据交互大都是copy,不允许指针或引用,epoll_wait()需要内核返回数据给用户空间,内存由程序提供,所以内核需要验证用户传递的空间是否可写。随后通过 file的private_data获取eventpoll ,调用ep_poll()完成wait工作,等待被唤醒
ep_poll(): 计算等待时间,判断eventpoll的rdlist是否为空,如果为空,初始化一个等待队列,将当前进程添加到eventpoll的wq如果非空,通过ep_send_events将event转发到用户空间。
<1> ep_send_events():函数调用ep_scan_ready_list对就绪队列进行扫描
<2>ep_scan_ready_list():将所有的fd从rdlist都转移到了txlist上, 清空rdllist,然后调用ep_send_events_proc()。
<3>ep_send_events_proc():扫描rdlist从头上面拿出epitem,获取事件掩码,如果事件掩码与调用者请求的掩码相同,则将事件传递到用户空间,然后返回到ep_eventpoll_poll函数,完成转发。if(!(epi->event.events & EPOLLET)),存在则再次插入到就绪链表,不存在返回0
rdlist 、xlist、ovflist的转换关系:
1、所有监听到events的epitem都链到rdllist上。
2、将ep->ovflist置为null。
3、初始化txlist(将rdlist所有就绪事件加入到txlist链表中)。
4、调用回调函数遍历epitem并拷贝到用户空间。
5、在ep_poll_callback()被调用时且epoll_wait()已经返回时,程序可能循坏获取events,内核将此刻发生的 events的epitem用ep_ovflist()连接。
6、检查epitem是否已存在,在“sproc”回调执行期间,可能存在epitem被排队到ep->ovflist中,但“txlist”可能已经包含它们。
7、把txlist里留下的fd返还给rdllist以便下次还能从rdllist里发现它。if(!(epi->event.events & EPOLLET))判断是否存在ET标记,若存在则不会将其epitem加入relist直到该epitem的fd彻底改变(EPOLLET (1 << 31))
8、唤醒wq eventpoll等待列表和poll_wait poll()等待列表。
描绘函数调用图:
1、epoll_create():
2、epoll_ctl()
3、epoll_wait()
4、函数调用汇总
static unsigned int scull_p_poll(struct file *filp, poll_table *wait)
{
struct scull_pipe *dev = filp->private_data;
unsigned int mask = 0;//缓冲区是循环的;如果“wp”正好在“rp”之后,则认为缓冲区已满;如果两者相等,则认为缓冲区为空。
down(&dev->sem);
poll_wait(filp, &dev->inq, wait);
poll_wait(filp, &dev->outq, wait);
if (dev->rp != dev->wp)
mask |= POLLIN | POLLRDNORM; /* readable */
if (spacefree(dev))
mask |= POLLOUT | POLLWRNORM; /* writable */
up(&dev->sem);
return mask;
}
//设备先要把current(当前进程)挂在 inq和outq两个队列上然后等设备来唤醒,唤醒后就能拿到事件掩码