188bet.com社区

 找回暗码
 注册
188bet官方网址
188bet.com社区 主页 业界资讯 技能文摘 检查内容

epoll的实质是什么?

2019-5-22 10:42| 发布者: joejoe0332| 检查: 1444| 谈论: 0|原作者: oschina|来自: oschina

摘要: 从事服务端开发,少不了要触摸网络编程。epoll 作为 Linux 下高性能网络服务器的必备技能至关重要,nginx、Redis、Skynet 和大部分游戏服务器都运用到这一多路复用技能。 epoll 很重要,可是 epoll 与 select 的差异 ...

从事服务端开发,少不了要触摸网络编程。epoll 作为 Linux 下高性能网络服务器的必备技能至关重要,nginx、Redis、Skynet 和大部分游戏服务器都运用到这一多路复用技能。

epoll 很重要,可是 epoll 与 select 的差异是什么呢?epoll 高效的原因是什么?

网上尽管也有不少解说 epoll 的文章,但要么是过于粗浅,或许堕入源码解析,很少能有通俗易懂的。笔者所以决议编写此文,让缺少专业布景常识的读者也能够了解 epoll 的原理。

文章中心思维是:要让读者明晰了解 epoll 为什么性能好。

本文会从网卡接纳数据的流程讲起,串联起 CPU 中止、操作体系进程调度等常识;再一步步剖析堵塞接纳数据、select 到 epoll 的进化进程;终究探求 epoll 的完成细节。

一、从网卡接纳数据说起

下边是一个典型的核算机结构图,核算机由 CPU、存储器(内存)与网络接口等部件组成,了解 epoll 实质的榜首步,要从硬件的视点看核算机怎样接纳网络数据。

 

核算机结构图(图片来历:Linux内核彻底注释之微型核算机组成结构)

下图展现了网卡接纳数据的进程。

  • 在 ① 阶段,网卡收到网线传来的数据;
  • 经过 ② 阶段的硬件电路的传输;
  • 终究 ③ 阶段将数据写入到内存中的某个地址上。

这个进程触及到 DMA 传输、IO 通路挑选等硬件有关的常识,但咱们只需知道:网卡会把接纳到的数据写入内存


 
网卡接纳数据的进程

经过硬件传输,网卡接纳的数据寄存到内存中,操作体系就能够去读取它们。

二、怎么知道接纳了数据?

了解 epoll 实质的第二步,要从 CPU 的视点来看数据接纳。了解这个问题,要先了解一个概念——中止。

核算机履行程序时,会有优先级的需求。比方,当核算机收到断电信号时,它应立即去保存数据,保存数据的程序具有较高的优先级(电容能够保存少量电量,供 CPU 运转很短的一小段时刻)。

一般来说,由硬件发作的信号需求 CPU 立马做出回应,否则数据或许就丢掉了,所以它的优先级很高。CPU 理应中止掉正在履行的程序,去做出呼应;当 CPU 完成对硬件的呼应后,再从头履行用户程序。中止的进程如下图,它和函数调用差不多,只不过函数调用是事前定好方位,而中止的方位由“信号”决议。


 
中止程序调用

以键盘为例,当用户按下键盘某个按键时,键盘会给 CPU 的中止引脚宣布一个高电平,CPU 能够捕获这个信号,然后履行键盘中止程序。下图展现了各种硬件经过中止与 CPU 交互的进程。


CPU 中止(图片来历:net.pku.edu.cn)

现在能够答复“怎么知道接纳了数据?”这个问题了:当网卡把数据写入到内存后,网卡向 CPU 宣布一个中止信号,操作体系便能得知有新数据到来,再经过网卡中止程序去处理数据。

三、进程堵塞为什么不占用 CPU 资源?

了解 epoll 实质的第三步,要从操作体系进程调度的视点来看数据接纳。堵塞是进程调度的要害一环,指的是进程在等候某事情(如接纳到网络数据)发作之前的等候状况,recv、select 和 epoll 都是堵塞办法。下边剖析一下进程堵塞为什么不占用 CPU 资源?

为简略起见,咱们从一般的 recv 接纳开端剖析,先看看下面代码:

//创立socket
int s = socket(AF_INET, SOCK_STREAM, 0);   
//绑定
bind(s, ...)
//监听
listen(s, ...)
//承受客户端衔接
int c = accept(s, ...)
//接纳客户端数据
recv(c, ...);
//将数据打印出来
printf(...)

这是一段最根底的网络编程代码,先新建 socket 目标,顺次调用 bind、listen 与 accept,终究调用 recv 接纳数据。recv 是个堵塞办法,当程序运转到 recv 时,它会一向等候,直到接纳到数据才往下履行。

那么堵塞的原理是什么?

作业行列

操作体系为了支撑多使命,完成了进程调度的功用,会把进程分为“运转”和“等候”等几种状况。运转状况是进程取得 CPU 运用权,正在履行代码的状况;等候状况是堵塞状况,比方上述程序运转到 recv 时,程序会从运转状况变为等候状况,接纳到数据后又变回运转状况。操作体系会分时履行各个运转状况的进程,由于速度很快,看上去就像是一起履行多个使命。

下图的核算机中运转着 A、B 与 C 三个进程,其间进程 A 履行着上述根底网络程序,一开端,这 3 个进程都被操作体系的作业行列所引证,处于运转状况,会分时履行。

作业行列中有 A、B 和 C 三个进程

等候行列

当进程 A 履行到创立 socket 的句子时,操作体系会创立一个由文件体系办理的 socket 目标(如下图)。这个 socket 目标包含了发送缓冲区、接纳缓冲区与等候行列等成员。等候行列是个十分重要的结构,它指向一切需求等候该 socket 事情的进程。

创立 socket

当程序履行到 recv 时,操作体系会将进程 A 从作业行列移动到该 socket 的等候行列中(如下图)。由于作业行列只剩下了进程 B 和 C,根据进程调度,CPU 会轮番履行这两个进程的程序,不会履行进程 A 的程序。所以进程 A 被堵塞,不会往下履行代码,也不会占用 CPU 资源。

socket 的等候行列

注:操作体系增加等候行列仅仅增加了对这个“等候中”进程的引证,以便在接纳到数据时获取进程目标、将其唤醒,而非直接将进程办理归入自己之下。上图为了便利阐明,直接将进程挂到等候行列之下。

唤醒进程

当 socket 接纳到数据后,操作体系将该 socket 等候行列上的进程从头放回到作业行列,该进程变成运转状况,持续履行代码。一起由于 socket 的接纳缓冲区现已有了数据,recv 能够回来接纳到的数据。

四、内核接纳网络数据全进程

这一步,贯穿网卡、中止与进程调度的常识,叙说堵塞 recv 下,内核接纳数据的全进程。

如下图所示,进程在 recv 堵塞期间,核算机收到了对端传送的数据(进程①),数据经由网卡传送到内存(进程②),然后网卡经过中止信号告诉 CPU 有数据抵达,CPU 履行中止程序(进程③)。

此处的中止程序首要有两项功用,先将网络数据写入到对应 socket 的接纳缓冲区里边(进程④),再唤醒进程 A(进程⑤),从头将进程 A 放入作业行列中。

内核接纳数据全进程

唤醒进程的进程如下图所示:

唤醒进程

以上是内核接纳数据全进程,这儿咱们或许会考虑两个问题:

  • 其一,操作体系怎么知道网络数据对应于哪个 socket?
  • 其二,怎么一起监督多个 socket 的数据?

榜首个问题:由于一个 socket 对应着一个端口号,而网络数据包中包含了 ip 和端口的信息,内核能够经过端口号找到对应的 socket。当然,为了进步处理速度,操作体系会保护端口号到 socket 的索引结构,以快速读取。

第二个问题是多路复用的重中之重,也正是本文后半部分的要点。

五、一起监督多个 socket 的简略办法

服务端需求办理多个客户端衔接,而 recv 只能监督单个 socket,这种对立下,人们开端寻觅监督多个 socket 的办法。epoll 的要义便是高效地监督多个 socket

从前史开展视点看,必定先呈现一种不太高效的办法,人们再加以改善,正如 select 之于 epoll。

先了解不太高效的 select,才能够更好地了解 epoll 的实质。

假定能够预先传入一个 socket 列表,假定列表中的 socket 都没有数据,挂起进程,直到有一个 socket 收到数据,唤醒进程。这种办法很直接,也是 select 的规划思维。

为便利了解,咱们先温习 select 的用法。在下边的代码中,先预备一个数组 fds,让 fds 寄存着一切需求监督的 socket。然后调用 select,假定 fds 中的一切 socket 都没有数据,select 会堵塞,直到有一个 socket 接纳到数据,select 回来,唤醒进程。用户能够遍历 fds,经过 FD_ISSET 判别详细哪个 socket 收到数据,然后做出处理。

int s = socket(AF_INET, SOCK_STREAM, 0);  
bind(s, ...);
listen(s, ...);
int fds[] =  寄存需求监听的socket;
while(1){
    int n = select(..., fds, ...)
    for(int i=0; i < fds.count; i++){
        if(FD_ISSET(fds[i], ...)){
            //fds[i]的数据处理
        }
    }}

select 的流程

select 的完成思路很直接,假定程序一起监督如下图的 sock1、sock2 和 sock3 三个 socket,那么在调用 select 之后,操作体系把进程 A 别离参加这三个 socket 的等候行列中。

操作体系把进程 A 别离参加这三个 socket 的等候行列中

当任何一个 socket 收到数据后,中止程序将引发进程。下图展现了 sock2 接纳到了数据的处理流程:

注:recv 和 select 的中止回调能够设置成不同的内容。

sock2 接纳到了数据,中止程序引发进程 A

所谓引发进程,便是将进程从一切的等候行列中移除,参加到作业行列里边,如下图所示:

将进程 A 从一切等候行列中移除,再参加到作业行列里边

经由这些进程,当进程 A 被唤醒后,它知道至少有一个 socket 接纳了数据。程序只需遍历一遍 socket 列表,就能够得到安排妥当的 socket。

这种简略办法卓有成效,在简直一切操作体系都有对应的完成。

可是简略的办法往往有缺点,首要是:

其一,每次调用 select 都需求将进程参加到一切监督 socket 的等候行列,每次唤醒都需求从每个行列中移除。这儿触及了两次遍历,并且每次都要将整个 fds 列表传递给内核,有必定的开支。正是由于遍历操作开支大,出于功率的考量,才会规则 select 的最大监督数量,默许只能监督 1024 个 socket。

其二,进程被唤醒后,程序并不知道哪些 socket 收到数据,还需求遍历一次。

那么,有没有削减遍历的办法?有没有保存安排妥当 socket 的办法?这两个问题便是 epoll 技能要处理的

弥补阐明: 本节只解说了 select 的一种景象。当程序调用 select 时,内核会先遍历一遍 socket,假定有一个以上的 socket 接纳缓冲区有数据,那么 select 直接回来,不会堵塞。这也是为什么 select 的回来值有或许大于 1 的原因之一。假定没有 socket 有数据,进程才会堵塞。

六、epoll 的规划思路

epoll 是在 select 呈现 N 多年后才被创造的,是 select 和 poll(poll 和 select 底子相同,有少量改善)的增强版别。epoll 经过以下一些办法来改善功率:

办法一:功用别离

select 低效的原因之一是将“保护等候行列”和“堵塞进程”两个进程合二为一。如下图所示,每次调用 select 都需求这两步操作,但是大多数运用场景中,需求监督的 socket 相对固定,并不需求每次都修正。epoll 将这两个操作分隔,先用 epoll_ctl 保护等候行列,再调用 epoll_wait 堵塞进程。清楚明了地,功率就能得到提高。

比较 select,epoll 拆分了功用

为便利了解后续的内容,咱们先了解一下 epoll 的用法。如下的代码中,先用 epoll_create 创立一个 epoll 目标 epfd,再经过 epoll_ctl 将需求监督的 socket 增加到 epfd 中,终究调用 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){
        //处理
    }
}

功用别离,使得 epoll 有了优化的或许。

办法二:安排妥当列表

select 低效的另一个原因在于程序不知道哪些 socket 收到数据,只能一个个遍历。假定内核保护一个“安排妥当列表”,引证收到数据的 socket,就能防止遍历。如下图所示,核算机共有三个 socket,收到数据的 sock2 和 sock3 被安排妥当列表 rdlist 所引证。当进程被唤醒后,只需获取 rdlist 的内容,就能够知道哪些 socket 收到数据。

安排妥当列表示意图

七、epoll 的原理与作业流程

本节会以示例和图表来解说 epoll 的原理和作业流程。

创立 epoll 目标

如下图所示,当某个进程调用 epoll_create 办法时,内核会创立一个 eventpoll 目标(也便是程序中 epfd 所代表的目标)。eventpoll 目标也是文件体系中的一员,和 socket 相同,它也会有等候行列。

内核创立 eventpoll 目标

创立一个代表该 epoll 的 eventpoll 目标是有必要的,由于内核要保护“安排妥当列表”等数据,“安排妥当列表”能够作为 eventpoll 的成员。

保护监督列表

创立 epoll 目标后,能够用 epoll_ctl 增加或删去所要监听的 socket。以增加 socket 为例,如下图,假定经过 epoll_ctl 增加 sock1、sock2 和 sock3 的监督,内核会将 eventpoll 增加到这三个 socket 的等候行列中。

增加所要监听的 socket

当 socket 收到数据后,中止程序会操作 eventpoll 目标,而不是直接操作进程。

接纳数据

当 socket 收到数据后,中止程序会给 eventpoll 的“安排妥当列表”增加 socket 引证。如下图展现的是 sock2 和 sock3 收到数据后,中止程序让 rdlist 引证这两个 socket。

给安排妥当列表增加引证

eventpoll 目标相当于 socket 和进程之间的中介,socket 的数据接纳并不直接影响进程,而是经过改动 eventpoll 的安排妥当列表来改动进程状况。

当程序履行到 epoll_wait 时,假定 rdlist 现已引证了 socket,那么 epoll_wait 直接回来,假定 rdlist 为空,堵塞进程。

堵塞和唤醒进程

假定核算机中正在运转进程 A 和进程 B,在某时刻进程 A 运转到了 epoll_wait 句子。如下图所示,内核会将进程 A 放入 eventpoll 的等候行列中,堵塞进程。

epoll_wait 堵塞进程

当 socket 接纳到数据,中止程序一方面修正 rdlist,另一方面唤醒 eventpoll 等候行列中的进程,进程 A 再次进入运转状况(如下图)。也由于 rdlist 的存在,进程 A 能够知道哪些 socket 发作了改变。

epoll 唤醒进程

八、epoll 的完成细节

至此,信任读者对 epoll 的实质现已有必定的了解。但咱们还需求知道 eventpoll 的数据结构是什么姿态?

此外,安排妥当行列应该应运用什么数据结构?eventpoll 应运用什么数据结构来办理经过 epoll_ctl 增加或删去的 socket?

如下图所示,eventpoll 包含了 lock、mtx、wq(等候行列)与 rdlist 等成员,其间 rdlist 和 rbr 是咱们所关怀的。

epoll 原理示意图,图片来历:《深化了解Nginx:模块开发与架构解析(第二版)》,陶辉

安排妥当列表的数据结构

安排妥当列表引证着安排妥当的 socket,所以它应能够快速的刺进数据。

程序或许随时调用 epoll_ctl 增加监督 socket,也或许随时删去。当删去时,若该 socket 现已寄存在安排妥当列表中,它也应该被移除。所以安排妥当列表应是一种能够快速刺进和删去的数据结构。

双向链表便是这样一种数据结构,epoll 运用双向链表来完成安排妥当行列(对应上图的 rdllist)。

索引结构

已然 epoll 将“保护监督行列”和“进程堵塞”别离,也意味着需求有个数据结构来保存监督的 socket,至少要便利地增加和移除,还要便于查找,以防止重复增加。红黑树是一种自平衡二叉查找树,查找、刺进和删去时刻复杂度都是O(log(N)),功率较好,epoll 运用了红黑树作为索引结构(对应上图的 rbr)。

注:由于操作体系要统筹多种功用,以及由更多需求保存的数据,rdlist 并非直接引证 socket,而是经过 epitem 直接引证,红黑树的节点也是 epitem 目标。相同,文件体系也并非直接引证着 socket。为便利了解,本文中省掉了一些直接结构。

九、小结

epoll 在 select 和 poll 的根底上引入了 eventpoll 作为中间层,运用了先进的数据结构,是一种高效的多路复用技能。这儿也以表格方式简略比照一下 select、poll 与 epoll,完毕此文。期望读者能有所收成。 

  • 快毕业了,没作业经验,
    找份作业好难啊?
    赶忙去人才芯片公司锻炼吧!!

最新谈论

关于LUPA|人才芯片工程|人才招聘|LUPA认证|LUPA教育|188bet.com社区 ( 浙B2-20090187  

回来顶部