【Linux】浅谈eloop机制

目录

1.eloop 机制

2.eloop结构体

2.1.eloop_data结构体

2.2 Socket事件结构体

2.3 Timeout事件结构体

2.4 Signal事件结构体

3.eloop_init

4.eloop_run

4.1 signal事件

4.2 socket事件

4.3 timeout事件


1.eloop 机制


        主线程中启动事件监听机制,对不同的事件响应不同的处理函数。即所有的操作都是基于事件驱动的。事件驱动和消息驱动类似,主线程运行一个 event loop,等待事件的发生并处理它们。

2.eloop结构体

2.1.eloop_data结构体

 eloop.c中主要核心结构体为eloop_data,以下是eloop_data结构体:

eloop主要处理三大类型的Event事件:Socket事件,Timeout事件,Signal事件:

2.2 Socket事件结构体

      Socket事件:有 readers,writers,exceptions 三个 eloop_sock_table 结构体,
每个里面都有一个 eloop_sock 类型的指针table,这里可以将该指针变量理解成动态数组,
可以向各个table里面添加、删除 eloop_sock,
事件处理就是遍历 eloop_sock_table,依次运行里面的每个handler。

2.3 Timeout事件结构体

        Timeout事件:每个 struct eloop_timeout 都被放在一个双向链表中dl_list中,
链表头就是 eloop_data 中的“timeout”项。这些struct eloop_timeout按超时先后排序。

2.4 Signal事件结构体

        Signal事件:每个 struct eloop_signal 都通过 eloop_signal 类型的指针链接起来,下面是eloop_signal结构体:

3.eloop_init

  eloop为静态全局变量:

  eloop初始化函数为eloop_init,如下: 

  1.  通过os_memset将eloop结构体清0;
  2. 通过dl_list_init初始化双向链表prev和next指向eloop.timeout;
  3. 初始化eaders,writers,exceptionsSocket事件类型为EVENT_TYPE_READ、EVENT_TYPE_WRITE、EVENT_TYPE_EXCEPTION。

4.eloop_run

         下面结合eloop_run函数来分析eloop机制,部分内容摘自链接: wpa_supplicant之eloop_run分析

先看eloop_run源码:

void eloop_run(void)
{
	fd_set *rfds, *wfds, *efds;
	struct timeval _tv;
	int res;
	struct os_reltime tv, now;

	rfds = os_malloc(sizeof(*rfds));
	wfds = os_malloc(sizeof(*wfds));
	efds = os_malloc(sizeof(*efds));
	if (rfds == NULL || wfds == NULL || efds == NULL)
		goto out;

	while (!eloop.terminate &&
	       (!dl_list_empty(&eloop.timeout) || eloop.readers.count > 0 ||
		eloop.writers.count > 0 || eloop.exceptions.count > 0)) {
		struct eloop_timeout *timeout;

		if (eloop.pending_terminate) {
			/*
			 * This may happen in some corner cases where a signal
			 * is received during a blocking operation. We need to
			 * process the pending signals and exit if requested to
			 * avoid hitting the SIGALRM limit if the blocking
			 * operation took more than two seconds.
			 */
			eloop_process_pending_signals();
			if (eloop.terminate)
				break;
		}

		timeout = dl_list_first(&eloop.timeout, struct eloop_timeout,
					list);
		if (timeout) {
			os_get_reltime(&now);
			if (os_reltime_before(&now, &timeout->time))
				os_reltime_sub(&timeout->time, &now, &tv);
			else
				tv.sec = tv.usec = 0;
			_tv.tv_sec = tv.sec;
			_tv.tv_usec = tv.usec;
		}

		eloop_sock_table_set_fds(&eloop.readers, rfds);
		eloop_sock_table_set_fds(&eloop.writers, wfds);
		eloop_sock_table_set_fds(&eloop.exceptions, efds);
		res = select(eloop.max_sock + 1, rfds, wfds, efds,
			     timeout ? &_tv : NULL);
		if (res < 0 && errno != EINTR && errno != 0) {
			wpa_printf(MSG_ERROR, "eloop: %s: %s",
				   "select"

				   , strerror(errno));
			goto out;
		}

		eloop.readers.changed = 0;
		eloop.writers.changed = 0;
		eloop.exceptions.changed = 0;

		eloop_process_pending_signals();

		/* check if some registered timeouts have occurred */
		timeout = dl_list_first(&eloop.timeout, struct eloop_timeout,
					list);
		if (timeout) {
			os_get_reltime(&now);
			if (!os_reltime_before(&now, &timeout->time)) {
				void *eloop_data = timeout->eloop_data;
				void *user_data = timeout->user_data;
				eloop_timeout_handler handler =
					timeout->handler;
				eloop_remove_timeout(timeout);
				handler(eloop_data, user_data);
			}

		}

		if (res <= 0)
			continue;

		if (eloop.readers.changed ||
		    eloop.writers.changed ||
		    eloop.exceptions.changed) {
			 /*
			  * Sockets may have been closed and reopened with the
			  * same FD in the signal or timeout handlers, so we
			  * must skip the previous results and check again
			  * whether any of the currently registered sockets have
			  * events.
			  */
			continue;
		}

		eloop_sock_table_dispatch(&eloop.readers, rfds);
		eloop_sock_table_dispatch(&eloop.writers, wfds);
		eloop_sock_table_dispatch(&eloop.exceptions, efds);
	}

	eloop.terminate = 0;
out:
	os_free(rfds);
	os_free(wfds);
	os_free(efds);
	return;
}
eloop_run方法中的“死循环”:
	while (!eloop.terminate &&
	       (!dl_list_empty(&eloop.timeout) || eloop.readers.count > 0 ||
		eloop.writers.count > 0 || eloop.exceptions.count > 0))

如果eloop.terminate变为非零值,就会退出循环。这是为了提供一种从外部结束循环的方法。例如 void eloop_terminate(void) 方法。
如果eloop.terminate为零,只要timeout链表或者任一个Socket不为空,都会继续循环。

select系统调用的原型是:

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

关于select()函数的相关介绍,可见 select()函数用法

简单来讲:select函数可以同时监视多个文件描述符,并且可以监视三种事件。一旦某个文件描述符所指的对象发生了相应事件,就可以进行相应的处理。

在循环体里面:

(1)找到timeout链表的第一项(因为是按超时先后排序的,所以第一项肯定是最先超时的),计算超时时间距现在还有多久,并据此设置select的timeout参数。

(2)设置rfds,wfds和efds三个fd_set:方法是遍历各个eloop_sock_table,把每个sock描述符加入相应的fd_set里面。

(3)调用select。可能阻塞在此处。最大等待时间取决于“最快会发生的一次”timeout时间 。

(4)eloop_process_pending_signals(); 处理Signal事件。

(5)判断是否有timeout事件“超时”发生,如果是则调用其handler,并从timeout链表移除。然后继续下次循环。

(6)如果不是超时事件,则应该是rfds, wfds或者efds事件,fd_set里面会被改变,存放发生事件的描述符。因此分别遍历三个sock_table,如果其描述符在fd_set里面则调用其handler方法。

(7)继续下次循环。

我按循环中处理事件类型的先后顺序来分析
 

4.1 signal事件

 int eloop_register_signal(int sig, eloop_signal_handler handler,
 			  void *user_data)
 {
 	struct eloop_signal *tmp;
 
 	tmp = os_realloc_array(eloop.signals, eloop.signal_count + 1,
 			       sizeof(struct eloop_signal));
 	if (tmp == NULL)
 		return -1;

	tmp[eloop.signal_count].sig = sig;
	tmp[eloop.signal_count].user_data = user_data;
	tmp[eloop.signal_count].handler = handler;
	tmp[eloop.signal_count].signaled = 0;
	eloop.signal_count++;
	eloop.signals = tmp;
	signal(sig, eloop_handle_signal);

	return 0;
}

        别处调用 eloop_register_signal() 函数注册 signal 时,对应的 eloop_handle_signal 不会立即执行(初注册时将其 signaled置0)
        只有当 signal 发生时才会将此 signal 的 signaled加1,表示此类 signal 已收到并处于 pending 状态(如何做到的呢?见 signal(sig, eloop_handle_signal); eloop_handle_signal 会将此 signal 的 signaled加1且eloop_data的signaled加1)
       signal()函数用法见 signal()用法

      在select()超时或者有Socket事件方式时才会顺便调用eloop_process_pending_signals(), 对每个处于Pending状态的struct eloop_signal调用其handler。

4.2 socket事件


        在循环期间,外部调用 eloop_sock_table_add_sock() 函数往 eloop_sock_table 中添加 eloop_sock
eloop_sock_table_set_fds() 函数将 rfds/wfds/efds 对应 sock位用 FD_SET 置1
而后调用 select() 函数

        select()返回值:发生错误时返回-1,超时时返回0。如果发生监视的事件,返回相应的文件描述符。
        如果 select() 返回结果大于0,说明有socket事件发生,则调用 eloop_sock_table_dispatch() 依次处理三个 table(遍历三个table中的eloop_sock,调用其handler)

4.3 timeout事件

        eloop_date中维护一个 timeout 链表,按超时先后排序,依次处理

读者可自行把 eloop.c 和 eloop.h 中的一些 tool function读完

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深度学习模型压缩与优化加速

1. 简介 深度学习&#xff08;Deep Learning&#xff09;因其计算复杂度或参数冗余&#xff0c;在一些场景和设备上限制了相应的模型部署&#xff0c;需要借助模型压缩、系统优化加速、异构计算等方法突破瓶颈&#xff0c;即分别在算法模型、计算图或算子优化以及硬件加速等层…

如何优雅地停掉线程?

很久很久以前&#xff0c;在一个名为“Springboot”的村庄中&#xff0c;住着一群热爱编程的程序员。他们喜欢探索新技术、优化自己的代码&#xff0c;为了打造更好的软件而不断努力着。 在这个村庄中&#xff0c;有一个名叫小明的程序员&#xff0c;他是村庄中最优秀的程序员…

一文打通java中内存泄露

目录 前置知识 内存泄漏&#xff08;memory leak&#xff09; 内存溢出&#xff08;out of memory&#xff09; Java中内存泄露的8种情况 静态集合类 单例模式 内部类持有外部类 各种连接&#xff0c;如数据库连接、网络连接和IO连接等 变量不合理的作用域 改变哈希值 …

第二十八章 React脚手架配置代理

为了更好地理解如何在React应用程序中配置代理&#xff0c;我们需要先了解什么是代理。 代理是一种充当客户端和服务器之间中间人的服务器。当客户端向服务器发送请求时&#xff0c;代理服务器将接收请求并将其转发到服务器。服务器将响应发送回代理服务器&#xff0c;代理服务…

机器视觉工程师职场四点“心态>交流=思路>知行合一”

视觉人机器视觉团队,他们热爱机器视觉行业,爱学习,爱分享。这一路上,首先感谢粉丝们805天一如既往的支持。我想团队拥有这些粉丝,是富有的,也是我们一直创作的动力。 是否记得毕业季,自己的豪言壮语。希望你毕业三年后,无论结果如何,不忘初心,继续前行。 机器视觉工程…

Flutter 中使用 Widgetbook 管理你的组件

Flutter 中使用 Widgetbook 管理你的组件 前言 Flutter 界面开发中我们有几个痛点 &#xff1a; 与设计师协作复用一套设计规范&#xff08;figma&#xff09; 可视化的管理你的组件代码&#xff08;基础组件、业务组件&#xff09; 不同设备尺寸测试你的组件 实时修改你的测试…

python并发编程:什么是并发编程?python对并发编程有哪些支持?

Python并发编程是指同时执行多个任务的编程模式。Python提供了多种实现并发编程的方式&#xff0c;包括多线程、多进程、协程、异步IO等。 为什么要引入并发编程 假设以下两个场景&#xff1a; 场景一: 一个网络爬虫&#xff0c;按顺序爬取花了一个小时&#xff0c;采用并发…

spring-模型数据和视图---视图解析器的说明以及大量代码演示

目录 spring-模型数据 ● 说明 应用实例需求 创建后面所有代码执行成功之后跳转的vote_ok.jsp页面 方式 1: 通过 HttpServletRequest放入 request 域 创建 Master类 创建Pet类 创建model_data.jsp 修改 VoteHandler增加方法 创建vote_ok.jsp, 显示数据 完成测试(Post…

[LeetCode周赛复盘] 第 103 场双周赛20230429

[LeetCode周赛复盘] 第 103 场双周赛20230429 一、本周周赛总结2656. K 个元素的最大和1. 题目描述2. 思路分析3. 代码实现 2657. 找到两个数组的前缀公共数组1. 题目描述2. 思路分析3. 代码实现 2658. 网格图中鱼的最大数目1. 题目描述2. 思路分析3. 代码实现 2659. 将数组清…

Docker consul

目录 一、Docker consul的容器服务和发现 ①服务注册与发现的含义 ②什么是consul 二、服务部署 ①部署consul服务 &#xff08;1&#xff09;查看集群信息 &#xff08;2&#xff09;通过http api获取集群信息 ②部署registrator服务器 &#xff08;1&#xff09;安装…

计算机视觉毕业后找不到工作怎么办?怒刷leetcode,还是另寻他路?

文章目录 一、计算机视觉毕业后找不到工作怎么办&#xff1f;二、大环境&#xff1a;前两年的泡沫太大三、还是要把自己的基本功搞扎实&#xff0c;真正的人才什么时候都紧缺四、转换思路&#xff0c;另投他坑五、要有毅力&#xff0c;心态放平六、最后的建议 一、计算机视觉毕…

应急加固初试(windows sever 2008)

前言 红中(hong_zh0) CSDN内容合伙人、2023年新星计划web安全方向导师、 华为MindSpore截至目前最年轻的优秀开发者、IK&N战队队长、 吉林师范大学网安大一的一名普通学生、搞网安论文拿了回大挑校二、 阿里云专家博主、华为网络安全云享专家、腾讯云自媒体分享计划博主 …

【服务器】威联通NAS文件共享 - 搭建SFTP服务并内网穿透实现在外远程访问

Yan-英杰的主页 悟已往之不谏 知来者之可追 C程序员&#xff0c;2024届电子信息研究生 目录 前言 1. 威联通NAS启用SFTP 2. 测试局域网访问 3. 内网穿透 3.1 威联通安装cpolar内网穿透 3.2 创建隧道 3.3 测试公网远程访问 4. 配置固定公网TCP端口地址 4.1 保留一个固定TCP…

chatGPT+Midjourney制作绘画本

chatGPTMidjourney制作绘画本 灵感来源&#xff1a;https://www.bilibili.com/video/BV1N24y1F7ga/?spm_id_from888.80997.embed_other.whitelist&vd_source6dd97671c42eb7cf111063714216bd0b 最终效果&#xff1a; 绘本故事 故事塑造能力弱的人可以使用chatGPT来帮助编…

JAVAWeb11-服务器渲染技术 -JSP-01-JSP基础

1. 现状 1、JSP 使用情况 2、Thymeleaf 使用情况, 通常和 SpringBoot 结合(也会讲) 3、Vue 使用情况 2. 学 JSP 前&#xff0c;老师要说的几句话 目前主流的技术是 前后端分离 (比如: Spring Boot Vue/React), 我们会讲的.[看一下]JSP 技术使用在逐渐减少&#xff…

C. Maximum Subrectangle(思维 + 考察两个数组相乘得到的矩阵的含义)

Problem - C - Codeforces 给定两个正整数数组 a 和 b&#xff0c;长度分别为 n 和 m。 定义矩阵 c 为一个 nm 的矩阵&#xff0c;其中 ci,jai⋅bj。 你需要在矩阵 c 中找到一个子矩形&#xff0c;使得它的元素之和最多为 x&#xff0c;并且它的面积&#xff08;即元素总数&a…

【Redis】Redis分布式锁的10个坑

文章目录 前言1. 非原子操作&#xff08;setnx expire&#xff09;2.被别的客户端请求覆盖&#xff08; setnx value为过期时间&#xff09;3. 忘记设置过期时间4. 业务处理完&#xff0c;忘记释放锁5. B的锁被A给释放了6. 释放锁时&#xff0c;不是原子性7. 锁过期释放&…

【Linux内核解析-linux-5.14.10-内核源码注释】MM内存管理内核启动初始化源码解析

源码 这是Linux内核中的mm_init函数的代码&#xff0c;其作用是初始化内存管理相关的组件和数据结构。 static: 这是一个函数声明修饰符&#xff0c;表示该函数只在当前文件中可见。 void __init: 这是函数的返回类型和修饰符&#xff0c;表示该函数是内核初始化代码。 page…

Redis缓存(双写一致性问题)

Redis缓存&#xff08;双写一致性问题&#xff09; 1 什么是缓存?1.1 为什么要使用缓存1.2 如何使用缓存 2 添加缓存2.1 、缓存模型和思路2.2、代码如下 3 缓存更新策略3.1 、数据库缓存不一致解决方案&#xff1a;3.2 、数据库和缓存不一致采用什么方案 4 实现商铺和缓存与数…

( 字符串) 647. 回文子串 ——【Leetcode每日一题】

❓647. 回文子串 难度&#xff1a;中等 给你一个字符串 s &#xff0c;请你统计并返回这个字符串中 回文子串 的数目。 回文字符串 是正着读和倒过来读一样的字符串。 子字符串 是字符串中的由连续字符组成的一个序列。 具有不同开始位置或结束位置的子串&#xff0c;即使…