libev源码解读

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

1、源码:
源码参见官网或者我的github上
2、安装使用:
安装与基本使用参见我的另一篇博客:传送门
3、架构
Libev通过一个 struct ev_loop结结构表示一个事件驱动的框架。在这个框架里面通过ev_xxx结构,ev_init、ev_xxx_set、ev_xxx_start接口向这个事件驱动的框架里面注册事件监控器,当相应的事件监控器的事件出现时,便会触发该事件监控器的处理逻辑,去处理该事件。处理完之后,这些监控器进入到下一轮的监控中。符合一个标准的事件驱动状态的模型。
4、源码
对于libev这样组件类的网络库阅读源码,我是按照以下的顺序:
1.官方文档2.看api写几个示例程序3.浏览代码,搞清楚结构组织4.根据API中的接口去源码中看看是怎么实现的,主干是什么5.把源码中demo没有的功能自己测试使用下

libev库是用c代码编写的,共有八千行左右,下面分析每个文件
ev.c:是libev的主要逻辑,大概有五千行,
ev_select.c:包括ev_select.c在内的这几个都是对系统提供的IO复用机制的支持这里写图片描述
ev.h:是对一些接口api和变量、常量的定义
ev_wrap.h:各种宏定义,对各种变量的引用的掩盖、封装
ev.vars.h:结构的成员变量定义所有的这些代码大致都是分为3个部分,1、一连串的宏定义,根据环境定义哪些状态变量2、针对不同的硬件平台及编译器的跨平台实现3、程序的主要逻辑
如图这里写图片描述
所以我们可以直接看代码结构部分即可,具体分析如下:

首先我们来看ev.h,其中的对于每一项事件都有一个结构体/类的定义,先看一下基类watcher是怎么定义的(都在ev.h中):

/* base class, nothing to see here unless you subclass */
typedef struct ev_watcher
{EV_WATCHER (ev_watcher)
} ev_watcher;

其中宏定义EV_WATCHER (ev_watcher)定义如下:

/* **shared by all watchers** */
#define EV_WATCHER(type)            \int active; /* private */         \int pending; /* private */            \EV_DECL_PRIORITY /* private */        \EV_COMMON /* rw */                \EV_CB_DECLARE (type) /* private */

实际上可以看做如下:

typedef struct ev_watcher
{int active; int pending;int priority;void *data;    void (*cb)(struct ev_loop *loop, struct ev_watcher *w, int revents);
} ev_watcher;

宏定义太多了反而不利于我们阅读,借助Windows下的IDE或者Linux下vim emacs或者vim+cscope或者vim+ctags来阅读下载的源码,可以很方便的找到变量的定义位置。
除了watcher的基类,还有一个监控器list类:

typedef struct ev_watcher_list
{EV_WATCHER_LIST (ev_watcher_list)
} ev_watcher_list;

其也是基于上面EV_WATCHER(type),展开也就是如下:

typedef struct ev_watcher_list
{int active; int pending;int priority;void *data;    void (*cb)(struct ev_loop *loop, struct ev_watcher_list *w, int revents);struct ev_watcher_list *next;
} ev_watcher_list;

同理,再看一个io watcher:

typedef struct ev_io
{EV_WATCHER_LIST (ev_io)int fd;     /* ro */int events; /* ro */
} ev_io;

展开就是:

typedef struct ev_io
{int active; int pending;int priority;void *data;    void (*cb)(struct ev_loop *loop, struct ev_io *w, int revents);struct ev_watcher_list *next;int fd;int events; 
} ev_io;

这里的fd,events就是派生类的私有成员,分别表示监听的文件描述符fd和触发的事件,可以看到,所有派生类的私有变量放在公共变量的后面,这样,在使用c语言的指针强制转换的时候,若把一个派生类ev_io类型对象的指针赋值给一个基类ev_watcher类型的指针p后,就可以通过p->active,p->pending,p->priority等来访问派生类中的active成员了。(不是多态,我想多了)
同样,定时器的watcher直接写出如下:

typedef struct ev_watcher_time
{int active; int pending;int priority;void *data;    void (*cb)(struct ev_loop *loop, struct ev_watcher_time *w, int revents);ev_tstamp at;
} ev_watcher_time;

这个at就是派生类中新的自有成员 ,表示的是at时间后,触发这个定时器,at类型为double
信号signal的watcher如下:

typedef struct ev_signal
{int active; int pending;int priority;void *data;    void (*cb)(struct ev_loop *loop, struct ev_signal *w, int revents);struct ev_watcher_list *next;int signum; 
} ev_signal;

这个signum就是派生类中新的自有成员 ,当给定的signum信号接收到时,触发调用
其他的事件watcher跟这三大事件类似,也就是:

**ev_periodic:**invoked at some specific time, possibly repeating at regular intervals (based on UTC)
**ev_child:**invoked when sigchld is received and waitpid indicates the given pid
**ev_stat:**invoked each time the stat data changes for a given path
**ev_fork:**the callback gets invoked before check in the child process when a fork was detected

**ev_idle:**invoked when the nothing else needs to be done, keeps the process from blocking
ev_prepare:
invoked for each run of the mainloop, just before the blocking call ,you can still change events in any way you like
**ev_check:**invoked for each run of the mainloop, just after the blocking call
**ev_cleanup:**is invoked just before the loop gets destroyed
ev_embed:
used to embed an event loop inside another ,the callback gets invoked when the event loop has handled events, and can be 0
**ev_async:**invoked when somebody calls ev_async_send on the watcher
我数了一下,包括3个常用类型,一共是13个事件类型,代码中还定义了一个可以容纳所有watcher对象的类型ev_any_watcher,定义如下:

union ev_any_watcher
{struct ev_watcher w;struct ev_watcher_list wl;struct ev_io io;struct ev_timer timer;struct ev_periodic periodic;struct ev_signal signal;struct ev_child child;
#if EV_STAT_ENABLEstruct ev_stat stat;
#endif
#if EV_IDLE_ENABLEstruct ev_idle idle;
#endifstruct ev_prepare prepare;struct ev_check check;
#if EV_FORK_ENABLEstruct ev_fork fork;
#endif
#if EV_CLEANUP_ENABLEstruct ev_cleanup cleanup;
#endif
#if EV_EMBED_ENABLEstruct ev_embed embed;
#endif
#if EV_ASYNC_ENABLEstruct ev_async async;
#endif
};

/割割割割割割割割
下面再看ev.c中主循环ev_loop定义如下:

  struct ev_loop{ev_tstamp ev_rt_now;#define ev_rt_now ((loop)->ev_rt_now)#define VAR(name,decl) decl;#include "ev_vars.h"#undef VAR};

其中ev_tstamp 就是时间单位,实际上就是double类型

把宏定义替换后,看到完整的ev_loop定义如下:

struct ev_loop
{ev_tstamp ev_rt_now;ev_tstamp now_floor;int rfeedmax;... .........;
}

类似这种加很多宏定义的
ev.h中还有如下的方便编程的define,可以使代码简洁不少,但同时也增加了阅读的难度。

struct ev_loop;
# define EV_P  struct ev_loop *loop               /* a loop as sole parameter in a declaration */
# define EV_P_ EV_P,                              /* a loop as first of multiple parameters */
# define EV_A  loop                               /* a loop as sole argument to a function call */
# define EV_A_ EV_A,                              /* a loop as first of multiple arguments */
# define EV_DEFAULT_UC  ev_default_loop_uc_ ()    /* the default loop, if initialised, as sole arg */
# define EV_DEFAULT_UC_ EV_DEFAULT_UC,            /* the default loop as first of multiple arguments */
# define EV_DEFAULT  ev_default_loop (0)          /* the default loop as sole arg */
# define EV_DEFAULT_ EV_DEFAULT,                  /* the default loop as first of multiple arguments */

在struct ev_loop的定义下面可以看到这样一句:

 static struct ev_loop default_loop_struct;extern struct ev_loop *ev_default_loop_ptr = 0; /* needs to be initialised to make it a definition despite extern */

该操作就是创建了一个ev_loop类型的实例对象以及ev_loop类型的空指针,default_loop_struct表示的是预制事件驱动器(就是loop)。如果在代码中使用的是预制事件驱动器,那么后续的操作就都围绕着这个数据结构展开了。
注意ev_default_loop_ptr是一个全局变量
//
下面我们用一个只有io事件的例子,通过gdb设置断点单步调试,来观察代码的过程,测试demo如下:

#include<ev.h>
#include <stdio.h>
#include <signal.h>
#include <sys/unistd.h>ev_io io_w;
void io_action(struct ev_loop *main_loop,ev_io *io_w,int e)
{int rst;char buf[1024] = {'\0'};puts("in io cb\n");read(STDIN_FILENO,buf,sizeof(buf));buf[1023] = '\0';printf("Read in a string %s \n",buf);ev_io_stop(main_loop,io_w);}
int main(int argc ,char *argv[])
{struct ev_loop *main_loop = ev_default_loop(0);ev_init(&io_w,io_action);ev_io_set(&io_w,STDIN_FILENO,EV_READ);  ev_io_start(main_loop,&io_w);ev_run(main_loop,0);return 0;
}

编译该程序,注意带上-g参数,这样就是把调试信息加到可执行文件中,如果没有-g,你将看不见程序的函数名、变量名,所代替的全是运行时的内存地址。编译命令如下:

gcc -g libev_io.c -l ev -o libio

启动gdb调试:
启动gdb

我们从main开始一步步走。首先执行 struct ev_loop *main_loop = ev_default_loop(0)这一句,通过跟进代码可以跟到函数 ev_default_loop 里面去,通过查阅源码我们可以看到这一块:

ev_default_loop (unsigned int flags) EV_THROW
{if (!ev_default_loop_ptr){
#if EV_MULTIPLICITYEV_P = ev_default_loop_ptr = &default_loop_struct;
#elseev_default_loop_ptr = 1;
#endif**loop_init (EV_A_ flags);**if (ev_backend (EV_A)){
#if EV_CHILD_ENABLEev_signal_init (&childev, childcb, SIGCHLD);ev_set_priority (&childev, EV_MAXPRI);ev_signal_start (EV_A_ &childev);ev_unref (EV_A); /* child watcher should not keep loop alive */
#endif}elseev_default_loop_ptr = 0;}return ev_default_loop_ptr;
}

首先判断上面说的在ev_loop中定义的全局对象指针ev_default_loop_ptr是否为空,也就是不曾使用预制的驱动器时,就让他指向静态变量default_loop_struct。
EV_P = ev_default_loop_ptr = &default_loop_struct;这一句表明同时在本函数里面统一用名字”loop”来表示该预制驱动器的指针。从而与函数参数为 EV_P 以及 EV_A的写法配合。也即是:

# define EV_P  struct ev_loop *loop               /* a loop as sole parameter in a declaration */
# define EV_P_ EV_P,                              /* a loop as first of multiple parameters */
# define EV_A  loop                               /* a loop as sole argument to a function call */
# define EV_A_ EV_A,                              /* a loop as first of multiple arguments */

接着对该指针做 loop_init操作,即初始化预制的事件驱动器。这里函数的调用了也是用到了 EV_A这样的写法进行简化。
初始化之后如果配置中Libev支持子进程,那么通过信号监控器实现了子进程监控器。
注意,在Libev的函数定义的时候,会看到 “EV_THROW” 这个东西,这里可以不用管它,他是对CPP中”try … throw”的支持,和EV_CPP中的extern “C”一样是一种编码技巧。

下面看看驱动器loop的初始化过程,也就是这一句:loop_init (EV_A_ flags);
比较长,很多都是根据环境判断宏定义,来确定是否执行,代码如下:

/* initialise a loop structure, must be zero-initialised */
static void noinline ecb_cold
loop_init (EV_P_ unsigned int flags) EV_THROW
{if (!backend){origflags = flags;#if EV_USE_REALTIMEif (!have_realtime){struct timespec ts;if (!clock_gettime (CLOCK_REALTIME, &ts))have_realtime = 1;}
#endif#if EV_USE_MONOTONICif (!have_monotonic){struct timespec ts;if (!clock_gettime (CLOCK_MONOTONIC, &ts))have_monotonic = 1;}
#endif/* pid check not overridable via env */
#ifndef _WIN32if (flags & EVFLAG_FORKCHECK)curpid = getpid ();
#endifif (!(flags & EVFLAG_NOENV)&& !enable_secure ()&& getenv ("LIBEV_FLAGS"))flags = atoi (getenv ("LIBEV_FLAGS"));ev_rt_now          = ev_time ();mn_now             = get_clock ();now_floor          = mn_now;rtmn_diff          = ev_rt_now - mn_now;
#if EV_FEATURE_APIinvoke_cb          = ev_invoke_pending;
#endifio_blocktime       = 0.;timeout_blocktime  = 0.;backend            = 0;backend_fd         = -1;sig_pending        = 0;
#if EV_ASYNC_ENABLEasync_pending      = 0;
#endifpipe_write_skipped = 0;pipe_write_wanted  = 0;evpipe [0]         = -1;evpipe [1]         = -1;
#if EV_USE_INOTIFYfs_fd              = flags & EVFLAG_NOINOTIFY ? -1 : -2;
#endif
#if EV_USE_SIGNALFDsigfd              = flags & EVFLAG_SIGNALFD  ? -2 : -1;
#endifif (!(flags & EVBACKEND_MASK))flags |= ev_recommended_backends ();#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);
#endifev_prepare_init (&pending_w, pendingcb);#if EV_SIGNAL_ENABLE || EV_ASYNC_ENABLEev_init (&pipe_w, pipecb);ev_set_priority (&pipe_w, EV_MAXPRI);
#endif}
}

开始是根据系统变量或者环境来选择性的执行一些步骤,这些在官方的Manual中有提到,主要就是影响默认支持的IO复用机制。接着是一连串的初始值的赋值,接着是根据系统支持的IO复用机制,对其进行初始化操作。
譬如如果选择select进行io复用,则会执行如下的初始化过程:

if (!backend && (flags & EVBACKEND_SELECT)) backend = select_init (EV_A_ flags);

select_init的过程具体在ev_select.c中,如下:

int inline_size
select_init (EV_P_ int flags)
{
//确定了要用的io复用方式,首先把名字统一了backend_mintime = 1e-6;backend_modify  = select_modify;backend_poll    = select_poll;
//读和写的fd_set的vector  ri用来装select函数返回后符合条件的fd
#if EV_SELECT_USE_FD_SETvec_ri  = ev_malloc (sizeof (fd_set)); FD_ZERO ((fd_set *)vec_ri);vec_ro  = ev_malloc (sizeof (fd_set));vec_wi  = ev_malloc (sizeof (fd_set)); FD_ZERO ((fd_set *)vec_wi);vec_wo  = ev_malloc (sizeof (fd_set));#ifdef _WIN32vec_eo  = ev_malloc (sizeof (fd_set));#endif
#elsevec_max = 0;vec_ri  = 0;vec_ro  = 0;vec_wi  = 0;vec_wo  = 0;#ifdef _WIN32vec_eo  = 0;#endif
#endifreturn EVBACKEND_SELECT;
}

最后是判断如果系统需要信号事件,那么进行一系列的操作。

下面看我们的demo下一句监控器初始化:
ev_init(&io_w,io_action);
经过跟踪查看,发现这不是一个函数,仅仅是一个宏定义:

/* these may evaluate ev multiple times, and the other arguments at most once */
/* **either use ev_init + ev_TYPE_set, or the ev_TYPE_init macro, below, to first initialise a watcher** */
#define ev_init(ev,cb_) do {            \((ev_watcher *)(void *)(ev))->active  =   \((ev_watcher *)(void *)(ev))->pending = 0;    \ev_set_priority ((ev), 0);            \ev_set_cb ((ev), cb_);            \
} while (0)

再看demo下一句,设置io watcher的触发条件:
ev_io_set(&io_w,STDIN_FILENO,EV_READ);
跟踪发现该句也是一个宏定义:

#define ev_io_set(ev,fd_,events_)            do { (ev)->fd = (fd_); (ev)->events = (events_) | EV__IOFDSET; } while (0)

即把检测的文件描述符跟事件类型赋值
下面看demo中io观察器配置的最后一步:
ev_io_start(main_loop,&io_w);
在ev.c文件中可以看到,该函数定义如下:
ev_io_start:

void noinline
ev_io_start (EV_P_ ev_io *w) EV_THROW
{int fd = w->fd;if (expect_false (ev_is_active (w)))return;assert (("libev: ev_io_start called with negative fd", fd >= 0));assert (("libev: ev_io_start called with illegal event mask", !(w->events & ~(EV__IOFDSET | EV_READ | EV_WRITE))));EV_FREQUENT_CHECK;**ev_start (EV_A_ (W)w, 1);**array_needsize (ANFD, anfds, anfdmax, fd + 1, array_init_zero);wlist_add (&anfds[fd].head, (WL)w);/* common bug, apparently */assert (("libev: ev_io_start called with corrupted watcher", ((WL)w)->next != (WL)w));fd_change (EV_A_ fd, w->events & EV__IOFDSET | EV_ANFD_REIFY);w->events &= ~EV__IOFDSET;EV_FREQUENT_CHECK;
}

首先是两个assert,就是用来检测错误的,可能是检查内部数据结构啊,边界值是否合理等等。
再下面就是ev_start,其代码如下:

inline_speed void
ev_start (EV_P_ W w, int active)
{pri_adjust (EV_A_ w);w->active = active;ev_ref (EV_A);
}

在这里面把active置1,并且++了activecnt,activecnt也就是指事件驱动器loop上面存在的监控器数量,因为现在新建了一个io watcher,所以数量要加1,而active就是指watcher是否正在监控。
下面就是:

array_needsize (ANFD, anfds, anfdmax, fd + 1, array_init_zero);wlist_add (&anfds[fd].head, (WL)w);

这就是libev里面对动态数组的实现,判断anfd数组的空间是否足够fd+1,不够的话就调整数组内存的大小。其中ADFD结构的定义如下:

/* file descriptor info structure */
typedef struct
{WL 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;
#if EV_USE_EPOLLunsigned int egen;    /* generation counter to counter epoll bugs */
#endif
#if EV_SELECT_IS_WINSOCKET || EV_USE_IOCPSOCKET handle;
#endif
#if EV_USE_IOCPOVERLAPPED or, ow;
#endif
} ANFD;

这是一个文件描述符信息的数据结构,包括WL类型也就是watcher的基类链表:ev_watcher_list *类型的变量head,一个ANFD就是一个文件描述符所关联的信息,前面anfds就是ANFD类型的数组,数组的下标是通过fd进行索引的,这样由于fd不会太大,所以分配的空间合理,索引速度也是o(1)级别的。
再看下面:

  fd_change (EV_A_ fd, w->events & EV__IOFDSET | EV_ANFD_REIFY);w->events &= ~EV__IOFDSET;

对于fd_change:

/* something about the given fd changed */
inline_size void
fd_change (EV_P_ int fd, int flags)
{unsigned char reify = anfds [fd].reify;anfds [fd].reify |= flags;if (expect_true (!reify)){++fdchangecnt;array_needsize (int, fdchanges, fdchangemax, fdchangecnt, EMPTY2);fdchanges [fdchangecnt - 1] = fd;}
}

**fd_change:如果对于给定的fd,在其上的event发生了,那么就把fd添加到一个fdchanges的数组里。
fd_reify:依次遍历fdchanges数组中的fd,并且比较fd上的handle与对应anfd上的handle是否相同,从而判断监控条件是否发生改变,如果改变了则调用backend_modify也就是 select_modify或者epoll_ctl等调整系统对该fd的监控条件,代码如下:**

SOCKET handle = EV_FD_TO_WIN32_HANDLE (fd);if (handle != anfd->handle){unsigned long arg;assert (("libev: only socket fds supported in this configuration", ioctlsocket (handle, FIONREAD, &arg) == 0));/* handle changed, but fd didn't - we need to do it in two steps */backend_modify (EV_A_ fd, anfd->events, 0);anfd->events = 0;anfd->handle = handle;}

fdchanges数组的作用就在于此,该数组记录了anfds数组中fd对应的watcher监控条件可能被修改的文件描述符,并且在适当的时候低啊用系统的io复用机制修改系统的监控条件。正如官方代码注释中说的:
make sure the external fd watch events are in-sync with the kernel/libev internal state
具体的anfd和fdchanges结构如下:
结构图
至此,把io事件的注册过程(ev_io_start)走了一遍,大致就是从之前设置了监控条件的io watcher获取文件描述符fd,找到fd在anfds中对应的ANFD结构,然后把watcher加入到该结构的ev_watcher_list *类型的head链上去。因为对应该fd的监控条件肯定已经改变了(新加入的啊),所以在fdchanges数组中加入该fd,然后后续会通过io复用机制来修改对该fd的监控条件。

/
下面就是启动我们的时间驱动器,也就是定义的loop/mainloop:
ev_run(main_loop,0);
这个函数直接从ev.c中就可以看到,比较长就不全部贴上来了,大致的逻辑就是

do{//fork prepare...XXXXXX;/* update fd-related kernel structures */fd_reify (EV_A);/* calculate blocking time */XXXXXxxxX;//进入poll之前,先sleep io_blocktimebackend_poll();//也就是select_poll或者poll_poll或者epoll_poll}while(condition_is_ok)

先计算出poll之前要阻塞的时间,进入poll之前,先sleep io_blocktime,然后就进入backend_poll()函数,也就是select_poll或者poll_poll或者epoll_poll,我们以epoll举例,epoll共有3个系统调用:
1)调用epoll_create()建立一个epoll对象(在epoll文件系统中为这个句柄对象分配资源)
2)调用epoll_ctl向epoll对象中添加这100万个连接的套接字
3)调用epoll_wait收集发生的事件的连接
具体select poll epoll的使用与分析见我的另一篇博客:
传送门

我们进入backend_poll也就是epoll_poll,首先进入epoll_wait()去收集发生的事件连接,返回发生的事件数目eventcnt,然后依次遍历所有发生的事件,分别做处理。
从epoll返回后,将监控中的文件描述符fd以及其pending(满足监控)的条件通过fd_event()做判断监控条件是否改变,然后到fd_event_nocheck()里面对anfds[fd]数组中的fd上的挂的监控器依次做检测,如果pending条件符合,便通过ev_feed_event()将该监控器加入到pendings数组中pendings[pri]上的pendings[pri][old_lenght+1]的位置。ev_feed_event()代码如下:

void noinline
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;},pendingpri = NUMPRI - 1;
}

这里要介绍一个新的数据结构,他表示pending中的监控条件满足了,但是还没有触发动作的状态:

/* stores the pending event set for a given watcher */
typedef struct
{W w;int events; /* the pending event set for the given watcher */
} ANPENDING;

W就是 ev_watcher * 类型,上面用到的pendings[]就是ANPENDING类型的一个二维数组,其一级下标pri就是指watcher的优先级,该优先级上pending的监控器数目为二级下标,对应监控器中实际的pending值就是二维下标+1。
首先对于二维数组pendings[],其定义为ANPENDING *pendings [NUMPRI],其中以优先级为下标的每个元素都是ANPENDING类型的,这样就可以把pending的event类型及watcher指针加入到二维数组pendings[]里去,这是通过xxx_reify函数实现这个过程的。
下图就是anfds数组跟pendings数组的对应结构与关系:
pendings
再后面就是执行:
EV_INVOKE_PENDING;
其实就是invoke_cb (EV_A),也就是调用loop->invoke_cb,也就是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);EV_FREQUENT_CHECK;}}
}

该函数会遍历pendings这个二维数组,按优先级执行在pending的每一个watcher上触发事件对应的的回调函数。遍历结束后判断是否结束整个loop,不的话就再次从io复用那儿等待来一遍,结束的话就清理、退出。

//
至此,我们就把一个简单的io watcher的例子看完了,其他的事件观察器也基本类似,整个逻辑都是围绕watcher来做的,libev内部维护一个基类ev_watcher和一些特定监控器的派生类ev_xxx,如ev_io,当我们要使用一个监控器的时候,首先生成一个具体的watcher实例,并且通过派生类的私有成员设置触发条件,监控fd。
然后就用anfds或者最小堆管理这些watchers,然后通过backend_poll代表的系统io复用机制如epoll,以及时间堆管理运算出pending的watcher,再调用reify函数把触发事件的watcher按优先级加入到pendings[]二维数组中去。
最后就是依次按优先级调用pendings里面watcher上触发事件的回调函数,这样就实现了一个按优先级的事件模型。
整个过程如下图所示:
libev


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

相关文章

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

Python:二叉树遍历

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