关于epoll的四件“小事”

目录

一、epoll数据结构组成是?

二、协议栈如何与epoll模块通信?

三、epoll如何加锁?

四、ET LT如何实现?


        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必须知道的四件事情。

一、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带出即可,不需要拷贝总集。

二、协议栈如何与epoll模块通信?

        协议栈和epoll模块之间的通信是异步的,没有耦合,不需要等待。

通知时机:

(1)协议栈三次握手完成,往accept全连接队列里加入这个节点时,通知epoll有事件来了epollin;

(2)客户端发了1个数据到协议栈,协议栈此时要返回ack给客户端的这里的时机,会通知epoll有事件可读 epollin。

三、epoll如何加锁?

1、对红黑树枷锁:一种是锁整棵树,另一种是锁子树。一般使用互斥锁。

2、对就绪队列枷锁:用自旋锁,队列操作比较简单,等到一些时间比让出线程更高效点。

四、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。        

参考链接:

推荐一个零声学院免费公开课,个人觉得老师讲得不错,分享给大家:

C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值