Epoll也是通过文件描述符的方式控制,因此,epoll被设计成虚拟文件系统” eventpollfs”。
数据结构: struct eventpoll;epoll文件的控制结构,存储在file-> private_data。 ep->wq: 等待ep事件发生的wait queue。通过sys_epoll_wait(…)加入的wait queue。 ep->poll_wait: 等等ep文件接口事件发生的wait queue,通过ep_eventpoll_poll(…)加入的wait queue。一般情况下,使用eq->wq较普遍!
struct epitem; 描述一个被监视文件的相关信息。Epitem可以在eventpoll的几个链中,分别为: epi->rbn:对应ep->rbr,表示ep监视文件表中的一个结点。 epi->rdllink:对应ep->rdllist,表示ep监视文件表中已经ready的epitem链表。 epi->fllink: 对应tfile-> f_ep_links,用于关闭tfile时,从ep中释放相应的epitem。 epi->pwqlist: epitem 加入的wait queue的列表。 epi->txlink: transfer list,用户调用epoll_wait,将ep的已经ready的文件中转化为用户的event集合时使用。
struct eppoll_entry; 描述poll hook,通过这个结构可以把epitem加入被监视文件的wait queue。
EPOLL模块初始化 eventpoll_init():创建epitem cache和epoll_entry cache,注册并挂载eventpollfs。
接口函数: 创建一个epoll文件的函数 long sys_epoll_create(int size): 1. 申请eventpoll结构ep; 2. 创建一个epoll inode, 创建一个dentry,获取get_unused_fd,get_empty_filp获取file, 然后把file->private_data = ep; 然后调用fd_install(fd, file)安装epoll,最后返回文件描述符epollfd。
sys_epoll_ctl (int epfd, int op, int fd, struct epoll_event __user *event): 1. 通过epfd找到eventpoll结构ep, 通过fd找到需要查询的文件tfile, 并确认tfile有支持poll函数。 2. 通过红黑树查找被监视的tfile是否在ep-> rbr中。如果在,则返回其监视fd的epitem。 3. 看操作类型op,决定做不同的事情: a) 如果是EPOLL_CTL_ADD,则调用ep_insert()生成亲的epitem,被设置epi->event = *event; 然后将epitem加入ep->rbr红黑树中。 调用tfile->f_op->poll(tfile, &epq.pt),将epitem加入tfile的waitquque中,注册唤醒回调函数:ep_poll_callback()。将epi->fllink加入tfile->f_ep_links链表中。
获取event,如果有感兴趣的事件,则将调用list_add_tail(&epi->rdllink, &ep->rdllist)将epitem加入到ep-> rdllist。如果ep->wq有等待进程,则唤醒等待进程。 注:这里设置的epi->event除了用户设置的事件外,还自动添加了POLLERR | POLLHUP事件,因此用户不需要额外设置这两个事件。
b) 如果是EPOLL_CTL_modify,则调用ep_modify(),更新epi->event.events,然后获取tfile的poll事件,(注意:这里不会加入被监视的事件wait queue),如果有感兴趣的事件,则调用list_add_tail(&epi->rdllink, &ep->rdllist);
c) 如果是EPOLL_CTL_DEL,调用ep_remove(),从被监视文件的wait queue中删除,删除epi->fllink,把epitem从红黑树中删除,如果已经在ep的感兴趣队列,则从感兴趣队列中删除它。
long sys_epoll_wait(int epfd, struct epoll_event __user *events, int maxevents, int timeout): 1. 通过epfd找到ep, 然后调用ep_poll()做以下所有事情: 2. 如果ep->rdllist为空,或者timeout=0,则将current加入到eq->wq中,然后调用schedule_timeout(timeout)进行进程切换。 3. 判断ep-> rdllist列表是否为空,如果还是为空,说明epoll_wait超时,直接返回。否则说明已经有文件处于ready状态,调用ep_events_transfer(),将ep->rdllist的epitem加入到用户态的event集合中。ep_events_transfer函数第4步说明。 4. ep_events_transfer()函数说明: A) 将epitem从ep的ready list从移走,放到一个临时的transfr list, B) 然后对每一个在transfer list中的epitem,判断是否有用户感兴趣的事件,有加入用户events中。 C) 然后将epitem从transfer list中移除,如果epitem被上报的事件有用户感兴趣的事件,且不是ET模式,则加epitem重新加入ep的ready list。
当某个监视文件有事件发生时,会调用wake_up_xxx (),这时,ep_poll_callback被调用,处理如下:
被监控文件事件ready时,会调用wake_up_xxx (),对于epoll接口来说, ep_poll_callback ()被调用:首先将epitem加入ep-> rdllist, 然后wakup 被阻塞的进程ep->wq。被epoll_wait()函数阻塞的进程被唤醒后,调用ep_events_transfer()将用户感兴趣的事件返回给应用程序。
Q:ET与LT模式的区别? 答:见ep_events_transfer函数说明的C)说明。
Q:epoll的ready list怎么区分in事件和out事件?虽然用户感兴趣的可能是读事件,但是对于套接字来说,加入的是sk->sk_sleep。后续无论是读事件还是写事件上报,都会唤醒调用epoll wait()的进程,从而促发检查。换句话说,用户可能只关心读事件,但socket可写事件也会唤醒调用epoll_wait()的进程,从而进行无效的检查。 改进点:sk->sk_sleep分为sk_rsleep, sk_wsleep,不同的事件hook注册到不同的wait queue中。
关闭epoll接口:ep_eventpoll_close(struct inode *inode, struct file *file); 略。
|