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_ENABLE
struct ev_stat stat;
#endif
#if EV_IDLE_ENABLE
struct ev_idle idle;
#endif
struct ev_prepare prepare;
struct ev_check check;
#if EV_FORK_ENABLE
struct ev_fork fork;
#endif
#if EV_CLEANUP_ENABLE
struct ev_cleanup cleanup;
#endif
#if EV_EMBED_ENABLE
struct ev_embed embed;
#endif
#if EV_ASYNC_ENABLE
struct 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调试:
我们从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_MULTIPLICITY
EV_P = ev_default_loop_ptr = &default_loop_struct;
#else
ev_default_loop_ptr = 1;
#endif
**loop_init (EV_A_ flags);**
if (ev_backend (EV_A))
{
#if EV_CHILD_ENABLE
ev_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
}
else
ev_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_REALTIME
if (!have_realtime)
{
struct timespec ts;
if (!clock_gettime (CLOCK_REALTIME, &ts))
have_realtime = 1;
}
#endif
#if EV_USE_MONOTONIC
if (!have_monotonic)
{
struct timespec ts;
if (!clock_gettime (CLOCK_MONOTONIC, &ts))
have_monotonic = 1;
}
#endif
/* pid check not overridable via env */
#ifndef _WIN32
if (flags & EVFLAG_FORKCHECK)
curpid = getpid ();
#endif
if (!(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_API
invoke_cb = ev_invoke_pending;
#endif
io_blocktime = 0.;
timeout_blocktime = 0.;
backend = 0;
backend_fd = -1;
sig_pending = 0;
#if EV_ASYNC_ENABLE
async_pending = 0;
#endif
pipe_write_skipped = 0;
pipe_write_wanted = 0;
evpipe [0] = -1;
evpipe [1] = -1;
#if EV_USE_INOTIFY
fs_fd = flags & EVFLAG_NOINOTIFY ? -1 : -2;
#endif
#if EV_USE_SIGNALFD
sigfd = flags & EVFLAG_SIGNALFD ? -2 : -1;
#endif
if (!(flags & EVBACKEND_MASK))
flags |= ev_recommended_backends ();
#if EV_USE_IOCP
if (!backend && (flags & EVBACKEND_IOCP )) backend = iocp_init (EV_A_ flags);
#endif
#if EV_USE_PORT
if (!backend && (flags & EVBACKEND_PORT )) backend = port_init (EV_A_ flags);
#endif
#if EV_USE_KQUEUE
if (!backend && (flags & EVBACKEND_KQUEUE)) backend = kqueue_init (EV_A_ flags);
#endif
#if EV_USE_EPOLL
if (!backend && (flags & EVBACKEND_EPOLL )) backend = epoll_init (EV_A_ flags);
#endif
#if EV_USE_POLL
if (!backend && (flags & EVBACKEND_POLL )) backend = poll_init (EV_A_ flags);
#endif
#if EV_USE_SELECT
if (!backend && (flags & EVBACKEND_SELECT)) backend = select_init (EV_A_ flags);
#endif
ev_prepare_init (&pending_w, pendingcb);
#if EV_SIGNAL_ENABLE || EV_ASYNC_ENABLE
ev_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_SET
vec_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 _WIN32
vec_eo = ev_malloc (sizeof (fd_set));
#endif
#else
vec_max = 0;
vec_ri = 0;
vec_ro = 0;
vec_wi = 0;
vec_wo = 0;
#ifdef _WIN32
vec_eo = 0;
#endif
#endif
return 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_EPOLL
unsigned int egen; /* generation counter to counter epoll bugs */
#endif
#if EV_SELECT_IS_WINSOCKET || EV_USE_IOCP
SOCKET handle;
#endif
#if EV_USE_IOCP
OVERLAPPED 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_blocktime
backend_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数组的对应结构与关系:
再后面就是执行:
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上触发事件的回调函数,这样就实现了一个按优先级的事件模型。
整个过程如下图所示: