#
epoll作为服务端架构的基础,它的概念相信已经不必再过多赘述。在平时工作和面试中,epoll一直是一个重点,所以关于它的几件“小事”,你不得不知道。 epoll其底层数据结构是基于红黑树的,重要的函数接口有3个:
(1)epoll_create:创建一个红黑树节点
(2)epoll_ctl:
注册事件:EPOLL_CTL_ADD(往红黑树内添加一个节点)
修改事件:EPOLL_CTL_MOD(修改红黑树内一个节点)
删除事件:EPOLL_CTL_DEL(从红黑树内删除一个节点)
(3)epoll_wait:把就绪队列里面节点拷贝的用户空间,即:
epoll_wait(fd, events, 50, -1); //拷贝50个就绪节点到用户空间的events中,-1表示超时时间。
了解了epoll的基本接口函数后,我们来看下关于epoll必须知道的四件事情。
1.epoll数据结构组成是?
1、应用层3个api:epoll_create、epoll_ctl、epoll_wait,内核源码在fs/eventpoll.c。
2、内核里有哪些数据结构?
(1)所有fd的集合:红黑树。总集是所有交给epoll管理的fd。
思考:集合如何用key-value(key就是fd,value就是fd对应的事件)存储? hash、红黑树、btree/b+tree。 hash:缺点:初始化创建时,内存消耗大;优点:fd数量足够多时查找效率高; btree/b+tree:用在磁盘,因为btree/b+tree层高低,便于磁盘索引,所以效率高; 红黑树:最优。查找效率、空间利用高于hash,查找效率也高于btree/b+tree。
(2)准备就绪可读可写的集合:队列。总集中有一个就绪了就放入就绪队列。
思考:如何存储就绪集合不是以查找为主,不用key/value,不分优先级,所有的都要处理,推荐队列、栈。这里选队列,因为栈是先进后出,会导致先进入栈的就绪fd因为后出,一直得不到epoll_wait的处理,而队列是先进先出的,不存在这问题。
3、epoll工作环境?
epoll工作在应用程序和内核协议栈之间。 epoll是在内核协议栈和vfs都有的情况下才有的。
4、epoll和poll/select区别?
(1)使用接口:select/poll需要把fds总集拷贝到内核协议栈中,epoll不需要。
(2)实现原理:select/poll在内核内循环 遍历是否有就绪io,epoll是单个加入红黑树。
解释:poll/select每次都要把fds总集拷贝到内核协议栈内,内核采取轮询/遍历,返回就绪的fds集合。(大白话:poll/select的fds是存放在用户态协议栈,调用时拷贝到内核协议栈中并轮询,轮询完成后再拷贝到用户态协议栈)。而epoll是通过epoll_ctl每次有新的io就加入到红黑树里,有触发的时候用epoll_wait带出即可,不需要拷贝总集。
2.协议栈如何与epoll模块通信?
协议栈和epoll模块之间的通信是异步的,没有耦合,不需要等待。
通知时机:
(1)协议栈三次握手完成,往accept全连接队列里加入这个节点时,通知epoll有事件来了epollin;
(2)客户端发了1个数据到协议栈,协议栈此时要返回ack给客户端的这里的时机,会通知epoll有事件可读 epollin。
3.epoll如何加锁?
1、对红黑树枷锁:一种是锁整棵树,另一种是锁子树。一般使用互斥锁。
2、对就绪队列枷锁:用自旋锁,队列操作比较简单,等到一些时间比让出线程更高效点。
4.ET LT如何实现?
ET边沿触发,不管服务端有没有读完,只触发一次;LT水平触发,如果没有读完会一直触发。
假如:客户端发送4K数据到服务端,服务端调用recv接收了1K,如果是ET,剩下的3K就不会触发了,如果4K后面有另一个4K来了 同样只触发一次 接收1K。如果是LT,会一直触发。 为什么要有ET LT? (1)跟TCP三次握手一样,是自然而然的产生的,不是故意设计的; (2)回调函数的关系:ET是接收到数据调一次回调、LT是检测到recvbuffer内一有数据就调一次回调。回调的目的:就是加入到就绪队列里查找fd。 ET LT总结起来就是回调次数的问题。
例子:
TCP发送1M数据的传输过程(send buffer=1024,MSS最大传输片512,MTU=1500): 先调用send函数拷贝1024字节到内核协议栈tcb上的send buffer,因为MSS=512,所以再分两个包发送到对端。 发送的过程: while(1){ epoll(fd); //检测fd是否可写 send(); } send如果发送1024后,send buffer满了,不再可写会返回-1。如果send buffer内只有50字节空间剩余,send要发送512,其实只能拷贝50到send buffer,内核协议栈返回拷贝成功的50。 第一个包的seq number,再对端内核协议栈回复ack是要加上第一个包长,比如seq number=1356,对端内核协议栈发送的ack=1356+512。本端再发第二个包的seq number=1356+512+1。
————————————————
版权声明:本文为CSDN博主「当当响」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zhpCSDN921011/article/details/124522340