0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

一文详解epoll的实现原理

xCb1_yikoulinux 来源:一口Linux 作者:一口Linux 2022-08-01 13:28 次阅读

前言

本文以四个方面介绍epoll的实现原理,1.epoll的数据结构;2.协议栈如何与epoll通信;3.epoll线程安全如何加锁;4.ET与LT的实现。

epoll的数据结构

多种数据结构进行决策

epoll至少需要两个集合

  1. 所有fd的总集

  2. 就绪fd的集合

那么这个总集选用什么数据结构存储呢?我们知道,一个fd,其底层对应一个TCB。那么也就是说key=fd,val=TCB,是一个典型的kv型数据结构,对于kv型数据结构我们可以使用以下三种进行存储。

1. hash2. 红黑树3. b/b+tree

如果使用hash进行存储,其优点是查询速度很快,O(1)。但是在我们调用epoll_create()的时候,hash底层的数组创建多大合适呢?如果我们有百万的fd,那么这个数组越大越好,如果我们仅仅十几个fd需要管理,在创建数组的时候,太大的空间就很浪费。而这个fd我们又不能预先知道有多少,所以hash是不合适的。

b/b+tree是多叉树,一个结点可以存多个key,主要是用于降低层高,用于磁盘索引的,所以在我们这个内存场景下也是不适合的。

在内存索引的场景下我们一般使用红黑树来作为首选的数据结构,首先红黑树的查找速度很快,O(log(N))。其次在调用epoll_create()的时候,只需要创建一个红黑树树根即可,无需浪费额外的空间。

那么就绪集合用什么数据结构呢,首先就绪集合不是以查找为主的,就绪集合的作用是将里面的元素拷贝给用户进行处理,所以集合里的元素没有优先级,那么就可以采用线性的数据结构,使用队列来存储,先进先出,先就绪的先处理。

  1. 所有fd的总集 -----> 红黑树

  2. 就绪fd的集合 -----> 队列

红黑树和就绪队列的关系

红黑树的结点和就绪队列的结点的同一个节点,所谓的加入就绪队列,就是将结点的前后指针联系到一起。所以就绪了不是将红黑树结点delete掉然后加入队列。他们是同一个结点,不需要delete。

2f225f1a-1153-11ed-ba43-dac502259ad0.jpg

2f59eea8-1153-11ed-ba43-dac502259ad0.png

协议栈如何与epoll模块通信

epoll的工作环境

应用程序只能通过三个api接口来操作epoll。当一个io准备就绪的时候,epoll是怎么知道io准备就绪了呢?是由协议栈将数据解析出来触发回调通知epoll的。也就是说可以把epoll的工作环境看出三部分,左边应用程序的api,中间的epoll,右边是协议栈的回调(协议栈当然不能直接操作epoll,中间的vfs在此不是重点,就直接省略vfs这一层了)。

2f766b8c-1153-11ed-ba43-dac502259ad0.png

协议栈触发回调通知epoll的时机

socket有两类,一类是监听listenfd,一类是客户端clientfd。对于sockfd而言,我们一般比较关注EPOLLIN和EPOLLOUT这两个事件,所以如果是listenfd,我们通常的做法就是accept。对于clientfd来说,如果可读我们就recv,如果可写我们就send。

协议栈将数据解析出来触发回调通知epoll。epoll是怎么知道哪个io就绪了呢?我们从ip头可以解析出源ip,目的ip和协议,从tcp头可以解析出源端口和目的端口,此时五元组就凑齐了。socket fd --- < 源IP地址 , 源端口 , 目的IP地址 , 目的端口 , 协议 > 一个fd就是一个五元组,知道了fd,我们就能从红黑树中找到对应的结点。

那么这个回调函数做什么事情呢?我们传入fd和具体事件这两个参数,然后做下面两个操作

1. 通过fd找到对应的结点

2. 把结点加入到就绪队列

1、协议栈中,在三次握手完成之后,会往全连接队列中添加一个TCB结点,然后触发一个回调函数,通知到epoll里面有个EPOLLIN事件

2f815416-1153-11ed-ba43-dac502259ad0.jpg

2、客户端发送一个数据包,协议栈接收后回复ACK,之后触发一个回调函数,通知到epoll里面有个EPOLLIN事件

2fa506f4-1153-11ed-ba43-dac502259ad0.jpg

3、每个连接的TCB里面都有一个sendbuf,在对端接收到数据并返回ACK以后,sendbuf就可以将这部分确认接收的数据清空,此时sendbuf里面就有剩余空间,此时触发一个回调函数,通知到epoll里面有个EPOLLOUT事件

2fc4a93c-1153-11ed-ba43-dac502259ad0.png

4、当对端发送close,在接收到fin后回复ACK,此时会调用回调函数,通知到epoll有个EPOLLIN事件

2fd8dfb0-1153-11ed-ba43-dac502259ad0.jpg

5、当接收到rst标志位的时候,回复ack之后也会触发回调函数,通知epoll有一个EPOLLERR事件

2ff34f26-1153-11ed-ba43-dac502259ad0.png

通知的时机总结

一个有5个通知的地方

1. 三次握手完成之后2. 接收数据回复ACK之后3. 发送数据收到ACK之后4. 接收FIN回复ACK之后 5. 接收RST回复ACK之后

从回调机制看epoll 与 select/poll的区别

由于select和poll没有本质的区别,所以下面统一称为poll。

//  poll跟select类似, 其实poll就是把select三个文件描述符集合变成一个集合了。int select(int nfds, fd_set * readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);int poll(struct pollfd *fds, nfds_t nfds, int timeout);

我们看到每次调用poll,都需要把总集fds拷贝到内核态,检测完之后,再有内核态拷贝的用户态,这就是poll。而epoll不是这样,epoll只要有新的io就调用epoll_ctl()加入到红黑树里面,一旦有触发就用epoll_wait()将有事件的结点带出来,可以看到他们的第一个区别:poll总是拷贝总集,如果有100w个fd,只有两三个就绪呢?这会造成大量资源浪费;而epoll总是将需要拷贝的东西进行拷贝,没有浪费。

第二个区别:我们从上面知道了epoll的事件都是由协议栈进行回调然后加入到就绪队列的,而poll呢?内核如何检测poll的io是否就绪?只能通过遍历的方法判断,所以poll检测io通过遍历的方法也是比较慢的。

所以两者的区别:

  1. select/poll需要把总集copy到内核,而epoll不用

  2. 实现原理上面,select/poll 需要循环遍历总集是否有就绪,而epoll是那个结点就绪了就加入就绪队列里面。

注意:poll不一定就比epoll慢,在io量小的情况下,poll是比epoll快的,而在大io量下,epoll绝对是有主导地位的。至于有多少个io才算多,其实也很难说,一般认为500或者1024为分界点(我乱说的) 。

epoll线程安全如何加锁

3个api做什么事情

  1. epoll_create() ===》创建红黑树的根节点

  2. epoll_ctl() ===》add,del,mod 增加、删除、修改结点

  3. epoll_wait() ===》把就绪队列的结点copy到用户态放到events里面,跟recv函数很像

3014e2ee-1153-11ed-ba43-dac502259ad0.png

分析加锁

如果有3个线程同时操作epoll,有哪些地方需要加锁?我们用户层面一共就只有3个api可以使用:

  1. 如果同时调用 epoll_create() ,那就是创建三颗红黑树,没有涉及到资源竞争,没有关系。

  2. 如果同时调用 epoll_ctl() ,对同一颗红黑树进行,增删改,这就涉及到资源竞争需要加锁了,此时我们对整棵树进行加锁。

  3. 如果同时调用epoll_wait() ,其操作的是就绪队列,所以需要对就绪队列进行加锁。

我们要扣住epoll的工作环境,在应用程序调用 epoll_ctl() ,协议栈会不会有回调操作红黑树结点?调用epoll_wait() copy出来的时候,协议栈会不会操作操作红黑树结点加入就绪队列?综上所述:

epoll_ctl() 对红黑树加锁epoll_wait()对就绪队列加锁回调函数()   对红黑树加锁,对就绪队列加锁

那么红黑树加什么锁,就绪队列加什么锁呢?

对于红黑树这种节点比较多的时候,采用互斥锁来加锁。就绪队列就跟生产者消费者一样,结点是从协议栈回调函数来生产的,消费是epoll_wait()来消费。那么对于队列而言,用自旋锁(对于队列而言,插入删除比较简单,cpu自旋等待比让出的成本更低,所以用自旋锁)。

ET与LT如何实现

  • ET边沿触发,只触发一次

  • LT水平触发,如果没有读完就一直触发

代码如何实现ET和LT的效果呢?水平触发和边沿触发不是故意设计出来的,这是自然而然,水到渠成的功能。水平触发和边沿触发代码只需要改一点点就能实现。从协议栈检测到接收数据,就调用一次回调,这就是ET,接收到数据,调用一次回调。而LT水平触发,检测到recvbuf里面有数据就调用回调。所以ET和LT就是在使用回调的次数上面的差异。

那么具体如何实现呢?协议栈流程里面触发回调,是天然的符合ET只触发一次的。那么如果是LT,在recv之后,如果缓冲区还有数据那么加入到就绪队列。那么如果是LT,在send之后,如果缓冲区还有空间那么加入到就绪队列。那么这样就能实现LT了。

302505de-1153-11ed-ba43-dac502259ad0.png


审核编辑:汤梓红


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 存储
    +关注

    关注

    13

    文章

    4310

    浏览量

    85835
  • 数据结构
    +关注

    关注

    3

    文章

    573

    浏览量

    40127
  • epoll
    +关注

    关注

    0

    文章

    28

    浏览量

    2952

原文标题:从4个方面分析epoll的实现原理

文章出处:【微信号:yikoulinux,微信公众号:一口Linux】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    epoll的使用

    以下内容是参考华清远见《linux/unix系统编程手册》对epoll个个人总结,是我在华清远见比较全面的总结。epoll的优点同I/O多路复用和信号驱动I/O
    发表于 05-11 13:22

    揭示EPOLL些原理性的东西

    我们对这些流的操作都是有意义的。(复杂度降低到了O(1))在讨论epoll实现细节之前,先把epoll的相关操作列出:epoll_create 创建
    发表于 08-24 16:32

    poll&&epollepoll实现

    poll&&epollepoll实现
    发表于 05-14 14:34 2792次阅读
    poll&&<b class='flag-5'>epoll</b>之<b class='flag-5'>epoll</b><b class='flag-5'>实现</b>

    详解精密封装技术

    详解精密封装技术
    的头像 发表于 12-30 15:41 1664次阅读

    详解分立元件门电路

    详解分立元件门电路
    的头像 发表于 03-27 17:44 3181次阅读
    <b class='flag-5'>一</b><b class='flag-5'>文</b><b class='flag-5'>详解</b>分立元件门电路

    详解pcb和smt的区别

    详解pcb和smt的区别
    的头像 发表于 10-08 09:31 3353次阅读

    详解pcb地孔的作用

    详解pcb地孔的作用
    的头像 发表于 10-30 16:02 1654次阅读

    epoll实现多路复用

    本人用epoll实现多路复用,epoll触发模式有两种: ET(边缘模式) LT(水平模式) LT模式 是标准模式,意味着每次epoll_wait()返回后,事件处理后,如果之后还有
    的头像 发表于 11-09 10:15 511次阅读
    用<b class='flag-5'>epoll</b>来<b class='flag-5'>实现</b>多路复用

    epoll实现原理

    今儿我们就从源码入手,来帮助大家简单理解epoll实现原理,并在后边分析下,大家都说 epoll 性能好,那到底是好在哪里。
    的头像 发表于 11-09 11:14 534次阅读
    <b class='flag-5'>epoll</b> 的<b class='flag-5'>实现</b>原理

    epoll的基础数据结构

    epoll的基础数据结构 在开始研究源代码之前,我们先看epoll 中使用的数据结构,分别是 eventpoll、epitem 和 eppoll_entry。 1、event
    的头像 发表于 11-10 10:20 807次阅读
    <b class='flag-5'>epoll</b>的基础数据结构

    epoll源码分析

    个函数进行源码分析。 源码来源 由于epoll实现内嵌在内核中,直接查看内核源码的话会有些无关代码影响阅读。为此在GitHub上写的简化版TCP/IP协议栈,里面实现
    的头像 发表于 11-13 11:49 1046次阅读
    <b class='flag-5'>epoll</b>源码分析

    Epoll封装类实现

    )的事件,并将事件从内核通知到用户区,实现对特定事件的响应处理,而epoll可认为是poll的改进版,在多个方面大幅度提高了性能(当然也是在监听描述符多、活跃描述符少的条件下)。 epoll的主要特点有以下几点: 1.支持
    的头像 发表于 11-13 11:54 508次阅读

    详解pcb不良分析

    详解pcb不良分析
    的头像 发表于 11-29 17:12 1168次阅读

    详解pcb的msl等级

    详解pcb的msl等级
    的头像 发表于 12-13 16:52 9654次阅读

    详解pcb的组成和作用

    详解pcb的组成和作用
    的头像 发表于 12-18 10:48 1545次阅读