「Tech初见」对epoll的理解

一、Motivation

通常,操作系统会为每个进程划分一个时间片的,在这个时间片内进程可以合法占有 cpu 进行一些计算任务。并当时间片结束后自动退回至就绪状态待命,等待下一次的调度

但是,有一种情况会使进程提前(时间片还未用完)进入等待状态,即是进程发生了阻塞(多半是因为 I/O 请求)。进程一旦发生了阻塞,它就要让出 cpu 给其他进程,这个让位的动作就是进程之间切换的操作,这种操作非常蠢(在开发者眼里是无用功),也很耗时。可以说是时间和 cpu 资源没用在正儿八经的计算任务上

select 和 epoll 的提出就是来解决这个愚蠢的问题,有一种设想:在分配给该进程时间片还未结束之前,如果进程的某个 socket 连接发生阻塞,先不急着逼该进程退位,而是通过某种手段去查询一下进程的其他 socket 连接是否有已就绪的。如果其他 socket 连接有活动可以处理,不如充分利用 cpu 先进行计算,在处理完成 OR 时间片到期后再让位也不迟。这样不就可以提高计算机资源的利用率了嘛

但是,在 Linux 老的版本中,有关事件触发的问题,一直是采用 select 轮询手段来解决的,所谓的轮询就是 cpu 不停地去查询任务队列是否有已经就绪的任务。这种方法在任务较少的情况下还能勉强应付,当任务数量增加至千级数量级之后,效率就会出现断崖式地降低。因为每次需要轮询上千个任务,自然非常耗时

为此,Linux 提出了新的解决方法 epoll,不再采用轮询的方法来感知新事件的发生,而是通过 epoll 结构体内部的红黑树来自动将等待的任务和就绪的任务分开,从而使 kernel 能够快速感知新事件的发生

再说直白一点,只要活儿足够多,epoll_wait 根本就不会让用户进程阻塞,用户进程会一直干活,直到属于该进程的时间片结束。这样就大大减少了进程切换次数,提高了效率

epoll 相比于 select 和 poll 厉害的地方,即是它可以快速感知已连接的 socket 动静,而 select 和 poll 需要用户层将描述符集合 sockets 传入 kernel,接着进行一次遍历之后才能知道 socket 的变动情况

其实,socket 有什么动静我们是能够快速感知的(通过 TCP/IP 协议),但是我们要准确识别到底是哪个 socket 发生了变化,这件事确实有很多种不同的做法,不同的做法也带来了不同的效率

epoll 是在用户层第一次注册 socket 的时候就将其传入 kernel 了,具体表现为在 epoll 对象的红黑树 rbr 中创建该 socket 的 epitem,这种行为就是用户层数据拷贝进 kernel 的动作。与 select 和 poll 每次都要将集合 sockets 传入 kernel 的事实相比,无疑是 epoll 在初始化时就将 socket 传入 kernel 的做法效率要高很快

epoll 效率高的背后其实是牺牲 kernel 空间(创建红黑树)来换取时间的妥协,select 和 poll 因为只是临时(遍历完会释放)占用 kernel 空间,所以它们的效率比较低。归根结底,就是空间换时间

二、Solutions

S1 - epoll_create

创建一个 epoll 句柄,size 用来告诉 kernel 共能监听多少个事件,

int epoll_create(int size)

这个参数在现在的版本中没有意义,kernel 会根据实际情况自行决定的,意思就是说这个 size 只是我们规定的事件的大致数量,而不是能够处理的最大事件数

epoll 结构体中定义的等待队列 wq 存放阻塞在 epoll 对象上的用户进程,当软中断数据就绪时会前来寻找进程;epoll 对象用红黑树 rbr 来管理用户进程 accept 添加进来的所有 socket 连接,选用红黑树的原因是因为红黑树能够更好地支持海量连接的查找、插入和删除;就绪链表 rdllist 存放着一些已就绪的任务,这样一来,应用进程只需要查询 rdllist 就能判断是否有就绪任务可供处理,而不必去遍历整棵红黑树

S2 - epoll_ctl

该方法向 epoll 对象中添加、修改和删除特定的事件,返回 0 表示成功,-1 表示失败,

int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event)

添加意味着对这件事感兴趣,应用进程想收来处理;删除则表示对这件事没了兴趣。其中,epfd 是 epoll 对象的 id,epoll_create() 的返回值;op 有三种操作类型,EPOLL_CTL_ADD、EPOLL_CTL_MOD 和 EPOLL_CTL_DEL;fd 是需要监听的文件描述符,通常是连接至服务端的 socket;最后一个参数 event 可以是以下几种宏的集合,

  • EPOLLIN:文件描述符可读
  • EPOLLOUT:文件描述符可写
  • EPOLLPRI:文件描述符有紧急数据可读
  • EPOLLERR:文件描述符发生错误
  • EPOLLHUP:文件描述符被挂断
  • EPOLLET:边缘触发(后面会讲到)
  • EPOLLONESHOT:只监听一次,意味着触发来事件之后就被踢出 epoll 对象中了

它是一个传入的指针,这就要求我们需要在进入函数之前分配好空间并初始化,以便 epoll_create() 可以在方法内获取内容,但 epoll_create() 并不会替我们释放 events 空间

再进一步解释,当有新的 socket 连接加入 epoll 对象时,epoll 对象会创建一个 epitem 用来关联该 socket 连接,然后将 epitem 挂到红黑树 rbr 中。之后,会设置该 epitem 的回调函数(如果该连接有数据写入,请将其存入 epoll 对象的就绪链表 rdllist 中),以及其他的回调函数

在这我只列举了 “增” 的一个例子,其他关于 “删” 和 “改” 的操作,它们的本质是一样的,都是 socket 连接有什么动作就会去调用对应的回调函数。关于能够快速实现 “增删改查” 最主要的原因是因为选用了红黑树

补充一个 demo,假设处于阻塞状态的 socket 有数据写入了,第一步会去通知红黑树 rbr 找到(很快)该 socket 的 epitem;然后,调用 epitem 的回调函数将 epitem 加入就绪链表 rdllist 中。这一步主要是为了 epoll_wait 能够快速获取已经就绪的 socket 信息

S3 - epoll_wait

等待处于监听范围的事件发生,

int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout)

epoll 对象会将已经发生的事件复制到数组 events 中,maxevents 是数组的长度;timeout 如果为 0,则意味着就绪链表 rdllist 若为空则立刻返回,不会等待;-1 表示阻塞,会一直陷入 epoll_wait 状态中

关于 ET 和 LT 模式,我想用简短的语言去描述,不要深究细节。ET(边缘触发)模式仅当状态发生变化时才会感知事件的发生,即使这个事件对应的缓冲区内还有未读取的数据;而 LT(水平触发)模式是只要有数据没处理就会一直通知下去

三、Result

我想透过一个简单的 demo 来介绍 epoll 的经典用法。说到用法,最常用的就是连接 socket,监听 socket 的动静并读/写数据进行处理,之后返回给 client 结果。我写了一个小写转大写的程式来说明 epoll 的用法,请看代码,

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <ctype.h>
#include <unistd.h>#define EPOLL_MAXSIZE 16
#define SRV_PORT_ID 1980  /* 端口号 */
#define SOCKET_QUEUE_LEN 20
#define BUFSIZE 256struct myepoll_data {int fd;char data[BUFSIZE];
};int main()
{int i,j;int epfd, sockfd, nfds, clntfd;struct sockaddr_in srvaddr, clntaddr;struct epoll_event ev, evs[EPOLL_MAXSIZE];socklen_t clntlen = sizeof(clntaddr);char buf[BUFSIZE];/* 创建epoll结构体(就绪链表、等待队列和红黑树) */epfd = epoll_create(EPOLL_MAXSIZE);if(epfd == -1) {printf("epoll_create err\n");goto over;}printf("epoll_create ok\n");/* 创建socket结构体 */sockfd = socket(AF_INET, SOCK_STREAM, 0);if(sockfd == -1) {printf("socket_create err\n");goto over;}printf("socket_create ok\n");/* 初始化socket绑定监听 */bzero(&srvaddr, sizeof(srvaddr));srvaddr.sin_family = AF_INET;srvaddr.sin_port = htons(SRV_PORT_ID);srvaddr.sin_addr.s_addr = htonl(INADDR_ANY);if(bind(sockfd, (struct sockaddr*)&srvaddr, sizeof(struct sockaddr)) == -1) {printf("socket_bind err\n");goto over;}printf("socket_bind ok\n");if(listen(sockfd, SOCKET_QUEUE_LEN) == -1) {printf("socket_listen err\n");goto over;}printf("socket_listen ok\n");/* 向epoll结构体中注册socket,实现监听功能 */ev.data.fd = sockfd;ev.events = EPOLLIN | EPOLLET;if(epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev) == -1) {printf("epoll_ctl_add err\n");goto over;}printf("epoll_ctl_add ok\n");/* 不停地处理外来事件 */while(1) {/* 阻塞地等待事件发生,其中0为没有就绪事件就立刻返回,-1为阻塞 */nfds = epoll_wait(epfd, evs, EPOLL_MAXSIZE, -1);/* 处理每个收上来的事件 */for(i=0; i<nfds; i++) {if(evs[i].data.fd == sockfd) {  /* 有人敲sockfd的门了(收到新的连接)*/clntfd = accept(sockfd, (struct sockaddr*)&clntaddr, &clntlen);ev.events = EPOLLIN | EPOLLET;ev.data.fd = clntfd;if(epoll_ctl(epfd, EPOLL_CTL_ADD, clntfd, &ev) == -1)printf("epoll_ctl_add %d err\n", clntfd);elseprintf("epoll_ctL_add %d clnt ok\n", clntfd);} else if(evs[i].events & EPOLLIN) {  /* 读取数据但先不处理 */clntfd = evs[i].data.fd;memset(buf, 0, BUFSIZE);if(read(clntfd, buf, BUFSIZE) == 0) { /* 客户端关闭连接 */if(epoll_ctl(epfd, EPOLL_CTL_DEL, clntfd, NULL) == -1) {printf("epoll_ctl_del %d err\n", clntfd);} else {printf("epoll_ctl_del %d ok\n", clntfd);close(clntfd);}continue;}/* 先接收client的请求 */struct myepoll_data fddata;fddata.fd = clntfd;strcpy(fddata.data, buf);ev.data.ptr = &fddata;memset(buf, 0, BUFSIZE);strcpy(buf, "i'm keep u's data, deal with it later, please check u can be written...\n");send(clntfd, buf, strlen(buf), 0);ev.events = EPOLLOUT | EPOLLET;/* 下一次epoll时再处理client的请求 */if(epoll_ctl(epfd, EPOLL_CTL_MOD, clntfd, &ev) == -1) printf("epoll_ctl_mod clnt %d EPOLLIN -> EPOLLOUT err\n", clntfd);else printf("epoll_ctl_mod clnt %d EPOLLIN -> EPOLLOUT ok\n", clntfd);} else if(evs[i].events & EPOLLOUT) { /* 对之前读取的数据予以处理并将处理结果返回给client */struct myepoll_data* fddata = (struct myepoll_data*)evs[i].data.ptr;clntfd = fddata->fd;char* data = fddata->data;memset(buf, 0, BUFSIZE);strcpy(buf, "i'm processing u's data, please waiting...\n");send(clntfd, buf, strlen(buf), 0);/* 将小写转为大写的业务逻辑 */for(j=0; j<strlen(data); j++)data[j] = toupper(data[j]);send(clntfd, data, strlen(data), 0);ev.events = EPOLLIN | EPOLLET;/* 准备接收client的下一次计算请求 */if(epoll_ctl(epfd, EPOLL_CTL_MOD, clntfd, &ev) == -1)printf("epoll_ctl_mod clnt %d EPOLLOUT -> EPOLLIN err\n", clntfd);else printf("epoll_ctl_mod clnt %d EPOLLOUT -> EPOLLIN ok\n", clntfd);} else {printf("unknown event\n");}}}over:return 0;
}

整个流程,我认为较为清晰,首先创建 socket,然后将 socket 添加进 epoll 对象中,这就意味着让 epoll 对象监听 socket 的一举一动。如果有数据写入 socket 中,那么就读出来,等待下一轮再进行处理(为什么下一轮再进行处理?而不是接收了请求就处理,其中的道理我暂时还没有悟透,但有人告诉我,先接收后处理的手法是 epoll 的精髓,说实话我并不认同,因为我不能说服自己要相信这种脱裤子放屁的说法)

按照流程走下去,在下一轮中进行处理(小写转大写),然后将结果返回给 client。这就是 epoll demo。在另一个终端中透过 nc 命令尝试连接 server 进程,

nc 127.0.0.1 1980

作为 client,输入小写的字符串,server 就会返回大写的结果,

在这里插入图片描述

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

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

相关文章

vue中为什么data属性是一个函数而不是一个对象

面试官&#xff1a;为什么data属性是一个函数而不是一个对象&#xff1f; 一、实例和组件定义data的区别 vue实例的时候定义data属性既可以是一个对象&#xff0c;也可以是一个函数 const app new Vue({el:"#app",// 对象格式data:{foo:"foo"},// 函数格…

EDA实验-----4*4矩阵键盘与数码管显示测试(Quartus ‖)

目录 一、实验目的 二、实验仪器设备 三、实验原理 四、实验要求 五、实验步骤 六、实验报告 七、实验过程 1.矩阵键盘按键原理 2.数码管原理 3.分频器代码 4.电路图连接 5.文件烧录 一、实验目的 了解数码管的工作原理&#xff1b;掌握4*4矩阵键盘和数码管显示的编…

纵行科技亮相2023汽车物流行业年会,与菜鸟共推ZETag资产管理方案

近日&#xff0c;由中物联汽车物流分会主办的“汽车物流行业年会”在十堰召开。纵行科技受邀亮相&#xff0c;并与菜鸟共推ZETag资产管理方案&#xff0c;助力汽车物流数字化发展。 当前&#xff0c;我国物流业处于恢复性增长和结构性调整的关键期&#xff0c;国务院印发的《…

大模型的交互能力

摘要&#xff1a; 基础大模型显示出明显的潜力&#xff0c;可以改变AI系统的开发人员和用户体验&#xff1a;基础模型降低了原型设计和构建AI应用程序的难度阈值&#xff0c;因为它们在适应方面的样本效率&#xff0c;并提高了新用户交互的上限&#xff0c;因为它们的多模式和生…

中间件安全:Apache 目录穿透.(CVE-2021-41773)

中间件安全&#xff1a;Apache 目录穿透.&#xff08;CVE-2021-41773&#xff09; Apache 的 2.4.49、2.4.50 版本 对路径规范化所做的更改中存在一个路径穿越漏洞&#xff0c;攻击者可利用该漏洞读取到Web目录外的其他文件&#xff0c;如系统配置文件、网站源码等&#xff0c…

K-Means聚类

文章目录 概要整体架构流程技术名词解释技术细节小结 概要 K-means聚类算法实现 技术细节 选取的数据集是sklearn.datasets里面的鸢尾花数据集&#xff0c;方便最后的算法评价。 根据手肘法&#xff08;即根据SSE代价函数&#xff09;得出最合适的k值。 此处思路是先根据E …

【实用技巧】更改ArduinoIDE默认库文件位置,解放系统盘,将Arduino15中的库文件移动到其他磁盘

本文主要介绍更改Arduino IDE &#xff08;含2.0以上版本&#xff09;默认库文件位置的方法。 原创文章&#xff0c;转载请注明出处&#xff1a; 【实用技巧】更改ArduinoIDE默认库文件位置&#xff0c;解放C盘&#xff0c;将Arduino15中的库文件移动到其他磁盘-CSDN博客文章浏…

Kubernetes Dashboard部署ImagePullBackOff问题处理

通常&#xff0c;出现ImagePullBackOff问题是由于Kubernetes集群无法拉取所需的镜像导致的。解决这个问题的方法通常包括以下步骤&#xff1a; 1. 检查Pod的描述信息&#xff1a; kubectl describe pod/[pod名称] --namespacekubernetes-dashboard 查看Events部分是否有关于…

Windows安装Java环境(OracleJDK)

在下载之前&#xff0c;我们先了解一下java的前世今生 1991年&#xff1a;Java 的前身 Oak 由 James Gosling 和他的团队在 Sun Microsystems 公司开发。1995年&#xff1a;Oak 更名为 Java&#xff0c;并在同年发布。Java 1.0 版本正式推出。1996年&#xff1a;Sun Microsyst…

Threejs_06 多材质的实现

Threejs 同一个几何体如何实现多材质呢&#xff1f; 多材质的实现 1.使用索引绘制一个几何体 //创建几何体(三角形) const geometry new THREE.BufferGeometry();//使用索引绘制 (两个共用的) const vertices new Float32Array([-1.0, -1.0, 0.0, 1.0, -1.0, 0.0, 1.0, 1…

谈谈系统性能调优中都需要考虑哪些因素

一、 什么是性能调优&#xff1f; 这个系统好慢、网站又打不开了&#xff0c;太卡了&#xff0c;又没响应了&#xff0c;相信大家都遇到过用户的这种抱怨&#xff0c;此时&#xff0c;说明我们的应用系统出现了性能问题&#xff0c;那么怎么办呢&#xff0c;首先想到的应该是优…

HP惠普暗影精灵9笔记本电脑OMEN by HP Transcend 16英寸游戏本16-u0000原厂Windows11系统

惠普暗影9恢复出厂开箱状态&#xff0c;原装出厂Win11-22H2系统ISO镜像 下载链接&#xff1a;https://pan.baidu.com/s/17ftbBHEMFSEOw22tnYvPog?pwd91p1 提取码&#xff1a;91p1 适用型号&#xff1a;16-u0006TX、16-u0007TX、16-u0008TX、16-u0009TX、16-u0017TX 原厂系…

数据结构与算法编程题2

逆置线性表&#xff0c;使空间复杂度为 O(1) #include <iostream> using namespace std;typedef int ElemType; #define Maxsize 100 #define OK 1 #define ERROR 0 typedef struct SqList {ElemType data[Maxsize];int length; }SqList;void Init_SqList(SqList& …

YOLOV8部署Android Studio安卓平台NCNN

下载Android Studio&#xff0c;配置安卓开发环境&#xff0c;这个过程比较漫长。 安装cmake&#xff0c;注意安装的是cmake3.10版本。 根据手机安卓版本选择相应的安卓版本&#xff0c;我的是红米K30Pro&#xff0c;安卓12。 使用腾讯开源的ncnn&#xff0c;这是一个为手机端极…

驶入产业发展快车道,汉鑫科技人工智能研发中心正式启用!

11月18日&#xff0c;汉鑫科技人工智能研发中心正式启用。中心立足烟台&#xff0c;服务全国&#xff0c;聚焦工业智能、智能网联、智慧城市三大业务板块&#xff0c;以人工智能技术赋能政企实现“数智化”转型升级。该中心的启用标志着汉鑫科技在人工智能研发应用领域迈上了新…

移动端表格分页uni-app

使用uni-app提供的uni-table表格 网址&#xff1a;https://uniapp.dcloud.net.cn/component/uniui/uni-table.html#%E4%BB%8B%E7%BB%8D <uni-table ref"table" :loading"loading" border stripe type"selection" emptyText"暂无更多数据…

【Nacos】配置管理、微服务配置拉取、实现配置热更新、多环境配置

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaEE 操作系统 Redis 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 Nacos 一、nacos实现配置管理1.1 统一配置管…

Taro安装及使用

安装及使用 安装​ Taro 项目基于 node&#xff0c;请确保已具备较新的 node 环境&#xff08;>12.0.0&#xff09;&#xff0c;推荐使用 node 版本管理工具 nvm 来管理 node&#xff0c;这样不仅可以很方便地切换 node 版本&#xff0c;而且全局安装时候也不用加 sudo 了…

不必购买Mac,这款国产设计工具能轻松替代Sketch!

介绍 即时设计是新一代可以直接在浏览器中使用的设计工具&#xff0c;具有Sketch和实时协作功能。与本地Sketch相比&#xff0c;增加了实时协作功能&#xff0c;即时设计可以看作是在线Sketch&#xff0c;两个工具可以简单粗暴地总结为一个公式&#xff1a; 即时设计Sketch云…

Nginx - 本机读取服务器图像、视频

目录 一.引言 二.安装 Nginx 1.安装 By apt 2.安装 By 官网 三.配置 Nginx 1.Linux 机器配置 2.重启 Nginx 服务 3.本机查看机器文件 四.总结 一.引言 前面介绍了 GFP-GAN、PNG2GIF、GIF2PNG 等操作&#xff0c;我们生成的 video、gif、png 等形式的文件都存储在 lin…