6个epoll设计,让你对着面试官打喷嚏,他也无法顶嘴

 2024-03-10 00:17:48  阅读 0

从事服务器端开发需要接触网络编程。 epoll是Linux下高性能网络服务器的必备技术。 Nginx、Redis 和大多数游戏服务器都使用这种复用技术。

◆() 和 poll() IO 复用模型

缺点:

单个进程可以监控的文件描述符数量有最大限制,通常为1024。当然这个数量是可以改变的,但是由于扫描文件描述符的轮询方式,文件描述符数量越大,性能越差; (在Linux内核头文件中,有这样的定义:#1024)

内核/用户空间内存复制问题需要复制大量的句柄数据结构,造成巨大的开销;

返回的是一个包含整个句柄的数组。 应用程序需要遍历整个数组来发现哪些句柄经历了事件;

触发方式为水平触发。 如果应用程序没有完成对一个就绪文件描述符的IO操作,那么后续的每次调用仍然会通知进程这些文件描述符。

与模型相比,poll 使用链表保存文件描述符,因此监控的文件数量没有限制,但其他三个缺点仍然存在。

以模型为例,假设我们的服务器需要支持100万并发连接,如果是1024的话,我们至少需要开启1k个进程才能实现100万并发连接。 除了进程间上下文切换的时间消耗之外,来自内核/用户空间的大量无意识的内存复制、数组轮询等都是系统难以承受的。 因此,基于模型的服务器程序要实现10万级的并发访问是一个困难的任务。

那么,epoll 是时候登场了。

通信eutran_epb通信信号错误_epoll大量通信出问题

◆epoll IO复用模型实现机制

由于epoll的实现机制与/poll机制完全不同,因此上述缺点在epoll上不再存在。

想象一下以下场景:100 万个客户端同时维护与服务器进程的 TCP 连接。 每个时刻,通常只有数百或数千个 TCP 连接处于活动状态(事实上,大多数场景都是这种情况)。 这么高的并发是如何实现的呢?

在/poll时代,服务器进程每次都会将这100万个连接告知操作系统(将句柄数据结构从用户态复制到内核态),让操作系统内核查询这些套接字上是否发生了事件,并轮询后,将句柄数据复制到用户模式,并轮询服务器应用程序以处理已发生的网络事件。 这个过程消耗大量资源。 因此/poll一般只能处理几千个并发连接。

epoll的设计和实现完全不同。 epoll在Linux内核中申请一个简单的文件系统(文件系统一般用什么数据结构来实现?B+树)。 将原始 /poll 调用分为 3 部分:

1)调用()创建epoll对象(在epoll文件系统中为这个句柄对象分配资源)

2)调用将这100万个已连接的套接字添加到epoll对象中

3)调用连接收集发生的事件

这样,要实现上述场景,只需要在进程启动时创建一个epoll对象,然后在需要的时候添加或删除到这个epoll对象的连接即可。 同时效率也很高,因为调用时,这100万个连接的句柄数据并没有复制到操作系统中,内核不需要遍历所有连接。

通信eutran_epoll大量通信出问题_epb通信信号错误

epoll很重要,但是epoll和epoll有什么区别呢? epoll高效的原因是什么?

从网卡接收数据开始

下面是典型的计算机结构图。 计算机由CPU、存储器(存储器)和网络接口组成。 了解Epoll本质的第一步就是从硬件角度看计算机是如何接收网络数据的。

epb通信信号错误_epoll大量通信出问题_通信eutran

计算机结构图(图片来源:Linux内核完全注释微机结构)

下图为网卡接收数据的流程:

这个过程涉及到DMA传输、IO通道选择等硬件相关知识,但我们只需要知道网卡会将接收到的数据写入内存即可。

epb通信信号错误_通信eutran_epoll大量通信出问题

网卡接收数据的流程

通过硬件传输,网卡接收到的数据存储在内存中,操作系统可以读取它们。

你怎么知道数据已经收到了?

了解Epoll本质的第二步是从CPU的角度来看数据接收。 要理解这个问题,我们首先要理解一个概念:中断

当计算机执行程序时,它有优先级要求。 例如,当计算机收到断电信号时,应立即保存数据。 保存数据的程序优先级较高(电容可以节省少量电量供CPU短时间运行)。

一般来说,硬件产生的信号需要CPU立即响应,否则数据可能会丢失,所以它的优先级非常高。

CPU应中断正在执行的程序来响应; 当CPU完成对硬件的响应后,应重新执行用户程序。

中断过程如下图所示。 它与函数调用类似,只不过函数调用有预定的位置,而中断位置是由“信号”决定的。

中断例程调用

以键盘为例,当用户按下键盘上的按键时,键盘会向CPU的中断引脚发送高电平。 CPU可以捕获该信号,然后执行键盘中断程序。

下图展示了各种硬件通过中断与CPU交互的过程:

epoll大量通信出问题_通信eutran_epb通信信号错误

当网卡将数据写入内存时,网卡向CPU发送中断信号。 操作系统这时就可以知道有新的数据到达,然后通过网卡中断程序来处理这些数据。

epoll的线程安全设计

1.对rbtree-->加锁;
2.对queue -->spinlock
3.epoll_wati,cond,mutex

开源项目中ET和LT的选择

ET模式
因为ET模式只有从unavailable到available才会触发,所以

读事件:需要使用while循环读取完,一般是读到EAGAIN,

也可以读到返回值小于缓冲区大小;

如果应用层读缓冲区满:那就需要应用层自行标记,

解决OS不再通知可读的问题


写事件:需要使用while循环写到EAGAIN,

也可以写到返回值小于缓冲区大小

如果应用层写缓冲区空(无内容可写):那就需要应用层自行标记,

解决OS不再通知可写的问题。


LT模式
因为LT模式只要available就会触发,所以:

读事件:因为一般应用层的逻辑是“来了就能读”,

所以一般没有问题,无需while循环读取到EAGAIN;

如果应用层读缓冲区满:就会经常触发,解决方式如下;

写事件:如果没有内容要写,就会经常触发,解决方式如下。

LT经常触发读写事件的解决办法:修改fd的注册事件,

或者把fd移出epollfd。


总结
目前好像还是LT方式应用较多,包括redis,libuv.

LT模式的优点在于:事件循环处理比较简单,

无需关注应用层是否有缓冲或缓冲区是否满,只管上报事件

缺点是:可能经常上报,可能影响性能。


在我目前阅读的开源项目中:

TARS用的是ET,Redis用的是LT,

Nginx可配置(开启NGX_HAVE_CLEAR_EVENT

--ET,开启NGX_USE_LEVEL_EVENT则为LT).

标签: 中断 内核 网卡

如本站内容信息有侵犯到您的权益请联系我们删除,谢谢!!


Copyright © 2020 All Rights Reserved 京ICP5741267-1号 统计代码