Epoll是Linux IO多路复用的一种IO管理机制。内核的实现代码在Linux内核源码的fs/eventpoll.c中。是比select和poll更高性能的一种IO管理机制。

前期准备

在实现epoll之前,要先了解内核epoll的运行原理。内核的epoll可以从四方面来理解。

  1. Epoll 的数据结构,rbtree 对<fd, event>的存储,ready 队列存储就绪 io。

  2. Epoll的线程安全,SMP的运行,以及防止死锁。

  3. Epoll内核回调

  4. Epoll的LT和ET

具体实现

Epoll数据结构

Epoll 主要由两个结构体:eventpoll 与 epitem。Epitem 是每一个 IO 所对应的的事件。比如

epoll_ctl EPOLL_CTL_ADD 操作的时候,就需要创建一个 epitem。Eventpoll 是每一个 epoll 所

对应的的。比如 epoll_create 就是创建一个 eventpoll。

  • Epitem定义

Epoll的实现原理-LMLPHP

  • Eventpoll定义

Epoll的实现原理-LMLPHP

数据结构示意图

Epoll的实现原理-LMLPHP

List用来存储就绪的IO。当内核IO准备就绪的时候就会执行epoll_event_callback的回调函数,将epitem添加到List中。当epoll_wait激活重新运行的时候,将list的epitem逐一copy到events参数中。

RBtree用来存储所有io数据,方便快速通过io_fd查找。也从insert与remove来讨论。

当 App 执行 epoll_ctl EPOLL_CTL_ADD 操作,将 epitem 添加到 rbtree
中。当 App 执行 epoll_ctl EPOLL_CTL_ADD 操作,将 epitem 从rbtree中删除。

Epoll锁机制

// 获取自旋锁	
pthread_spin_lock(&ep->lock);
// epitem的rdy置1,代表epitem在就绪队列重,后续触发相同事件只需要修改event
epi->rdy = 1;
// 添加到list中
LIST_INSERT_HEAD(&ep->rdlist, epi, rdlink);
// 将eventpoll的rdnum加1
ep->rdnum ++;
// 释放spinlock
pthread_spin_unlock(&ep->lock);

Epoll回调

Epoll 的回调函数何时执行,此部分需要与 Tcp 的协议栈一起来阐述。Tcp 协议栈的时序图如

下图所示,epoll 从协议栈回调的部分从下图的编号 1,2,3,4。具体 Tcp 协议栈的实现,后续

从另外的文章中表述出来。下面分别对四个步骤详细描述

编号 1:是 tcp 三次握手,对端反馈 ack 后,socket 进入 rcvd 状态。需要将监听 socket 的

event 置为 EPOLLIN,此时标识可以进入到 accept 读取 socket 数据。

编号 2:在 established 状态,收到数据以后,需要将 socket 的 event 置为 EPOLLIN 状态。

编号 3:在 established 状态,收到 fin 时,此时 socket 进入到 close_wait。需要 socket 的 event
置为 EPOLLIN。读取断开信息。

编号 4:检测 socket 的 send 状态,如果对端 cwnd>0 是可以,发送的数据。故需要将 socket

置为 EPOLLOUT。

所以在此四处添加 EPOLL 的回调函数,即可使得 epoll 正常接收到 io 事件。

Epoll的实现原理-LMLPHP

有点尴尬,这个图等我有空了再重新画一遍,我不知道他要vip才能无水印。

LT和ET

LT(水平触发)与 ET(边沿触发)是电子信号里面的概念。不清楚可以 man epoll 查看的。
如下图所示:

Epoll的实现原理-LMLPHP

比如:event = EPOLLIN | EPOLLLT,将 event 设置为 EPOLLIN 与水平触发。只要 event 为 EPOLLIN

时就能不断调用 epoll 回调函数。

比如: event = EPOLLIN | EPOLLET,event 如果从 EPOLLOUT 变化为 EPOLLIN 的时候,就会触

发。在此情形下,变化只发生一次,故只调用一次 epoll 回调函数。关于水平触发与边沿触

发放在 epoll 回调函数执行的时候,如果为 EPOLLET(边沿触发),与之前的 event 对比,如

果发生改变则调用 epoll 回调函数,如果为 EPOLLLT(水平触发),则查看 event 是否为 EPOLLIN,

即可调用 epoll 回调函数。

08-19 06:34