libev 源码解析

article/2025/10/8 15:05:25

一  libev简介

  libev是一个轻量级的事件通知库,具备支持多种事件通知能力,通过对libev的源码的阅读,可以清楚了解事件通知实现内部机制。

二 核心数据结构

 在libev中关键的数据结构是,loop结构体,该结构体定义的字段较多,但是主要核心的可以分为两大类

ev_loop结构体(loop为ev_loop结构的全局变量)的字段定义在ev_vars.h头文件中,然后在ev.c中通过include的方式导入

 

1.各类事件的watcher集合

 loop中有支持很多类型的事件,如下

ev_io                 // IO可读可写
ev_stat               // 文件属性变化
ev_signal             // 信号处理
ev_timer              // 相对定时器
ev_periodic           // 绝对定时器
ev_child              // 子进程状态变化
ev_fork               // fork事件
ev_cleanup            // event loop退出触发事件
ev_idle               // event loop空闲触发事件
ev_embed              // 嵌入另一个后台循环
ev_prepare            // event loop之前事件
ev_check              // event loop之后事件
ev_async              // 线程间异步事件

这些事件的监控管理都对应一种类型的watcher数组,比如

    (loop)->anfds :维护所有fd事件

    (loop)->timers :维护所有的定时器

    (loop)->periodics:周期性事件

     (loop)->prepares:该事件是loop启动之间就会执行的事件

等,每类事件都能在loop结构中找到对应的数组来维护对应的watchers。

2.2 watcher结构

    对于不同类型的事件的watcher,采用继承的方式来实现各个类型的watcher,libev是使用c的宏定义来实现继承(宏的奇技淫巧在libev中随处可见,这也导致libev看起来比较晦涩)

 这个是公共watcher得到结构,是会被所有的子watcher所共享。

  active:表示在loop中对应的watcher数组中的下标

  pending & EV_DECL_PRIORITY:分别用来记录在全局loop->pendings中列 & 行下标(loop->pendings下文会介绍)

  EV_CB_DECLARE (type) :回调函数入口

 比如io事件的watcher如上定义,类似的各种watcher都采取此类方式来生成(这样的好处是可以减少代码的重复度,降低了代码维护的成本,但是可读性方面也相应降低)

2.全局触发事件集合loop->pendings

loop->pendings记录管理当前已经发生等待调用回调函数的watcher集合,libev检查到事件发生后将对应的watcher加入到loop->pendings

 2.1 数据结构:

  *Watcher[N][M]:二位数组用来维护一轮循环下来,需要触发回调函数的watcher

  其中N:是每一类watcher的优先级

 另外还有loop->pendingcnt结构,也是一个二维数组,用来维护pending中每一行最大watcher下标。

 以下是事件函数回调过程的代码,

本质上就是个遍历loop->pendings挨个调用watcher的注册的回调函数,可以看到按照优先级从高(小)到低(大),对于同一优先级watcher按照下标从大到小的方式来调用callBack函数。

 值得注意的,就是pendingcnt结构,该结构维护当前每一行watcher数组当前的最大下标。

三 事件触发之io事件

  该节将会以IO事件在EPOLL平台的触发整个流程来阐述libev是如何来实现事件触发回调的整个过程。在介绍IO事件触发之前需要介绍一下相关的数据结构

       ANFD:用来记录对一个fd的所有监听事件,每一个监听事件使用链表结构进行组织。

  

  anfds:是ANFD*类型的数组,用来管理每一个fd的监听的事件

  changfds:是int*类型数组,每个元素记录当前发生更改的fd,比如加入新的监听fd,或者fd的监听事件发送修改。

在每次循环之前,都会对changefds和anfds的结构中对应的fd事件列表的所有event进行 | 操作,得到当前整个fd当前的监听事件和ANFD.events进行对比,如果没有修改将不会该表epoll的fd的监听事件,这样做的好处,避免了无效的修改,保证了所有对epoll的修改都是必须的,毕竟频繁对epoll进行修改代价还是挺大的。

    好了,下面正式分析IO事件是如何触发的,当一个fd监听事件加入时

 step1:调用ev_io_start将watcher加入到anfds中,并将修改记录在changfds中

  steps2: 调用fd_reify,通过比对changfds & anfds确定是否需要加入epoll事件,此时显然是需要的

    

 

    核心代码如下,第一处:暂时保存fd的events,第二处:通过遍历watcher链表计算新的events,比较前后是否发送变化,第三处:如果发生变化将修改epoll的监听事件

    自此完成监听事件的添加。

  step3:调用backend_poll函数得到当前监听已经发生的事件,

    #得到事件的fd & 已经发生的events,从anfds中获取对应的ANFD

        #遍历ANFD中的watcher链表,比对监听事件和已经发生的监听事件,如果符合将该watcher加入到loop->pendings,修改watcher中的pending变量标记在loop->pendings中的数组下标

  step4:遍历循环loop->pendings结构,挨个调用回调函数,从而完成事件触发的一个完整过程

四 事件触发之定时器事件

       定时器采用小根堆的方式来维护所有的timer,libev整个过程是采用loop循环的方式,周期性的检测是否有事件发生

在loop中会获取当前堆顶的timer(最近要发生的)以及其他信息来计算当前可以sleep多长时间,从而可以保证进程在休眠的这段时间也不会有事件发生而没有及时通知。

五 ev_run函数解析

 ev_run将整个libev的各类事件通知流程串起。整个过程就是个大循环。

    过程大致分为

           1.检测是否有fork事件,如果有进行fork事件的回调函数

           2.在loop之前调用prepare事件的回调函数。

           3.检测fd的监听事件是否发生变化,是否需要修改epoll的监听事件

           4.计算需要休眠的事件

     #根据定时器 & 周期任务 & timeout_blocktime(超时事件收集间隔事件) & io_blocktime(io事件收集间隔事件) 等信息计算此次循环需要sleep的时间

           5.如果需要休眠则进行休眠

           6.进程从休眠态唤起后,从epoll(pool,kqueue)中获取发生的事件,将对应的watcher加入到 loop->pendings中

           7.将定时时间到了的定时器,加入loop->pendings中

          8.收集周期任务,加入loop->pendings中

          9.收集空闲事件加入loop->pendings中

   10.依此对loop->pendings中的watcher中注册的回调函数

   自此整个loop完成,总体来说就是在整个loop中检测所有的监听的事件是否发生,然后依次对发生的事件,调用注册的回调函数。

六 源码文件结构

  #个平台网络编程接口,不同平台使用不同的文件,从而支持多平台

   ev_pool.c

     ev_port.c

     ev_kqueue.c

     ev_select.c

   ev_vars.h:定义ev_loop数据结构,使用宏定义的方式进行定义

   ev_warp.c:,使用宏定义的方式封装全局变量loop中字段的访问

   ev.c:整个libev的核心部分,实现了整个libev的事件通知的大部分业务逻辑

七 总结

  libev从整个设计来看还是比较精巧的,大体上将整个事件通知机制划分为两个阶段

  #事件发生检测:

    各个事件检测过程实现不太一样(IO事件,定时器,周期性任务等各不一样),将检测到发生的事件加入loop->pendings中

  #事件回调触发

    对loop->pendings的事件,遍历依次触发回调函数

  整个libev可能作者出于对代码复用减少重复代码的原因,大量使用宏定义,甚至用宏定义实现了简单的继承关系,这也使得整个项目代码看起来比较晦涩难懂。


http://chatgpt.dhexx.cn/article/GOBKku9u.shtml

相关文章

android libev 编译,移植libev事件库到Android中

因为libev库是使用C语言写的,所以在Android项目中使用此库的方法是把libev编译成.so文件,在Android中使用jni方式来调用libev的.so文件。 我们大家都知道android的ndk开发可以编译c,c代码,不过需要自己写Android.mk文件。但是对于大多数开源项…

libev

开始之前先看一下libevent libev libuv,参考附录1. 本着我自己的个性,我喜欢简单的东西,越简单越好,越傻越好,所以在此我考虑libev,只是tmd,libev的官网打不开,真是无语了。 上例子…

libev:详解

事件库之Libev(一) 使用Libev Libev的作者写了一份很好的官方Manual,比较的齐全,即介绍了Libev的设计思想,也介绍了基本使用还包括内部各类事件详细介绍。这里略微赘述一下。Libev通过一个 struct ev_loop 结结构表示一个事件驱动…

事件驱动库 libev 使用详解

C/CLinux服务器开发/后台架构师知识体系 libev 是一个通过 C 语言编写的,高性能的事件循环库,支持多种事件类型,与此类似的事件循环库还有 libevent、libubox 等,在此详细介绍下 libev 相关的内容。 简介 这是一个简单而且高性…

数据库和实例

这篇文章跟数据库开发有什么关系呢?我感觉呢只从字面上看确实没有什么关系,可是了解的话跟大牛讨论时他们最起码不会被鄙视、面试时可能也会有用。如果你再深入的钻研下去你就会发现里面的内容好“丰满”。作为一个菜鸟(指本人)以…

【达梦数据库实例创建】

达梦数据库实例创建 系统环境:银河麒麟V10 数据库版本:dm8 一、创建数据库实例 1.通过命令切换到路径/dm8/tool 2.执行命令进入配置助手 ./dbca.sh 选择一般用途,自动调整数据库性能参数,进行下一步操作: 设置数据…

SQL 经典实例

《SQL经典实例》 1&#xff0c;在WHERE子句中引用别名列 直接引用-报错 把查询包装为一个内嵌视图&#xff0c;这样就可以引用别名列了 select * from ( select sal as salary, comm as commission from emp ) x where salary < 5000同理&#xff1a;当你想在 WHERE 子句中…

SQL示例数据库

在本教程中&#xff0c;我们将向您介绍在整个教程中使用的SQL示例数据库。以下数据库关系图 - HR 示例数据库&#xff1a; 在这个HR 示例数据库有7个表&#xff1a; employees表存储员工的数据信息。 jobs表存储工作数据信息&#xff0c;包括职位和工资范围。 departments表…

数据库案例

目录 微信朋友圈设计 用户及用户关系 发朋友圈 CDN 发布表 相册表 时间线 刷朋友圈 删除、拉黑、标签、不让他看、三天可见该怎么办 谁可以看 标签 第二步的权限控制 评论和赞 微信朋友圈设计 用户及用户关系 肯定有用户表作为基础 用户关系表&#xff0c;用户…

SQL数据库编写及示例

一、 数据库编写 1、数据库常用约束 主键约束: primary key 外键约束: foreign key (references) 唯一值约束: unique 默认值约束: default 检查约束: check 非空约束: not null 标识列: identity 2、创建数据表注意事项 主外键数据类型必须一致 列与列之间用&#xff0c;间隔…

sqlserver:什么是数据库实例?

环境&#xff1a; window server 2019 datacentersqlserver2014 x64 问题&#xff1a; 什么是SQL server实例&#xff1f;数据库的对象架构是怎样设计的&#xff1f; 先把官方的解释贴出来&#xff1a; https://docs.microsoft.com/zh-cn/sql/relational-databases/databases…

【转】数据库设计实例一学习

​​​​​​数据库1​​​​​​​​​​​​​​​​​​​​​对多&#xff0c;1对1&#xff0c;M对N学习 以RBAC为例。​​​​​​​​​​​​​​ 于 RBAC&#xff08;Role-based Access Control&#xff09;权限访问控制。也就是说一个用户可以有多个角色&#xff…

sql server 数据库设计实例

本实例为综合实例,考察数据库原理中的,sql脚本的编写,创建——增删改查,视图和索引的创建等;数据库ER图,关系模式;以及一些高级的应用包括:触发器,函数和存储过程。 (一). 数据库设计题目如下 有一个图书出版发行管理系统,其主要业务规则如下: 一个作者可以编写多…

达梦数据库创建及数据库实例管理

一、配置助手创建和删除数据库 数据库配置助手创建数据库调用 dbca.sh 图形化界面创建数据库&#xff1a;[dmdbaDCA02 tool]$ ./dbca.sh2021-01-11 11:43:45 [com.dameng.dbca.Startup] [INFO] 启动 DBCA 指定数据库名称、实例名称&#xff08;单机情况下数据库和实例名称可以…

MySql 数据库操作实例

MySql 数据库操作实例 案例描述创建插入数据内外连接~问题问题1&#xff1a;查询周星星的成绩问题2&#xff1a;查询所有人的平均成绩以及其他信息1&#xff09;查询所有人的平均成绩2&#xff09;查询平均成绩最高的前三名3&#xff09;查询平均成绩排名第三的学生信息 问题3&…

数据库五个经典实例

创建数据库链接&#xff0c;需要用到connection对象&#xff0c;recordset对象。 对数据库进行操作&#xff0c;需要用到command对象&#xff0c;parameter对象。这两个对象成对出现。 connection对象&#xff1a;创建数据库链接。在对数据库进行操作的前提步骤。 recordset…

Oracle 数据库实例介绍

文章目录 数据库实例介绍实例结构实例配置读写实例与只读实例实例生命周期实例标识Oracle 根目录Oracle 主目录Oracle SID 实例启动与关闭启动实例与数据库管理员登录启动实例加载数据库打开数据库只读模式数据库文件检查 关闭数据库与实例关闭模式关闭数据库正常关闭异常关闭 …

Python:二叉树遍历

二叉树遍历共有四种方法&#xff0c;分别是前序遍历、中序遍历、后序遍历和层次遍历。 前序遍历&#xff1a; 父节点——左孩子——右孩子 中序遍历&#xff1a;左孩子——父节点——右孩子 后序遍历&#xff1a;左孩子——右孩子——父节点 层次遍历&#xff1a;利用队列解…

【算法】二叉树遍历的几种常见方法

二叉树遍历的几种常见方法 一. 二叉树分类&#xff1a; 完全二叉树满二叉树扩充二叉树平衡二叉树 二. 二叉树的四种遍历方式&#xff1a; 前序遍历&#xff08;先根&#xff0c;再左&#xff0c;最后右&#xff09;中序遍历&#xff08;先左&#xff0c;再根&#xff0c;最…

二叉树遍历的非递归算法

非递归的算法主要采用的是循环出栈入栈来实现对二叉树的遍历&#xff0c;下面是过程分析 以下列二叉树为例&#xff1a;&#xff08;图片来自懒猫老师《数据结构》课程相关内容&#xff09; 1.前序遍历 前序遍历的顺序为&#xff1a;根结点->左子树->右子树 基本过程&a…