一、内核从网卡接收网络数据的处理过程:

计算机由CPU、内存、网卡等设备硬件设备组成。

计算机接收网络数据的处理过程是:

  1. 网卡收到网线传来的数据,经过DMA传输、IO通路选择等处理后,将收到的数据写入内存;
  2. 网卡将接收到的网络数据写入内存后,网卡向CPU发出一个中断信号,CPU能够捕获这个信号,然后执行相应的中断处理程序(对应IRQ请求的处理程序);
    此时的中断程序主要有两项功能:
    ① 先将网络数据写入到对应socket的接收缓冲区中;
    ② 唤醒此socket阻塞队列上的进程,将其重新放入到工作队列中;

1.1 硬中断、软中断、信号的区别:

硬中断:
外部设备(磁盘、网卡、键盘等)对CPU的中断;

软中断:
中断服务程序对内核的中断;
软中断是Linux中断机制的“下半部分”(bottom-half),在软件上模拟硬件中断,达到异步调用下半部分服务函数的功能。

信号:
内核(或其他进程)对某个进程的中断。
软件上模拟硬件中断,中断某个用户进程的运行,转而去处理相应的“中断”。这一系列动作包含:
信号产生—>信号响应—>信号处理—>信号返回。

1.2 socket对象上的等待队列:

当进程创建一个socket套接字时,操作系统会相应的创建出一个由文件系统管理的socket对象,这个socket对象中包含:

发送缓冲区接收缓冲区等待队列、 等成员。

其中,等待队列是非常重要的成员。

操作系统会将调动recv阻塞的进程挂在对应socket的等待队列中(只是进程的引用,不是进程本身),此时进程进入阻塞态,不会占用CPU资源。

当socket上收到数据后,操作系统会将该socket从等待队列中重新放回到工作队列,继续执行。

1.3 操作系统如何知道网卡上收到的数据属于哪个socket:

每个socket对应的是一个网络连接(TCP或UDP),即一个socket对应一组IP地址和端口号,而网络数据中包含了IP地址和端口号信息,操作系统会维护端口号到socket的索引结构,根据收到的网络数据中的端口号信息就可以快速找到对应的socket套接字。

二、如何同时监视多个socket的数据:

2.1 单进程单socket的弊端:

  服务端需要管理多个客户端连接,而 recv() 系统调用只能监视单个socket。
这种情况下,如果要管理多个客户端连接,就需要多开进程或线程,每个进程维护一个socket套接字,没有网络数据时进程阻塞在recv()系统调用上,当网络数据到达时,操作系统环境对应socket等待队列上的进程。
此时面临的问题是维护进程或线程带来的系统开销(每个线程的栈空间8M,由于系统的内存资源有限,1K个线程就已经需要消耗8G内存,不可能无限制的多开线程,且进程、线程间的频繁切换也会带来较大的开销)。

2.2 select的设计思想:

  这种矛盾下,人们开始寻找监视多个socket的方法。
最先想到的办法是使用一个进程监视多个socket,预先传入一个socket列表,如果列表中的socket都没有数据,则进程继续挂起;直到有一个或以上的socket接收到网络数据,再唤醒进程。
这种方法很直接,这也是select的设计思想。

2.2.1 select的执行流程是:

如下图,假设进程A同时监听 sock1、sock2、sock3(通过fd_set传入),那么,在调用select之后,操作系统会把进程A分别加入到这三个socket的等待队列中:

当任何一个socket上收到数据时,中断程序将唤起进程。
所谓唤起进程,就是将其从所有的socket对象的等待队列中移除,并插入到就绪队列中。

经过这两步之后,当进程A被唤醒时,它知道它所检测的socket列表中至少有个socket已经接收到数据了。此时程序遍历一遍socket列表,就可以得到就绪的socket。

2.2.2 select的缺点是:

  1. 每次调用select都需要将进程加入到所有socket对象的等待队列中,每次唤醒进程又要将进程从所有socket对象的等待队列中移除。这里涉及到对socket列表的两次遍历,而且每次都要将整个fds列表传递给内核,有一定的开销。正因为遍历操作开销大,出于效率的考量,才会规定select的最大监视数量,默认只能监视1024个socket(强行修改也是可以的);
  2. 进程被唤醒后,程序并不知道socket列表中的那些socket上收到数据,因此在用户空间内需要对socket列表再做一次遍历。

2.3 epoll的设计思想:

2.3.1 epoll的实现原理和流程:

1. 创建epoll对象:

当进程调用 epoll_create 方法时,内核会创建一个 eventpoll 对象,也就是应用程序中的 epfd 所代表的对象。

eventpoll 对象也是文件系统中的一员(Linux中一切设备皆文件),和socket一样也拥有一个“等待队列”。

内核创建eventpoll对象:

2. 维护监视列表:

创建epoll对象 eventpoll 之后,可以使用 epoll_ctl 添加或者删除所要监听的socket。

以添加socket为例,如果要对sock1、sock2、sock3进行监视,内核会将eventpoll添加到这三个socket的等待队列中。

当socket收到数据后,中断回调程序会操作eventpoll对象,而不是直接操作进程。

3. 接收数据:

select的低效原因之一在于应用程序不知道哪些socket收到数据,只能一个个的遍历。如果内核维护一个“就绪列表”,在就绪列表中引用收到数据的socket,就能避免遍历。

eventpoll 对象中就实现了这样的一个“就绪列表” ---- rdlist

当socket收到数据,中断回调程序会给eventpoll的“就绪列表”添加socket的引用,如下图所示:

eventpoll对象相当于是socket和进程之间的中介,socket的数据接收并不直接影响进程,而是通过改变eventpoll的就绪列表来改变进程状态。

epoll_wait的返回条件也是根据rdlist的状态进行判断:
如果rdlist已经引用了socket,那么epoll_wait直接返回;
如果rdlist为空,阻塞进程。

4. 阻塞和唤醒进程:

如下图,假设当进程A运行到epoll_wait()时,操作系统会将进程A放入到eventpoll对象的等待队列中,阻塞进程。

(对于epoll,操作系统只需要将进程A放入eventpoll这一个对象的等待队列中;而对于select,操作系统则需要将进程A放入到socket列表中的所有socket对象的等待队列中。)

当socket接收到数据时,中断回调程序一方面修改rdlist“就绪列表”,另一方面唤醒eventpoll等待队列中的进程A。
也因为rdlist就绪列表的存在,进程A可以在重新进入运行态后准确知道哪些socket上发生了变化。

2.3.2 epoll高效的原因:

相较于select,epoll实现高效主要基于以下两点:

  1. 功能分离;
  2. 就绪列表。

select低效的原因之一是将“维护等待队列”和“阻塞进程”两个步骤合二为一。
每次调用select都需要这两步操作,然而大多数应用场景中,需要监视的socket相对固定,并不需要每次修改。

epoll将这两个操作分开,先用 epoll_ctl 维护等待队列,再调用 epoll_wait 阻塞进程,以此来提高效率。

select低效的另一个原因在于程序不知道哪些socket收到数据,只能一个一个的遍历。如果内核维护一个“就绪列表”,引用收到的数据的socket,就能避免遍历。

2.3.3 epoll的实现细节:

1. eventpoll对象的数据结构:

eventpoll对象包含了:lock、mtx、wq(等待队列)、rdlist 等成员。

2. rdlist就绪队列的数据结构:

epoll使用双向链表来实现就绪队列rdlist。

3. 索引结构:

epoll使用红黑树作为索引结构,以便于快速的插入和删除要监视的socket套接字。
红黑树时一种自平衡的二叉查找树,搜索、插入、删除的时间复杂度都是O(logN)。

参考内容:
添加链接描述

epoll的底层实现原理相关推荐

  1. 深入浅出学习透析Nginx服务器的架构分析及原理分析「底层技术原理+运作架构机制」

    Nginx再次回顾 也许你已经忘记了Nginx是做什么的?我来再次给你夯实一下概念. 多协议反向代理 Nginx是个高性能的Web和反向代理服务器及HTTP服务器,它能反向代理HTTP,HTTPS和邮 ...

  2. 使用多线程还是用IO复用select/epoll? epoll 或者 kqueue 的原理是什么?

    原作者:蓝形参 原文:http://www.zhihu.com/question/20114168/answer/14024115 使用多线程还是用IO复用select/epoll? 多线程模型适用于 ...

  3. Java并发机制的底层实现原理

    Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和CPU的指令.本章我们将 ...

  4. HashMap底层实现原理,红黑树,B+树,B树的结构原理,volatile关键字,CAS(比较与交换)实现原理

    HashMap底层实现原理,红黑树,B+树,B树的结构原理,volatile关键字,CAS(比较与交换)实现原理 首先HashMap是Map的一个实现类,而Map存储形式是键值对(key,value) ...

  5. Spring AOP概述及底层实现原理

    Spring AOP概述及底层实现原理 aop概述 AOP全称为Aspect Oriented Programming的缩写,意为:面向切面编程.将程序中公用代码进行抽离,通过动态代理实现程序功能的统 ...

  6. java底层原理书籍_阿里面试题:Java中this和super关键字的底层实现原理

    知道的越多,不知道的就越多,业余的像一棵小草! 编辑:业余草 来源:https://www.xttblog.com/?p=5028 B 站:业余草 最近一个粉丝加我说,接到了阿里的面试,问问我阿里会面 ...

  7. Spring(二)IOC底层实现原理

    IOC原理 将对象创建交给Spring去管理. 实现IOC的两种方式 IOC配置文件的方式 IOC注解的方式 IOC底层实现原理 底层实现使用的技术 1.1 xml配置文件 1.2 dom4j解析xm ...

  8. 《Java并发编程的艺术》一一第2章Java并发机制的底层实现原理

    第2章Java并发机制的底层实现原理 2.1 volatile的应用 Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上执行, ...

  9. Java多线程之线程池7大参数、底层工作原理、拒绝策略详解

    Java多线程之线程池7大参数详解 目录 企业面试题 线程池7大参数源码 线程池7大参数详解 底层工作原理详解 线程池的4种拒绝策略理论简介 面试的坑:线程池实际中使用哪一个? 1. 企业面试题 蚂蚁 ...

最新文章

  1. 设计模式:访问者模式(Visitor Pattern)
  2. 哈夫曼编码译码 C语言,【求助】严蔚敏版数据结构 哈夫曼编码译码
  3. perl(Class::MethodMaker) is needed by MySQL-ndb-tools-5.1.21-0.glibc23.i386
  4. mysql迁移数据目录,这个坑你遇到过吗?
  5. 第十三章 第六节 本章小结
  6. python random库 伪随机 随机抽数字 抽学号
  7. 利用python炒股talib_Python 通过 TALib 包构建股票自动技术分析
  8. 基于 Retinex 的几种图像增强算法总结
  9. 无线局域网和蜂窝移动网络_苹果调整 iPhone 移动数据下载限制:从 150 MB 升至 200 MB...
  10. YOLOV5目标检测-后处理NMS(非极大值抑制)
  11. 虚幻4 - ARPG实战教程(第一季)
  12. dnf丢失clientbase_登录dnf时出现dnfbase.dll的丢失
  13. vue-Vant组件上传图片
  14. 服务器游戏性能测试工具,python 游戏服务器 性能测试工具
  15. java智能点餐系统研究内容_JAVA课程实践报告 基于web的点餐系统毕业设计
  16. js 中~~是什么意思?
  17. 【文献翻译】Select-Storage: A New Oracle Design Pattern on Blockchain
  18. mysql联合索引如何创建
  19. 英伟达服务器显卡多实例技术(MIG)
  20. springboot整合netty实现tcp通信

热门文章

  1. 【Halcon二维测量】——2D计量模型
  2. Vue学习之路(基础篇)
  3. Dahua IPC 授权问题漏洞(CVE-2021-33045)
  4. 科普 | ETH2 Staking 指南 :客户端多样性为何如此重要
  5. 中银泰适合年轻人的理财计划
  6. php如何添加音乐播放器,怎么为Wordpress博客添加MP3播放器
  7. 2021 春运购票日程 抢购建议
  8. PHP如何获取世界各地的时间
  9. Metasploit工具
  10. oracle 12c undo,Oracle 12c 新特性之临时Undo--temp_undo_enabled