Epoll
是
Linux IO
多路复用的管理机制。作为现在
Linux
平台高性能网络
IO
必要的组件。内
核的实现可以参照:
fs/eventpoll.c .
在实现
epoll
之前,先得好好理解内核
epoll
的运行原理。内核的
epoll
可以从四方面来理解。
1. Epoll
的数据结构,
rbtree
对
<fd, event>
的存储,
ready
队列存储就绪
io
。
2. Epoll
的线程安全,
SMP
的运行,以及防止死锁。
3. Epoll
内核回调。
4. Epoll
的
LT
(水平触发)与
ET
(边沿触发)
下面从这四个方面来实现
epoll
。
一、 Epoll 数据结构
如何更好的管理大量的fd?
所有的fd有只有两种状态,就绪状态和空闲状态
就绪状态占少量,大部分的fd都处于空闲状态
那么空闲状态的fd用什么存储?
空闲状态的fd节点需要满足索引速度快的的条件
{
索引速度快:
1.hash 查找速度快,但是不利于扩展,也就是说如果需要加入一个装有fd的节点,很麻烦
2 红黑数(avl) 查找速度快,扩展性强
3 b/b+树 节点比较大,不适合存索引适合磁盘索引
4 调表 存在概率问题,并不可取
}
综上所述:空闲状态的fd节点采用红黑树存储
就绪fd节点采用队列存储比较好?
不仅是因为就绪状态的fd的数量比较少,而且是就绪队列的fd节点不需要遍历,只需要一次取出来用即可。
链式结构:队列 和 栈
为什么不采用栈而采用队列?队列是先进先出,而栈是先进后出。如果是栈存储九可能存在有fd节点不会被处理的可能。
list存储准备就绪的io,当内核准备就绪的时候,会执行epoll_event_callback的回调函数,将epitem添加到list当中,当epoll_wait激活重新运行的时候会将list的epitem注意拷贝到ecents参数中。
Rbtree
用来存储所有
io
的数据,方便快速通
io_fd
查找。也从
insert
与
remove
来讨论。
对于
rbtree
何时添加:当
App
执行
epoll_ctl EPOLL_CTL_ADD
操作,将
epitem
添加到
rbtree
中。何时删除呢?当
App
执行
epoll_ctl EPOLL_CTL_DEL
操作,将
epitem
添加到
rbtree
中。
2. Epoll 的线程安全,SMP 的运行,以及防止死锁
epoll在一些操作上是需要加锁保护。
list操作 rbtree操作 epoll_wait的等待操作。
list操作较为简单,只有简单的几行。所以采用力度较小的(spinlock)自旋锁,方便再添加操作的时候能够快速的操作list。
红黑树采用的互斥锁
三、 Epoll 回调
当讨论到epool回调则需要和协议栈一起用
1.tcp三次握手完成之后
对端反馈
ack
后,
socket
进入
rcvd
状态。需要将监听
socket
的
event
置为
EPOLLIN
,此时标识可以进入到
accept
读取
socket
数据
2 内核协议栈recvbuffer有数据的时候 收到数据以后,需要将
socket
的
event
置为
EPOLLIN
状态。
··
3 内核协议栈send有空闲的时候
检测
socket
的
send
状态,如果对端
cwnd>0
是可以,发送的数据。故需要将
socket
置为
EPOLLOUT
。
4 tcp四次挥手的收到fin数据的时候
在
established
状态,收到
fin
时,此时
socket
进入到
close_wait
。需要
socket
的
event
置为
EPOLLIN
。读取断开信息。
所以在四处添加epoll的回调函数,即可是的epoll正常接收到数据,就可以使epoll正常的接收到IO事件。
四、 LT 与 ET
LT(水平触发) ET(边沿触发)
比如:
event = EPOLLIN | EPOLLLT
,将
event
设置为
EPOLLIN
与水平触发。只要
event
为
EPOLLIN
时就能不断调用
epoll
回调函数。
比如
: event = EPOLLIN | EPOLLET
,
event
如果从
EPOLLOUT
变化为
EPOLLIN
的时候,就会触
发。在此情形下,变化只发生一次,故只调用一次
epoll
回调函数。关于水平触发与边沿触
发放在
epoll
回调函数执行的时候,如果为
EPOLLET
(边沿触发),与之前的
event
对比,如
果发生改变则调用
epoll
回调函数,如果为
EPOLLLT
(水平触发),则查看
event
是否为
EPOLLIN
,
即可调用
epoll
回调函数。
再简化一点 LT:只要内核协议栈recvbuffer 有数据就会不停的触发epoll函数。
ET是只要满足recvbuffer从无到有,九触发回调函数,而且只回调一次.