flash网站建设,网站遇到攻击时应该怎么做,网站建设发言,黑科技引流工具引言 epoll 是 Linux 系统下高性能网络服务的必备技术#xff0c;很多面试中高频出现的 Nginx、Redis 都使用了这一技术#xff0c;本文总结 linux 多路复用模型的演变过程#xff0c;看一看epoll 是如何实现高性能的。
一、相关基础知识
1.1 文件描述符
文件描述符很多面试中高频出现的 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 模型是什么》