【Linux】浅谈eloop机制

news/2024/12/5 1:17:25/

目录

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);elsetv.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读完。

可参考:

hostapd eloop初始化icon-default.png?t=N3I4https://www.cnblogs.com/xiaomingjun/p/16128774.html


http://www.ppmy.cn/news/62490.html

相关文章

C++11实现线程池

1.所有权的传递 适用移动语义可以将一个unique_lock赋值给另一个unique_lock,适用move实现。 void myThread1() {unique_lock<mutex> myUnique (testMutex1,std::defer_lock);unique_lock<mutex>myUnique1(std::move(myUnique));//myUnique 则实效 myUnique1 相当…

进程替换函数组介绍exec*

目录 前述 execl execlp execle execv execvp execvpe 前述 介绍后缀的意义&#xff1a; l &#xff08;list&#xff09;&#xff1a;表示参数采用列表。 v&#xff08;vector&#xff09;&#xff1a;参数同数组表示。 p&#xff08;path&#xff09;&#xff1a;自…

IP协议基础

文章目录 基本概念IP和TCP分别解决什么问题 以下过程都是在网络层完成的网段划分路由路由转发过程路由表 基本概念 主机: 配有IP地址, 但是不进行路由控制的设备。 路由器: 即配有IP地址, 又能进行路由控制。 节点: 主机和路由器的统称。 IP和TCP分别解决什么问题 TCP解决…

Acwing1293. 夏洛克和他的女朋友

夏洛克有了一个新女友&#xff08;这太不像他了&#xff01;&#xff09;。 情人节到了&#xff0c;他想送给女友一些珠宝当做礼物。 他买了 n 件珠宝&#xff0c;第 i件的价值是 i1&#xff0c;也就是说&#xff0c;珠宝的价值分别为 2,3,…,n1。 华生挑战夏洛克&#xff0…

算法DAY52 动态规划10 300.最长递增子序列 674. 最长连续递增序列 718. 最长重复子数组

300.最长递增子序列 五部曲&#xff1a; 1、dp数组的含义&#xff1a; dp[ i ] : 代表 截至到nums[i] (包括 nums[i]) 的序列中&#xff0c;以nums[i] 结尾的&#xff0c;最长递增子序列的长度。这里强调以nums[i] 结尾&#xff0c;是因为还要跟nums[j]做对比&#xff0c;确定…

106.(cesium篇)cesium椎体旋转

听老人家说:多看美女会长寿 地图之家总目录(订阅之前建议先查看该博客) 文章末尾处提供保证可运行完整代码包,运行如有问题,可“私信”博主。 效果如下所示: 下面献上完整代码,代码重要位置会做相应解释 <html lang="en"> <

互联网摸鱼日报(2023-05-08)

互联网摸鱼日报&#xff08;2023-05-08&#xff09; InfoQ 热门话题 数据库内核杂谈&#xff08;三十二&#xff09;- 杂谈五周年特别篇 比Python快35000倍&#xff01;LLVM&Swift之父宣布全新编程语言Mojo&#xff1a;编程被颠覆了 李彦宏回应文心一言与ChatGPT差距2个…

【Vue学习笔记4】基于Vue3的Composition API + <script setup>

继续前面的学习笔记。 1. 写一个累加器组件 在 src 下的 components 目录下新建一个 Counter.vue &#xff0c;并在这个文件里写出下面的代码&#xff1a; <template><div><h1 click"add">{{ count }}</h1></div> </template>…