Linux 底层原理 —— epoll 与多路复用

引言

epoll 是 Linux 系统下高性能网络服务的必备技术,很多面试中高频出现的 Nginx、Redis 都使用了这一技术,本文总结 linux 多路复用模型的演变过程,看一看epoll 是如何实现高性能的。

一、相关基础知识

1.1 文件描述符 

文件描述符:file descriptor,是Linux 内核为了高效管理已被打开的文件所创建的索引,形式上是一个非负整数,用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符。

最常见的文件描述符是 0 (标准输入)、1 (标准输出)、2(标准错误),它们被维护在进程的 fd 目录下:

1.2 多路复用

在数据通信系统或计算机网络系统中,为了有效的利用通信线路,希望一个信道同时传输多路信号,这就是多路复用技术。

采用多路复用技术能把多个信号组合起来在一条物理信道上进行传输,

二、多路复用模型演化过程

早期的 Socket 通信是 BIO,即阻塞 IO。

应用进程通过系统内核的 read()系统调用来访问 Socket fd,由于是阻塞的,应用程序的线程是阻塞的,必须收到 fd 返回的数据响应后,才能够继续向下执行。那么,对于一个网络程序,多个TCP请求过来时,应用进程必须分配对等数量的线程才能够完成处理,因此,这会造成极大的资源浪费,例如CPU的效率,线程变多,忙于线程调度,而且线程的内存占用也会增多。

为了解决一系列资源浪费和效率问题,内核改进了IO方式,变为了NIO。

NIO是非阻塞式IO通信,即便没有数据到达,系统调用也会立刻返回,告知应用进程没有数据。此时,应用程序就可以使用一个线程处理多个 socket fd,但这需要应用程序轮询访问 fd 集合,每次都需要调用 read 并反复切换用户态、内核态。这个时期的内核通过 NIO 的方式,用户程序使用一个线程来操作,是同步处理的,因此可以称为“同步非阻塞模型”。

随着内核的发展,1983年加入了新的系统调用——select,它也是“多路复用”技术实现的第一个版本。使用 man 2 select 命令查看相关信息:

当应用程序需要监视多个 fd 时,会把一个 fd 集合传给 select 接口(最多1024个fd),这需要将fd集合整个由用户态拷贝到内核态,这个开销在 fd 很多时会很大。

select 实际上是将用户轮询的操作移入了内核空间,内核依然需要循环每个 fd 进行判断是否有数据到达。而 poll 的原理和 select 一样,不过它支持的fd 数量要大于 1024。

于是,在2002年,epoll诞生,它是基于 select、poll 之上进行改进的线程安全的多路复用技术。

epoll内部使用了 mmap 共享了用户和内核的部分空间,避免数据的来回拷贝。

基于事件驱动,epoll_ctl 注册事件和callback回调函数,epoll_wait 只返回发生的事件(一般是读或写事件),避免像 select 和 poll 对事件的整体轮询。

三、epoll 原理的深入理解

在一个典型的计算机结构图中,网卡收到网线传来的网络数据,经过硬件电路的传输,最终将数据写入到内存中的某个地址上。

通过硬件传输,网卡接收的数据存放到内存中,操作系统就可以去读取它们。但是,忙碌的CPU并不会一直盯着网卡,这需要网卡在接收到数据之后,向CPU发送一个中断信号(80中断),告诉CPU有网络数据到达。

计算机执行程序时,会有优先级的需求,比如,当计算机收到断电信号时(电容可以保存少许电量,供cpu运行很短的一小段时间)它应立即去保存数据,保存数据的程序具有较高优先级。

一般而言,由硬件产生的信号需要CPU立马作出回应(不然数据可能会丢失),所以它的优先级很高。网卡向CPU发出一个中断信号,操作系统便能得知有新数据到来,再通过网卡中断程序去处理数据。

理解 epoll还需要理解另一个非常重要的概念——进程阻塞。它的含义很简单,就是进程停止执行,等待某个唤醒信号使其恢复运行状态。但是阻塞的进程并不会占用CPU资源,这是为什么?

这就需要理解阻塞的本质——进程调度

操作系统为了支持多任务,实现了进程调度的功能,会把进程分为“运行”和“等待”等几种状态。操作系统会分时执行各个运行状态的进程,由于速度很快,看上去就像是同时执行多个任务。

下图中计算机运行A、B、C三个进程,其中进程A执行着上述基础网络程序,一开始,这三个进程被操作系统的工作队列所引用,处于运行状态,会分时执行。

当进程A执行到创建socket语句时,操作系统会创建一个由文件系统管理的 socket 对象。这个socket 对象包含了发送缓冲区、接收缓冲区、等待队列等成员。而等待队列指向所有需要等待该socket事件的进程

当程序执行到recv时,操作系统会将进程A从工作队列移入该 socket 的等待队列中,CPU就只会执行另外两个B、C进程,而不会执行A进程。

所以,进程A被阻塞,不会往下执行代码,也不会占用CPU资源

当socket接收到数据后,操作系统将该socket等待队列上的进程重新放回工作队列,该进程就会再次变成运行状态,继续执行代码。也由于socket的接收缓冲区已经有数据,recv可以返回接收到的数据。

那么内核接收网络数据的全过程如下图(其中,中断程序主要两项功能:先将网络数据写入对应socket的接收缓冲区,再唤醒进程A,即重新将A放入工作队列):

服务端需要管理多个客户端连接,而 recv 只能监视单个socket,这种矛盾下,人们开始寻找监视多个socket 的方法。epoll 的要义是高效的监视多个 socket 。

假如能够预先传入一个 socket 列表,如果列表中的 socket 都没有数据,挂起进程(阻塞),直到有一个socket 收到数据,唤醒进程。这种方法很直接,也是 select 的设计思想。操作系统把进程A分别加入多个socket的等待队列中,如下图:

epoll 相对于 select ,主要有以下几点改进措施

措施一:功能分离

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

int s = socket(AF_INET, SOCK_STREAM, 0);   
bind(s, ...)
listen(s, ...)int epfd = epoll_create(...);
epoll_ctl(epfd, ...); //将所有需要监听的socket添加到epfd中while(1){int n = epoll_wait(...)for(接收到数据的socket){//处理}
}

措施二:就绪队列

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

epoll 系列总共有三个方法,贯穿数据接收和进程调度的始终:

1、epoll_create 会创建一个 eventpoll 对象,它也是文件系统中的一员,和socket 一样,它也会有等待队列。创建一个代表该 epoll 的 eventpoll 对象是必须的,因为内核需要维护“就绪列表” 等数据,可以作为 eventpoll 的成员。

2、epoll_ctl ,创建 epoll 对象之后,可以用 epoll_ctl 添加和删除所要监听的 socket。当socket 收到数据后,中断程序会操作 eventpoll 对象,而不是直接操作进程。

当socket 收到数据后,中断程序会给 eventpoll 的“就绪队列” 添加 socket 引用:

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

3、epoll_wait,当程序执行到 epoll_wait 时,如果 rdlist 已经引用了socket,那么 epoll_wait 直接返回,如果 rdlist 为空,内核会将进程A放入 eventpoll 的等待队列,阻塞进程。

当socket接收到数据,中断程序一方面修改 rdlist,另一方面唤醒 eventpoll 等待队列中的进程,进程A再次进入运行状态。也因为 rdlist 的存在,进程A可以知道哪些 socket 发生了变化。

四、epoll的实现思考

了解了 epoll的工作原理,我们再来思考一下 epoll的内部存储结构。

前面已经提到就绪列表,它引用着就绪的 socket ,所以应该具备快速插入的特性,因为程序可能随时调用 epoll_ctl 添加监听 socket,也可能随时删除。

所以,就绪列表应该是一种能够快速插入和删除的数据结构。双向链表就是这样一种数据结构,epoll 使用它来实现就绪队列。

而维护监视的 socket ,采用的是红黑树,因为它的搜索、插入、删除时间复杂度都是O(logN),效率较好。

总结

参考

《如果这篇文章说不清epoll的本质,那就过来掐死我吧!》

《阿里面试题 | Nginx 所使用的 epoll 模型是什么?》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/558675.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

异或运算的应用

一、基础知识 异或运算,相异为1。 异或运算是一种常用的位运算,在算法题中,对于避免额外的空间复杂度有独特的用处。 异或运算也被称为“无进位相加”,它具有以下特性: 特性1:0 ^ N N 特性2&#xff1a…

单向队列、双端队列、栈的模型实现

引言 自己实现简单的队列、栈的逻辑结构。 队列都包含头和尾两个指针,简单的单向队列只能在一端(如:head端)入列,在另一端(如:tail 端)出列;双端队列可以在 head 进出&…

递归算法及其时间复杂度分析

引言 “递归” 一词是比较专业的计算机术语,在现实生活中,有一个更可爱的词——“套娃”。如果把“递归算法”叫做“套娃算法”,或许可以减少一些恐惧程度。 套娃是有限的,同样,递归也是有限的,这和我们经…

算法设计中的基础常用代码

引言 本篇博客旨在记录一些基础算法知识的常见组合用法,以及何时使用,需要注意的问题等,长期更新。 为什么要这样总结呢?难道掌握了位运算、常用算法工具API的定义还不够吗? 这是因为某些知识比如 &、 |、 ~、 …

Redis —— 常用命令一览

引言 参考《菜鸟教程 Redis 常用命令》,其中红色为极其重要,蓝色为重要。 一、总览 二、key相关命令 三、String 相关命令 四、Hash 相关命令 五、List 相关命令 六、Set 相关命令 七、ZSet 相关命令

Redis 实用技术——消息发布和订阅

引言 发布订阅模型是redis的重要功能,它可以像网站动态一样,将消息发送到多个订阅者的主页里。 一、常用命令 二、消息格式 消息是一个有三个元素的多块响应: 如上图,发布者向 mysub 频道发送了一条消息,redis会返回…

Redis 实用技术——事务

引言 redis的事务不像关系型数据库的事务那样完整。 “快”是redis的特征,在事务管理的过程中,使用muti命令开启事务块,当输入多条命令后,再使用exec命令执行事务块中的全部命令。 Redis事务可以保证两件事: 1、隔…

排序算法——归并排序的相关问题

一、小和问题 问题描述,给定一个数组,如[1, 3, 2, 6, 5],计算每个数左边小于自己的所有数的和,并累加。例如: 1左边没有数 3左边有一个小于自己的数 1 2左边有一个小于自己的数 1 6左边有三个小于自己的数 1 3 2 6…

经典数据结构——堆的实现

一、完全二叉树 堆是一种完全二叉树,什么是完全二叉树? 简单的说,一棵满二叉树表示的是所有节点全部饱和,最后一层全部占满: 而完全二叉树指的是满二叉树的最后一层,所有叶子节点都从左往顺序排满&#x…

排序算法 —— 堆排序

引言 此文基于《经典数据结构——堆的实现》中堆结构,实现一个以堆处理排序的算法。 一、算法思想 基于堆结构的堆排序的算法思想非常简单,循环获取大根堆中的最大值(0位置的根节点)放到堆的末尾,直到将堆拿空。 由…

经典数据结构——前缀树

引言 前缀树——trie /ˈtraɪ//树,也叫作“单词查找树”、“字典树”。 它属于多叉树结构,典型应用场景是统计、保存大量的字符串,经常被搜索引擎系统用于文本词频统计。它的优点是利用字符串的公共前缀来减少查找时间,最大限度…

排序算法 —— 计数排序

引言 计数排序是桶排序思想的一种具体实现,针对一些具有特殊限制的样本数据,如公司员工年龄,那么样本数据本身就一定在0~200之间,针对这样的数据,使用从0到200 的桶数组,桶的位置已经是有序的,…

Java多线程 —— 线程状态迁移

引言 线程状态迁移,又常被称作线程的生命周期,指的是线程从创建到终结需要经历哪些状态,什么情况下会出现哪些状态。 线程的状态直接关系着并发编程的各种问题,本文就线程的状态迁移做一初步探讨,并总结在何种情况下…

Java中的Unsafe

Java和C语言的一个重要区别就是Java中我们无法直接操作一块内存区域,不能像C中那样可以自己申请内存和释放内存。Java中的Unsafe类为我们提供了类似C手动管理内存的能力。 Unsafe类,全限定名是sun.misc.Unsafe,从名字中我们可以看出来这个类对…

arm中断保护和恢复_浅谈ARM处理器的七种异常处理

昨天的文章,我们谈了ARM处理器的七种运行模式,分别是:用户模式User(usr),系统模式System(sys),快速中断模式(fiq),管理模式Supervisor(svc),外部中断模式(irq),数据访问中止模式Abor…

Queue —— JUC 的豪华队列组件

目录引言一、Queue 的继承关系1.1 Queue 定义基础操作1.2 AbstractQueue 为子类减负1.3 BlockingQueue 阻塞式Queue1.4 Deque 两头进出二、Queue 的重要实现三、BlockingQueue 的实现原理四、Queue 在生产者消费者模式中的应用五、Queue 在线程池中的应用六、ConcurrentLinkedQ…

daad转换器实验数据_箔芯片电阻在高温应用A/D转换器中的应用

工业/应用领域高温:地震数据采集系统、石油勘探监测、高精度检测仪产品采用:V5X5 Bulk Metal (R) Foil芯片电阻案例介绍TX424是一个完整的4通道24位模数转换器,采用40脚封装。该设计采用最先进设计方案,两个双通道24位调节器和一个…

excel分段排序_学会这个神操作,报表填报不再五花八门,效率远超Excel

在报表工作人员的的日常工作中,常常要面临统计混乱的终端用户输入的问题。由于无法准确限制用户的输入内容,所以在最终进行数据统计时,常常会出现数据不合法的情况。为此需要花费大量的人力和时间核对校验数据。举个简单的例子,某…

IDEA——必备插件指南

目录一、Free-Mybatis-Plugin二、Lombok三、jclasslib Bytecode Viewer一、Free-Mybatis-Plugin 二、Lombok 三、jclasslib Bytecode Viewer 学习 class 文件的必备插件。 使用简单,安装后可以在菜单 View 中看到 show bytecode with jclasslib: 效果…

jitter 如何优化网络_如何做好关键词优化网络?

越来越多的传统企业开始建立自己的网站,进而不断的推广自己的产品。为了能够让自己的企业网站出现在搜索引擎的首页,现在最常用的手段就是竞价排名和关键词优化网络。往往很多企业会选择关键词优化网络这种方式来推广自己的网站,对于新手seoe…