libev实现分析

article/2025/10/8 13:56:09

libev实现分析

libev是一个事件驱动库,底层是基于select、epoll、kqueue等I/O复用接口。所谓事件驱动库,就是用户定义一个事件以及改事件发生时调用的函数,该库会监听该事件,并在事件发生时调用相应的函数。

libev提供了很多事件监听器(watcher),最主要的有IO、时间以及信号监听器。当某一个文件的读事件或者写事件发生时,周期时间到了时,进程接收到某个信号时,就会调用用户定义的回调函数。

下面以IO事件为例,讲述libev的工作原理:

1、实例

复制代码

 1 #include<stdio.h>2 #include <ev.h>3 // every watcher type has its own typedef'd struct4 // with the name ev_TYPE5 ev_io stdin_watcher;6 ev_timer timeout_watcher;7 8 // all watcher callbacks have a similar signature9 // this callback is called when data is readable on stdin
10 static void
11 stdin_cb (EV_P_ ev_io *w, int revents)
12 {
13   puts ("stdin ready OK!");
14   // for one-shot events, one must manually stop the watcher
15   // with its corresponding stop function.
16   ev_io_stop (EV_A_ w);
17 
18   // this causes all nested ev_run's to stop iterating
19   ev_break (EV_A_ EVBREAK_ALL);
20 }
21 
22 // another callback, this time for a time-out
23 static void
24 timeout_cb (EV_P_ ev_timer *w, int revents)
25 {
26    puts ("timeout");
27    // this causes the innermost ev_run to stop iterating
28    ev_break (EV_A_ EVBREAK_ONE);
29 }
30 
31 int
32 main (void)
33 {
34     // use the default event loop unless you have special needs
35     struct ev_loop *loop = EV_DEFAULT;
36     // initialise an io watcher, then start it
37     // this one will watch for stdin to become readable
38     ev_io_init (&stdin_watcher, stdin_cb, /*STDIN_FILENO*/ 0, EV_READ);
39     ev_io_start (loop, &stdin_watcher);
40     
41     // initialise a timer watcher, then start it
42     // simple non-repeating 5.5 second timeout
43     ev_timer_init (&timeout_watcher, timeout_cb, 5.5, 0.);
44     ev_timer_start (loop, &timeout_watcher);
45     
46     // now wait for events to arrive
47     ev_run (loop, 0);
48     //
49     // break was called, so exit
50     return 0;
51 }

复制代码

可以看出,libev库的使用简单、方便。我们只要定义个事件的监听器对象,初始化,开始,最后调用ev_run。不同事件监听器初始化的内容也不一样,比如,IO事件监听器需要初始化监听的文件描述符,事件以及回调函数。

2、事件监听器

复制代码

typedef ev_watcher *W;
typedef ev_watcher_list *WL;
typedef ev_watcher_time *WT;typedef struct ev_watcher
{int active; int pending;int priority;void *data;void (*cb)(EV_P_ struct type *w, int revents);
} ev_watcher;typedef struct ev_watcher_list
{int active; int pending;int priority;void *data;void (*cb)(EV_P_ struct ev_watcher_list *w, int revents);struct ev_watcher_list *next;
} ev_watcher_list;typedef struct ev_io
{int active; int pending;int priority;void *data;void (*cb)(EV_P_ struct ev_io *w, int revents);struct ev_watcher_list *next;int fd;     /* ro */int events; /* ro */
} ev_io;

复制代码

ev_watcher是一个基础监听器,包括回调函数cd;监听器列表(ev_watcher_list)是在监听器的基础上添加指向下一个监听器的指针next;IO监听器是在监听器列表的基础上加上了其特有的文件描述符和事件类型。
ev_io_init (&stdin_watcher, stdin_cb, /*STDIN_FILENO*/ 0, EV_READ);该函数就是初始化stdin_watcher这个监听器的回调函数,文件描述符,以及事假类型。
3、struct ev_loop

复制代码

struct ev_loop;
# define EV_P  struct ev_loop *loop
# define EV_P_ struct ev_loop *loop,
# define EV_A  loop
# define EV_A_ loop,  //这4个宏用于形参struct ev_loop{ //部分参数ANFD *anfdsint anfdmaxint *fdchangesint fdchangemaxint fdchangecntANPENDING *pendings [NUMPRI]int pendingmax [NUMPRI]int pendingcnt [NUMPRI]int backendint backend_fdvoid (*backend_modify)(EV_P_ int fd, int oev, int nev)void (*backend_poll)(EV_P_ ev_tstamp timeout)
}typedef struct
{WL head;           //ev_watcher_list *head;unsigned char events; /* the events watched for */unsigned char reify;  /* flag set when this ANFD needs reification (EV_ANFD_REIFY, EV__IOFDSET) */unsigned char emask;  /* the epoll backend stores the actual kernel mask in here */unsigned char unused;unsigned int egen;    /* generation counter to counter epoll bugs */SOCKET handle;OVERLAPPED or, ow;
} ANFD;typedef struct
{W w;       //ev_watcher *w;int events; /* the pending event set for the given watcher */
} ANPENDING;

复制代码

ev_run函数主要是一个while循环,在这个while循环中不断检测各个事件是否发生,如果发生就调用其回调函数。而这个过程中,主要用到的对象就是struct ev_loop结构体对象,检测哪些事件,回调哪个函数都存放在该对象中。
struct ev_loop结构体中的字段很多,以IO事件为例介绍几个主要的:
anfds是一个数组,数组元素是结构体ANFD,ANFD有一个成员是监听器列表。数组下标是文件描述符,而列表成员是监听该文件的事件监听器。所以,anfds有点类似散列表,以文件描述符作为键,以监听器作为值,采用开链法解决散列冲突。
该字段的初始化在ev_io_start函数中,主要目的是用户定义的监听器告诉ev_loop。
fdchanges是一个int数组,也是在ev_io_start中初始化。存放的是监听了的文件描述符。这样ev_run每次循环的时候,要先从fdchanges中取出已经监听的文件描述符,再以该描述符为下标,从anfds中取出监听器对象。这样就得到文件描述符以及监听
的事件。pendings是一个二维数组,第一维是优先级,第二维是监听器。这个数组是用于执行相应的回调函数,根据优先级,遍历所有监听器,调用监听器的回调函数。3、ev_io_start

复制代码

ev_io_start (EV_P_ ev_io *w) EV_THROW
{ev_start (EV_A_ (W)w, 1);   //w->active = active;array_needsize (ANFD, anfds, anfdmax, fd + 1, array_init_zero); //when fd + 1 > anfdmax : 重新分配数组大小//anfds = (type *)array_realloc(sizeof (ANFD), (anfds), &(anfdmax), (fd + 1));wlist_add (&anfds[fd].head, (WL)w);  // w->next = *head;*head = w;fd_change (EV_A_ fd, w->events & EV__IOFDSET | EV_ANFD_REIFY); //nfds [fd].reify |= flags;//++fdchangecnt;//array_needsize (int, fdchanges, fdchangemax, fdchangecnt, EMPTY2);//fdchanges [fdchangecnt - 1] = fd;
}array_realloc (int elem, void *base, int *cur, int cnt)
{*cur = array_nextsize (elem, *cur, cnt);return ev_realloc (base, elem * *cur);
}//分配当前数组大小的两倍内存,如果大于4096,则为4096的倍数
int array_nextsize (int elem, int cur, int cnt)
{int ncur = cur + 1;doncur <<= 1;while (cnt > ncur);/* if size is large, round to MALLOC_ROUND - 4 * longs to accommodate malloc overhead */if (elem * ncur > MALLOC_ROUND - sizeof (void *) * 4) //#define MALLOC_ROUND 4096{ncur *= elem;ncur = (ncur + elem + (MALLOC_ROUND - 1) + sizeof (void *) * 4) & ~(MALLOC_ROUND - 1);ncur = ncur - sizeof (void *) * 4;ncur /= elem;}return ncur;
}

复制代码

ev_io_start函数主要就是对ev_loop的anfds和fdchanges字段操作,上面已介绍。array_needsize函数实现当数组大小不够时,要重新分配内存,分配方式与stl::vector有些类似,都是新分配的内存为当前内存的2倍,然后移动原先数据到新内存,释放旧内存。

4、ev_run

复制代码

ev_run (EV_P_ int flags)
{...do{fd_reify (EV_A);backend_poll (EV_A_ waittime);if (expect_false (checkcnt))queue_events (EV_A_ (W *)checks, checkcnt, EV_CHECK);EV_INVOKE_PENDING;}while(...)...
}

复制代码

ev_run函数代码比较多,以上是以IO事件为例,进行的精简。下面是以epoll作为IO多路复用的机制进行ev_run说明
1、ev_loop对象的初始化:

复制代码

ev_loop对象初始化:
1、# define EV_DEFAULT  ev_default_loop (0)2、
static struct ev_loop default_loop_struct;
EV_API_DECL struct ev_loop *ev_default_loop_ptr = 0;ev_default_loop (unsigned int flags) EV_THROW
{if (!ev_default_loop_ptr){EV_P = ev_default_loop_ptr = &default_loop_struct;loop_init (EV_A_ flags);}return ev_default_loop_ptr;
}3、
static void noinline ecb_cold
loop_init (EV_P_ unsigned int flags) EV_THROW
{flags = atoi (getenv ("LIBEV_FLAGS"));
#if EV_USE_IOCPif (!backend && (flags & EVBACKEND_IOCP  )) backend = iocp_init   (EV_A_ flags);
#endif
#if EV_USE_PORTif (!backend && (flags & EVBACKEND_PORT  )) backend = port_init   (EV_A_ flags);
#endif
#if EV_USE_KQUEUEif (!backend && (flags & EVBACKEND_KQUEUE)) backend = kqueue_init (EV_A_ flags);
#endif
#if EV_USE_EPOLLif (!backend && (flags & EVBACKEND_EPOLL )) backend = epoll_init  (EV_A_ flags);
#endif
#if EV_USE_POLLif (!backend && (flags & EVBACKEND_POLL  )) backend = poll_init   (EV_A_ flags);
#endif
#if EV_USE_SELECTif (!backend && (flags & EVBACKEND_SELECT)) backend = select_init (EV_A_ flags);
}
EV_USE_EPOLL 、EV_USE_SELECT等宏是在调用./configure时,搜索sys/epoll.h sys/select.h等文件,如果文件存在,就将宏设置为1.
__cplusplus宏是g++编译器定义的4、
int inline_size
epoll_init (EV_P_ int flags)
{backend_fd = epoll_create (256);if (backend_fd < 0)return 0;fcntl (backend_fd, F_SETFD, FD_CLOEXEC);backend_mintime = 1e-3; /* epoll does sometimes return early, this is just to avoid the worst */backend_modify  = epoll_modify;backend_poll    = epoll_poll;epoll_eventmax = 64; /* initial number of events receivable per poll */epoll_events = (struct epoll_event *)ev_malloc (sizeof (struct epoll_event) * epoll_eventmax);return EVBACKEND_EPOLL;//即 EVBACKEND_EPOLL   = 0x00000004U
}
epoll_init返回值为非0,所以不会调用后面的poll_init、select_init。

复制代码

   所以,在初始化时,调用了epoll_create初始化backend_fd。

 2、fd_reify

复制代码

fd_reify (EV_P)
{for (i = 0; i < fdchangecnt; ++i){int fd = fdchanges [i];ANFD *anfd = anfds + fd;if (o_reify & EV__IOFDSET)backend_modify (EV_A_ fd, o_events, anfd->events); //即poll_modify}fdchangecnt = 0;
}
epoll_modify (EV_P_ int fd, int oev, int nev)
{struct epoll_event ev;ev.data.u64 = (uint64_t)(uint32_t)fd| ((uint64_t)(uint32_t)++anfds [fd].egen << 32);ev.events   = (nev & EV_READ  ? EPOLLIN  : 0)| (nev & EV_WRITE ? EPOLLOUT : 0);if (expect_true (!epoll_ctl (backend_fd, oev && oldmask != nev ? EPOLL_CTL_MOD : EPOLL_CTL_ADD, fd, &ev)))return;
}

复制代码

  在fd_reify中将andfs中监听的事件添加到backend_fd中。

  3、backend_poll

复制代码

backend_poll (EV_A_ waittime); // 即epoll_poll
epoll_poll (EV_P_ ev_tstamp timeout)
{eventcnt = epoll_wait (backend_fd, epoll_events, epoll_eventmax, timeout * 1e3);for (i = 0; i < eventcnt; ++i){struct epoll_event *ev = epoll_events + i;int fd = (uint32_t)ev->data.u64; /* mask out the lower 32 bits */int want = anfds [fd].events;int got  = (ev->events & (EPOLLOUT | EPOLLERR | EPOLLHUP) ? EV_WRITE : 0)| (ev->events & (EPOLLIN  | EPOLLERR | EPOLLHUP) ? EV_READ  : 0);fd_event (EV_A_ fd, got);  //将watcher设置到loop的pending数组中}
}
fd_event (EV_P_ int fd, int revents)
{ANFD *anfd = anfds + fd;if (expect_true (!anfd->reify))fd_event_nocheck (EV_A_ fd, revents);
}
fd_event_nocheck (EV_P_ int fd, int revents)
{ANFD *anfd = anfds + fd;ev_io *w;for (w = (ev_io *)anfd->head; w; w = (ev_io *)((WL)w)->next){int ev = w->events & revents;if (ev)ev_feed_event (EV_A_ (W)w, ev);}
}
ev_feed_event (EV_P_ void *w, int revents) EV_THROW
{W w_ = (W)w;int pri = ABSPRI (w_);if (expect_false (w_->pending))pendings [pri][w_->pending - 1].events |= revents;else{w_->pending = ++pendingcnt [pri];array_needsize (ANPENDING, pendings [pri], pendingmax [pri], w_->pending, EMPTY2);pendings [pri][w_->pending - 1].w      = w_;pendings [pri][w_->pending - 1].events = revents;}
}

复制代码

在backend_poll中会调用epoll_wait,通过fd_event函数,将就绪的文件描述符对应的监听器添加到ev_loop对象的pendings字段中

  4、EV_INVOKE_PENDING

复制代码

void noinline
ev_invoke_pending (EV_P)
{pendingpri = NUMPRI;while (pendingpri) /* pendingpri possibly gets modified in the inner loop */{--pendingpri;while (pendingcnt [pendingpri]){ANPENDING *p = pendings [pendingpri] + --pendingcnt [pendingpri];p->w->pending = 0;EV_CB_INVOKE (p->w, p->events);}}
}
# define EV_CB_INVOKE(watcher,revents) (watcher)->cb (EV_A_ (watcher), (revents))

复制代码

EV_INVOKE_PENDING会一次调用pendings中的监听器的回调函数。

至此,ev_run大体介绍完毕。

小结:

总的来说,对于IO事件驱动,libev是先将监听器存放在一个数组,每次遍历都将监听器监听的文件描述符添加到epoll_wait进行监听,然后将eopll_wait返回的就绪描述符对应的监听器添加到pendings,最后调用pendings中监听器的回调函数。


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

相关文章

libev源码解读

1、源码&#xff1a; 源码参见官网或者我的github上 2、安装使用&#xff1a; 安装与基本使用参见我的另一篇博客&#xff1a;传送门 3、架构 Libev通过一个 struct ev_loop结结构表示一个事件驱动的框架。在这个框架里面通过ev_xxx结构&#xff0c;ev_init、ev_xxx_set、…

libev库

libev库 概念数据结构watch集合watch结构 全局触发事件集合数据结构事件触发之IO事件定时器原理ev_run函数 概念 libev是一个轻量级的事件通知库&#xff0c;具备支持多种事件通知能力。 数据结构 在熟悉代码之前先了解其相关数据结构往往更加方便后续代码的阅读。 在libev中…

libev 源码解析

一 libev简介 libev是一个轻量级的事件通知库&#xff0c;具备支持多种事件通知能力&#xff0c;通过对libev的源码的阅读&#xff0c;可以清楚了解事件通知实现内部机制。 二 核心数据结构 在libev中关键的数据结构是&#xff0c;loop结构体&#xff0c;该结构体定义的字段较…

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

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

libev

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

libev:详解

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

事件驱动库 libev 使用详解

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

数据库和实例

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

【达梦数据库实例创建】

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

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 实例启动与关闭启动实例与数据库管理员登录启动实例加载数据库打开数据库只读模式数据库文件检查 关闭数据库与实例关闭模式关闭数据库正常关闭异常关闭 …