Skip to main content
标签ad报错:该广告ID(9)不存在。
  主页 > Linux

关于epoll的四件“小事”

2023-06-11 浏览:
标签ad报错:该广告ID(7)不存在。

#

  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都有的情况下才有的。

img

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