epoll_ctl方法
epoll_create方法 ,会创建eventpoll结构,然后将fd和file关联起来, 使得 通过 fd能找到 eventpoll结构,即ep结构;
struct eventpoll {
spinlock_t lock;
struct mutex mtx;
wait_queue_head_t wq; //sys_epoll_wait()使用的等待队列
wait_queue_head_t poll_wait; //file->poll()使用的等待队列
struct list_head rdllist; //所有准备就绪的文件描述符列表
struct rb_root rbr; //用于储存已监控fd的红黑树根节点
struct epitem *ovflist; //用于监听文件的结构。如果rdllist被锁定,临时事件会被连接到这里
struct wakeup_source *ws; // 当ep_scan_ready_list运行时使用wakeup_source
struct user_struct *user; //创建eventpoll描述符的用户
struct file *file;
int visited; //用于优化循环检测检查
struct list_head visited_list_link;
};
epoll_ctl方法 - 增删改
eventpoll维护着红黑树,每次添加注册事件时,都会申请一个epitem结构的变量表示事件的监听项,然后将epitem指针插入ep的红黑树里面
struct epitem {
union {
struct rb_node rbn; //RB树节点将此结构链接到eventpoll RB树
struct rcu_head rcu; //用于释放结构体epitem
};
struct list_head rdllink; //时间的就绪队列,主要就是链接到eventpoll的rdllist
struct epitem *next; //配合eventpoll中的ovflist一起使用来保持单向链的条目
struct epoll_filefd ffd; //该结构 监听的文件描述符信息,每一个socket fd都会对应一个epitem 。就是通过这个结构关联
int nwait; //附加到poll轮询中的活跃等待队列数
struct list_head pwqlist; //用于保存被监听文件的等待队列
struct eventpoll *ep; //epi所属的ep
struct list_head fllink; //主要是为了实现一个文件被多个epoll监听。将该结构链接到文件的f_ep_link。
struct wakeup_source __rcu *ws; //设置EPOLLWAKEUP时使用的wakeup_source
struct epoll_event event; //监控的事件和文件描述符
};
如果加入新的fd,调用ep_insert函数,将对目标文件的监听事件插入到ep维护的红黑树里面, 然后调用ep_item_poll,进而会调用不同类型fd的poll函数,执行ep_ptable_queue_proc() 将自己的回调函数ep_poll_callback 注册到 fd的设备等待队列的头部whead,用于就绪事件触发时,调用ep_poll_callback唤醒该进程。
sys_epoll_ctl -> ep_insert:-> ep_item_poll
epoll_wait
先对对等待时间的处理,当没有事件产生时,调用__add_wait_queue_exclusive函数将当前进程加入到ep->wq进程等待队列里面,在一个无限for循环里面,首先调用set_current_state(TASK_INTERRUPTIBLE),将当前进程设置为可中断的睡眠状态,让出cpu,进入睡眠。
1、有事件到来时,调用ep_poll_callback(),将epi->rdllink加入ep->rdllist的队列,导致rdlist不空,从而进程被唤醒,epoll_wait得以继续执行 2、回到epoll_wait(),从进程等待队列移除,再将拷贝就绪事件到用户空间
详细过程如下
sys_epoll_wait -> ep_poll -> ep_send_events -> ep_scan_ready_list:
ep_scan_ready_list首先将ep就绪链表里面的数据链接到一个全局的txlist里面,然后清空ep的就绪链表,同时还将ep的ovflist链表设置为NULL,ovflist是一个接受就绪事件的备份链表,当将事件从内核拷贝到用户空间时,这段时间目标文件可能会产生新的事件,这个时候,就需要将新的时间链入到ovlist里面。
ep_scan_ready_list -> ep_send_events_proc:
ep_send_events_proc回调函数循环获取监听项的事件数据,对每个监听项,调用ep_item_poll获取监听到的目标文件的事件,如果获取到事件,就调用__put_user函数将数据拷贝到用户空间。
回到ep_scan_ready_list函数,拷贝过程中,目标文件可能会产生新的事件链入ovlist链表里面,所以,在回调结束后,需要重新将ovlist链表里面的事件添加到rdllist就绪事件链表里面。 最后,如果rdlist不为空(表示是否有就绪事件),就调用wake_up_locked再一次唤醒内核进程处理事件的到达(流程跟前面一样,也就是将事件拷贝到用户空间)。