Epoll是Linux IO多路复用的管理机制。做为如今Linux平台高性能网络IO必要的组件。内核的实现能够参照:fs/eventpoll.c .git
为何须要本身实现epoll呢?如今本身打算作一个用户态的协议栈。采用单线程的模式。https://github.com/wangbojing/NtyTcp,至于为何要实现用户态协议栈?能够自行百度C10M的问题。github
因为协议栈作到了用户态故须要本身实现高性能网络IO的管理。因此epoll就本身实现一下。代码:https://github.com/wangbojing/NtyTcp/blob/master/src/nty_epoll_rb.c安全
在实现epoll以前,先得好好理解内核epoll的运行原理。内核的epoll能够从四方面来理解。网络
1. Epoll的数据结构,rbtree对<fd, event>的存储,ready队列存储就绪io。数据结构
2. Epoll的线程安全,SMP的运行,以及防止死锁。socket
3. Epoll内核回调。tcp
4. Epoll的LT(水平触发)与ET(边沿触发)ide
下面从这四个方面来实现epoll。函数
Epoll主要由两个结构体:eventpoll与epitem。Epitem是每个IO所对应的的事件。好比 epoll_ctl EPOLL_CTL_ADD操做的时候,就须要建立一个epitem。Eventpoll是每个epoll所对应的的。好比epoll_create 就是建立一个eventpoll。性能
Epitem的定义
Eventpoll的定义
数据结构以下图所示。
List 用来存储准备就绪的IO。对于数据结构主要讨论两方面:insert与remove。一样如此,对于list咱们也讨论insert与remove。什么时候将数据插入到list中呢?当内核IO准备就绪的时候,则会执行epoll_event_callback的回调函数,将epitem添加到list中。
那什么时候删除list中的数据呢?当epoll_wait激活从新运行的时候,将list的epitem逐一copy到events参数中。
Rbtree用来存储全部io的数据,方便快速通io_fd查找。也从insert与remove来讨论。
对于rbtree什么时候添加:当App执行epoll_ctl EPOLL_CTL_ADD操做,将epitem添加到rbtree中。什么时候删除呢?当App执行epoll_ctl EPOLL_CTL_DEL操做,将epitem添加到rbtree中。
List与rbtree的操做又如何作到线程安全,SMP,防止死锁呢?
Epoll 从如下几个方面是须要加锁保护的。List的操做,rbtree的操做,epoll_wait的等待。
List使用最小粒度的锁spinlock,便于在SMP下添加操做的时候,可以快速操做list。
List添加
346行:获取spinlock。
347行:epitem 的rdy置为1,表明epitem已经在就绪队列中,后续再触发相同事件就只需更改event。
348行:添加到list中。
349行:将eventpoll的rdnum域 加1。
350行:释放spinlock
List删除
301行:获取spinlock
304行:判读rdnum与maxevents的大小,避免event溢出。
307行:循环遍历list,判断添加list不能为空
309行:获取list首个结点
310行:移除list首个结点。
311行:将epitem的rdy域置为0,标识epitem再也不就绪队列中。
313行:copy epitem的event到用户空间的events。
316行:copy数量加1
317行:eventpoll中rdnum减一。
避免SMP体系下,多核竞争。此处采用自旋锁,不适合采用睡眠锁。
Rbtree的添加
149行:获取互斥锁。
153行:查找sockid的epitem是否存在。存在则不能添加,不存在则能够添加。
160行:分配epitem。
167行:sockid赋值
168行:将设置的event添加到epitem的event域。
170行:将epitem添加到rbrtree中。
173行:释放互斥锁。
Rbtree删除:
177行:获取互斥锁。
181行:删除sockid的结点,若是不存在,则rbtree返回-1。
188行:释放epitem
190行:释放互斥锁。
Epoll_wait的挂起。
采用pthread_cond_wait,具体实现能够参照。
https://github.com/wangbojing/NtyTcp/blob/master/src/nty_epoll_rb.c
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事件。
LT(水平触发)与ET(边沿触发)是电子信号里面的概念。不清楚能够man epoll查看的。以下图所示:
好比:event = EPOLLIN | EPOLLLT,将event设置为EPOLLIN与水平触发。只要event为EPOLLIN时就能不断调用epoll回调函数。
好比: event = EPOLLIN | EPOLLET,event若是从EPOLLOUT变化为EPOLLIN的时候,就会触发。在此情形下,变化只发生一次,故只调用一次epoll回调函数。关于水平触发与边沿触发放在epoll回调函数执行的时候,若是为EPOLLET(边沿触发),与以前的event对比,若是发生改变则调用epoll回调函数,若是为EPOLLLT(水平触发),则查看event是否为EPOLLIN,便可调用epoll回调函数。
BAT, 滴滴,今日头条,美图,美团等一线内推 技术岗位内推
QQ群:935760465