1.epoll特点
epoll是一种高效的多路IO服用模型,目前好多框架底层都使用的是该模型,其特点是高性能,并且能支持高并发量的io处理的特点。该模型底层使用红黑树存储监听的文件描述符,且支持多并发连接的处理能力,从而让系统更加高效稳定的运行提供服务。
2.epoll创建及其运行时内核相关的处理流程。
2.1 epoll_create方法
epoll_create调用的时候,内核中会创建一个struct eventpoll内核对象,并把当前结构关联到当前进程已打开的文件列表中。如下图各个内核结构&结构间的关联关系:
struct eventpoll: 内核路径:fs/eventpoll.c
eventpoll结构关联当前进程的文件列表:
注:上图中对应的从task_file结构的路径include/linux/sched.h,相关结构根据区嵌套追踪即可查看。
上图拆解:
当前进程调用epoll_create方法后,内核中会创建一个eventpoll结构,并将该结构关联到当前进程的文件列表中。eventpoll结构中,会创建epollwait的等待队列wq,就绪队列rdllist,并且会初始化上图中红黑树的rbr的根节点进行初始化等操作。
2.2 epoll_ctl方法
调用epoll-ctl方法目的是将当前的socket添加到eventpoll结构中的红黑树结构中,并让epoll进行管理。当有数据来时,再进行对应数据处理。
调用epoll_ctl时,系统内核都进行了那些操作呢?具体的操作有如下几个步骤:
a.内核中会创建一个epitem结构,并进行其成员相关的初始化处理.其结构如下图:
b.如果没有数据到来,将等待事件添加到socket的等待队列中,并注册一个回调函数ep_poll_callback.
c.将eptiem结构插入到eventpoll结构管理的红黑树结构中。
如下,epitem结构示意图:
调用epoll_ctl方法后内核结构关联图图:
上图流程:
当调用epoll_ctl方法后,内核会先创建一个epitem结构进行初始化,实质上将对应的进程的socket跟文件对象地址存储至epitem结构中,并将自身内部的ep指针指向所属的eventpoll结构,表示自己数据当前这个eventpoll结构。再者会创建一个socket的等待队列结构元素,(自己起名sk_item代替,后续都是用该名称),其base指针指向之前创建的epitem结构,并且注册一个回调函数ep_poll_callback,最后将sk_item元素插入到socket的等待队列中。最后将epitem结构元素插入到eventpoll结构的rbr红黑树结构中进行管理。
2.3 epoll_wait方法
epoll_wait调用该方法,内核中处理先判断eventpoll结构上的就绪描述符队列是否有就绪的事件,如果没有,那么会定义一个等待队列元素(自己起名ep_que,后都是用改名表示),将当前进程关联到ep_que,并且在注册一个回调函数default_wake_function,最后将ep_que存放到eventpoll结构的等待队列wq中。当前进程进入阻塞,让出cpu,切换到其他进程(实际情景,这块几乎不可能让出cpu,会持续的工作,因为实际线上环境数据会源源不断的传递过来的)。
2.4 数据到来
当有数据到来时,首先网卡会通过DMA方式将数据存储至ringbuff中,内核线程ksoftirq会将调用软中断处理函数将数据从ringbuff中取下来,根据内核中对应协议进行一些列处理,并且最终将数据存储到对应的socket的接收队列,之后中断处理函数sock_def_readable通过从socket的等待队列找到epoll_ctl调用时存储的关联epitem的元素,找到元素内之前注册的回到函数进行调用(epoll_poll_callback),该函数内存通过base指针找到指向的epitem元素,以及epoll对象上等待而被阻塞的进程号,并且会首先将找到的epitem元素添加到eventpoll结构中的就绪描述符队列中 ,接着判断eventpoll等待队列中是否有等待元素,如果有,找到等待元素的回调函数,通过获得的进程号将epoll_wait进程推入到运行队列,等待内核重新调度。并把eventpoll结构中就绪队列rdlist的时间返回给用户进程进行处理。