c++应用网络编程之十二Linux下的epoll模式分析

一、epoll的原理

在上一篇文章基本明白了epoll的入门知识,本篇开始分析一下其内在的原理,让大家对epoll的运行机制有一个真正的了解。其实分析epoll的原理就必须先说明一下epoll在整个网络通信过程中的位置或者说环节,这样才能从整体上对其有一个更明白的认识。
在前面的DPDK等关于网络通信的系列里可以明白,网络通信其实分为三大块即硬件、驱动和软件。这里把驱动从软件中剥离了出来,因为从后面的发展来看,这一块也相当的重要。而软件又分成内核和应用两层。
那么epoll在哪个位置呢?从严谨的角度看,它应该划分到内核中。而大家开发常用的接口其实等同于内核开放给上层应用的接口调用。所以此处只分析这一部分,其它部分暂时略过。
在前面的学习中已经明白,IO多路复用的重点有两大部分:
1、网络fd对网络通信的影响
它又分两部分,一个是网络fd的快速增长对效率的影响;另外一个是对fd数量的限制。在epoll中,使用了红黑树再加一层双向链表做为缓冲。为什么说epoll的复杂度是O(1)呢?红黑树的算法不是O(logn)么?其实这里主要是指epoll_wait的复杂度,它是从就绪队列rdllist中查找,一查都是就绪的,所以是O(1),百分百命中。而要做这到一点,就需要红黑树在查找的后回调一个函数,将就绪的fd插入到rdllist,而链表的插入操作也是O(1)。其实看一下数据定义就明白了:

struct eventpoll {
....../* Wait queue used by sys_epoll_wait() */wait_queue_head_t wq;/* Wait queue used by file->poll() */wait_queue_head_t poll_wait;/* List of ready file descriptors */struct list_head rdllist;
....../* RB tree root used to store monitored fd structs */struct rb_root_cached rbr;struct epitem *ovflist;
.....
};

2、数据在内核与用户层间的拷贝
如果说上面是对管理过程的控制,那么现在这一条是对真正工作的优化。epoll模式下,内核与用户层使用类似mmap方式来共享fd相关的数据,从而不需要进行频繁的fd进行内核与用户层间的交互。这就相当于省略非常耗时的中间层。这和古代山间的关口一样,挨个检查费时费力,直接通过会更快。

二、底层分析

在内核中在调用epoll_create时,会创建一个eventpoll结构体,也就是上面看到的那段代码,在这段代码中,可以看两个很重要的变量,即rdllist和rbr。同时,在这个过程中,内核还创建了一个文件fd的节点(一定要明白,在Linux系统中,一切皆文件)并且创建一个Cache。Cache中使用红黑树来存储通过接口epoll_ctl传递进来的Socket即网络通信的fd。
为了能够更快速的得到就绪的fd(socket),内核还会创建一个rdllist。也就是说,在epoll_wait接口调用时,仅仅扫描rdllist是否为空即可。非空即把相关数据返回给上层应用即可。
同样,对于IO多路复用的事件,在epoll中都会通过内核与网卡驱动建立事件触发关系(一般是通过回调)。而这个事件函数的定义在epollevent.c中可以看到:

static int ep_poll_callback(wait_queue_entry_t *wait, unsigned mode, int sync, void *key)
{int pwake = 0;struct epitem *epi = ep_item_from_wait(wait);struct eventpoll *ep = epi->ep;__poll_t pollflags = key_to_poll(key);unsigned long flags;int ewake = 0;
.../** If we are transferring events to userspace, we can hold no locks* (because we're accessing user memory, and because of linux f_op->poll()* semantics). All the events that happen during that period of time are* chained in ep->ovflist and requeued later on.*/if (READ_ONCE(ep->ovflist) != EP_UNACTIVE_PTR) {if (chain_epi_lockless(epi))ep_pm_stay_awake_rcu(epi);} else if (!ep_is_linked(epi)) {/* In the usual case, add event to ready list. */if (list_add_tail_lockless(&epi->rdllink, &ep->rdllist))ep_pm_stay_awake_rcu(epi);}
...
}

而在内核中每个事件会对应一个数据结构体:

/** Each file descriptor added to the eventpoll interface will* have an entry of this type linked to the "rbr" RB tree.* Avoid increasing the size of this struct, there can be many thousands* of these on a server and we do not want this to take another cache line.*/
struct epitem {union {/* RB tree node links this structure to the eventpoll RB tree */struct rb_node rbn;/* Used to free the struct epitem */struct rcu_head rcu;};/* List header used to link this structure to the eventpoll ready list */struct list_head rdllink;/** Works together "struct eventpoll"->ovflist in keeping the* single linked chain of items.*/struct epitem *next;/* The file descriptor information this item refers to */struct epoll_filefd ffd;/* List containing poll wait queues */struct eppoll_entry *pwqlist;/* The "container" of this item */struct eventpoll *ep;/* List header used to link this item to the "struct file" items list */struct hlist_node fllink;/* wakeup_source used when EPOLLWAKEUP is set */struct wakeup_source __rcu *ws;/* The structure that describe the interested events and the source fd */struct epoll_event event;
};

这就比较明白的看清了底层逻辑。
有了这些数据结构和事件处理,整个内核其实针对epoll提供了三个部分的支持,即底层数据结构的对象的处理(红黑树的添加、删除以及双向链表的处理等)、IO事件的监控(事件监听、异步唤醒的事件队列等)以及通信数据的拷贝即可。

三、epoll的工作流程

下面看一个epoll模型的通信整体的流程(服务端):
1、首先创建socket,即创建网络操作的文件描述符或者说文件句柄
2、将其bind到指定的地址
3、开始监听listen
4、调用epoll_create创建epoll文件描述符,用于后续操作。其参数size目前可认为无效(>0即可)
5、调用epoll_ctl进行epoll事件的注册(增、删、改),此处的事件即会存储于前面所述的红黑树中
6、调用epoll_wait等待epoll事件即唤醒工作任务(可能是读或写等)
7、事件触发,调用相关处理函数并处理相关事件或数据
8、将事件或数据通知上层应用
9、回到6继续等待处理
10、收到退出信号退出
上述只是一个主干流程,可能会涉及到一些阻塞操作参数以及触发方式(LT或ET)等的设置,都没在列出来,在实际的编码中要注意。
上面的流程是不是很简单,看上去没什么嘛!可是,当你真正的写起来就会发现,到处都是细节的处理,如何快速响应触发的动作把数据拷贝走并且进行处理?如何保证不丢失数据?如何处理流式数据(也就是常说的粘包)?在ET的情况下如何处理数据是否读完?如何防止写事件的连续触发等等!特别是在多核心的服务器上,如何更好的复用每个核心而防止出现热点?这都是非常重要的考虑。但这种种都有一个前提,你的服务端处理的并发量要高。否则这些都没有实际意义。

四、总结

对于一些复杂的网络通信模型,其实仅仅明白上层的应用,是无法在实际的项目解决各种异常问题的。主动的去学习其中内在的原理并清楚运行的流程,对于解决实际问题有着非常大的帮助。这就是知其然又知其所以然的好处。与诸君共勉。
建议大家手头有一份Linux内核的源码,尽量新一些!

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

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

相关文章

低代码可视化-uniapp海报可视化设计-代码生成

在uni-app中,海报生成器通常是通过集成特定的插件或组件来实现的,这些插件或组件提供了生成海报所需的功能和灵活性。我们采用了lime-painter海报组件。lime-painter是一款canvas海报组件,可以更轻松地生成海报。它支持通过JSON及Template的方…

企业网站设计之网站版式设计

一个成功的企业网站不仅仅需要强大的技术支持,更需要合理而吸引人的版式设计。版式设计在网站建设中扮演着关键角色,直接影响用户体验和品牌形象。本文将探讨主题企业网站版式设计的关键要素。 一、清晰的信息结构: 一个主题企业网站应该具有…

STM32学习笔记---独立看门狗

目录 一、什么是独立看门狗 1、什么是看门狗 2、看门狗的原理 3、看门狗的作用 4、看门狗的分类 二、如何配置独立看门狗 1、独立看门狗框图 2、独立看门狗的相关寄存器 2.1 关键字寄存器 2.2 分频寄存器 2.3 重载值寄存器 2.4 状态寄存器 3、程序设计 4、独立看门…

零基础入门人工智能,如何利用AI工具提升你的学习效率?

在这个信息爆炸的时代,人工智能(AI)不仅是技术行业的热词,更是我们日常生活中不可或缺的部分。你是否也想过,如何更有效地学习和利用这些强大的AI工具来提升自己的学习效率?今天,我们将介绍六款…

【WRF工具】QGis插件GIS4WRF:根据嵌套网格生成namelist.wps文件

【WRF工具】QGis插件GIS4WRF:根据嵌套网格生成namelist.wps文件 准备:WRF嵌套网格QGis根据嵌套网格生成namelist.wps文件检查:根据namelist.wps绘制模拟区域ArcGIS Pro中绘制嵌套网络投影变换参考GIS4WRF 是一个免费且开源的 QGIS 插件,旨在帮助研究人员和从业者进行高级研…

【Hive】8-Hive性能优化及Hive3新特性

Hive性能优化及Hive3新特性 Hive表设计优化 Hive查询基本原理 Hive的设计思想是通过元数据解析描述将HDFS上的文件映射成表 基本的查询原理是当用户通过HQL语句对Hive中的表进行复杂数据处理和计算时,默认将其转换为分布式计算 MapReduce程序对HDFS中的数据进行…

玫瑰花HTML源码

HTML源码 <pre id"tiresult" style"font-size: 9px; background-color: #000000; font-weight: bold; padding: 4px 5px; --fs: 9px;"><b style"color:#000000">0010000100000111101110110111100010000100000100001010111111100110…

buuctf[湖南省赛2019]Findme1

解压得5个图片&#xff0c;其中图片1&#xff0c;高度不正常&#xff0c;使用下面脚本破解真实高度和宽度 import os import binascii import structcrcbp open("1.png", "rb").read() for i in range(1024):for j in range(1024):data crcbp[12:16] st…

维修数据屏:重塑热力公司运维管理新格局

在热力公司的运维管理中&#xff0c;高效的报修和维修流程是确保系统稳定运行的关键。随着科技的发展&#xff0c;维修数据屏的出现为热力公司的运维工作带来了重大变革。 一、传统热力运维面临的挑战 过去&#xff0c;热力公司在报修和维修方面存在诸多问题&#xff0c;给运维…

SpringCloud学习:Seata总结与回顾

SpringCloud学习&#xff1a;Seata总结与回顾 文章目录 SpringCloud学习&#xff1a;Seata总结与回顾1. Seata实战&#xff1a;测试2. Seate原理总结和面试题3. Seata总结与回顾4. 易混点 1. Seata实战&#xff1a;测试 测试问题 未启用分布式事务 若不使用分布式事务&#xf…

Greenhills学习总结

学习背景&#xff1a;近期参与xx项目过程中&#xff0c;遇到较多的关于代码集成编译的知识盲区&#xff0c;因此需要进行相关知识的学习和扫盲。 参考资料&#xff1a;GreenHills2017.7编译手册:本手册是GreenHills 2017.7.14版编译器的软件使用手册。该手册详细介绍了GreenHi…

Docker consul注册中心

一、consul 1.1、什么是服务注册与发现 服务注册与发现是微服务架构中不可或缺的重要组件。 起初服务都是单节点的&#xff0c;不保障高可用性&#xff0c;也不考虑服务的压力承载&#xff0c;服务之间调用单纯的通过接口访问。 直到后来出现了多个节点的分布式架构&#x…

React(五) 受控组件和非受控组件; 获取表单元素的值。高阶组件(重点),Portals; Fragment组件;严格模式StrictMode

文章目录 一、受控组件1. 什么是受控组件2. 收集input框内容3. 收集checkBox的值4. 下拉框select总结 二、非受控组件三、高阶组件1. 高阶组件的概念 (回顾高阶函数)2. 高阶组件应用&#xff1a;注入props(1) 高阶组件给---函数式组件注入props(2) 高阶组件给---类组件注入prop…

前100+大型语言模型(LLMs)面试问题和路线图

介绍 获取前 100 个精选的 LLM 面试问题&#xff0c;了解如何准备生成式 AI 或 LLM 面试准备和大型语言模型 &#xff08;LLM&#xff09; 面试准备的学习路径。 This article explains learning path for large language models (LLMs) interview preparation. You will fin…

陪诊小程序之uniapp(从入门到精通)

1.uniapp如何使用vue3编写页面 <template><view class"content"><navbar name"navbar组件"></navbar><image class"logo" src"/static/logo.png"></image><view class"text-area"&…

C++进阶:AVL树实现

目录 一.AVL的概念 二.AVL的实现 2.1AVL树的结构 2.2AVL树的插入 2.2.1AVL树插入一个值的大概过程 2.2.2平衡因子更新 2.2.3插入节点及更新平衡因子的实现 2.3旋转 2.3.1旋转的原则 2.3.2右单旋 2.3.3右单旋的代码实现 2.3.4左单旋 2.3.5左单旋的代码实现 2.3.6…

vue综合指南(二)

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;Vue篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来Vuet篇专栏内容:vue综合指南(二) 目录 21、介绍虚拟DOM 22、vue生命周期的理解 23、vue父组件向子组件传递数据…

STM32_实验5_中断实验

通过外部中断来检测四个按键按下的状态&#xff1a; WK_UP 控制蜂鸣器响和停 KEY0 控制 LED_R 互斥点亮 KEY1 控制 LED_G 互斥点亮 KEY2 控制 LED_B 互斥点亮。 中断的基本概念&#xff1a; 中断请求&#xff08;IRQ&#xff09;&#xff1a; 当发生某个特定事件&#xff08;例…

【问题解决】——当出现0xc000007b和缺少mfc140.dll时,该怎么做才能让软件可以打开

目录 事情起因 问题处理 明确定义 填坑之路 最后我是怎么解决的&#xff08;不想看故事直接到这里&#xff09; 事情起因 最近想要重新安装西门子博途来做西门子的一些算法的时候&#xff0c;发现自己软件装的是V15.1的版本&#xff0c;而买的plc1200固件版本要求至少16以…

(AtCoder Beginner Contest 375)D - ABA

&#xff08;AtCoder Beginner Contest 375&#xff09;D - ABA 题目大意 给定一个只包含大写字母的字符串S&#xff0c;求解其长度为3的回文字序列个数 思路 首先暴力枚举区间计算答案 O ( ∣ S ∣ 2 ) O(|S|^2) O(∣S∣2) 一定是会超时的 我们考虑使用前缀和思想 我们对于…