epoll源码解析

191 阅读3分钟

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一次唤醒内核进程处理事件的到达(流程跟前面一样,也就是将事件拷贝到用户空间)。