libev 源码浅析

libev是一个开源的事件驱动库,基于epoll,kqueue等OS提供的基础设施。其以高效出名,它可以将IO事件,定时器,和信号统一起来,统一放在事件处理这一套框架下处理。

libev的基本使用方法如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

int main (void)

{

// use the default event loop unless you have special needs

struct ev_loop *loop = EV_DEFAULT;

// initialise an io watcher, then start it

// this one will watch for stdin to become readable

ev_io_init (&stdin_watcher, stdin_cb, /*STDIN_FILENO*/0, EV_READ);// 设置对stdin_watcher这个fd关注读事件,并指定回调函数

ev_io_start (loop, &stdin_watcher);// 激活stdin_watcher这个fd,将其设置到loop中

// initialise a timer watcher, then start it

// simple non-repeating 5.5 second timeout

ev_timer_init (&timeout_watcher, timeout_cb, 5.5, 0.);//设置一个定时器,并指定一个回调函数,这个timer只执行一次,5.5s后执行

ev_timer_start (loop, &timeout_watcher);//激活这个定时器,将其设置到loop中

// now wait for events to arrive

ev_run (loop, 0);//循环开始

// break was called, so exit

return 0;

}

libev中有一个抽象概念,叫做watcher(ev_watcher),libev中有各种各样的watcher,比如定时器watcher(struct ev_timer),I/O watcher(struct ev_io) , 信号watcher(struct ev_signal)

等等。这三个具体的watcher相当于父类watcher的子类。这种继承关系在C++这种高级语言中已内置,在C中实现就需要一些技巧,libev的作者使用了宏。

"父类"ev_watcher定义如下:

typedef structev_watcher

{

"color: #ff0000;">EV_WATCHER(ev_watcher)

} ev_watcher;

宏EV_WATCHER定义如下:

/* shared by all watchers */

#define EV_WATCHER(type)      \

int active;/* private */\    //该watcher是否被激活,加入到loop中

int pending;/* private */\  //该watcher关注的events是否已触发

EV_DECL_PRIORITY/* private */\//int priority; 优先级,watcher是有优先级的

EV_COMMON/* rw */\        // void *data;

EV_CB_DECLARE (type) /* private */// void (*cb)(struct ev_loop *loop, type *w, int revents);回调函数

再看一个"父类"ev_watcher_list的定义:

typedefstructev_watcher_list

{

EV_WATCHER_LIST (ev_watcher_list)

} ev_watcher_list;

宏EV_WATCHER_LIST定义如下:

#define EV_WATCHER_LIST(type)     \

"color: #ff0000;"> EV_WATCHER (type)       \

structev_watcher_list *next; /* private */

可以看出,ev_watcher_list 其实也是ev_watcher的一个"子类", 它多了一个成员变量 struct  ev_watcher_list *next;

这个成员变量用于将watcher串起来。

现在看一个I/O watcher 这个最重要的"子类":

typedef struct ev_io

{

EV_WATCHER_LIST (ev_io)

int fd;    /* ro */// 显而易见,与io相关联的fd

int events;/* ro */// 这个watcher在fd上关注的事件

} ev_io;

可以看出,ev_io是一种具体的watcher,它有两个自己专有的成员变量fd和events

下面看一下最关键的一个数据结构:

struct ev_loop

{

ev_tstamp ev_rt_now;

#define ev_rt_now ((loop)->ev_rt_now)

// 这里decl是declare的意思,ev_vars.h 里面会定义一堆的变量,这些变量

// 都是本结构的成员,ev_vars.h展开的时候会用到下面这一行VAR的宏

#define VAR(name,decl) decl;

#include "ev_vars.h"

#undef VAR

};

ev_vars.h中包含很多关键的成员,比如:

epoll相关的成员变量:

#if EV_USE_EPOLL || EV_GENWRAP

VARx(struct epoll_event *, epoll_events)  // 相当于struct epoll_event *epoll_events

VARx(int, epoll_eventmax) //目前epoll_events数组的大小,可以扩充,每次以2倍的大小扩充

VARx(int, backend_fd) // 对于epoll来说,就是epoll使用的fd

//对于epoll来说,实际的函数是ev_epoll.c中的epoll_modify函数,这个函数会执行epoll_ctl

VAR (backend_modify, void(*backend_modify)(EV_P_intfd,intoev,intnev))

//对于epoll来说,实际的函数是ev_poll.c中的epoll_poll函数,这个函数会执行epoll_wait

VAR (backend_poll  , void(*backend_poll)(EV_P_ ev_tstamp timeout))

与fd相关的成员变量:

VARx(ANFD *, anfds)//这个数组是以fd为索引

VARx(int, anfdmax) //上面数组的大小

VARx(int*, fdchanges) // fdchangemax大小的数组,每个元素是一个fd,这个数组中存了所有epoll需要poll的fd

VARx(int, fdchangemax) //数组的容量

VARx(int, fdchangecnt) // 数组中实际的元素的大小

ANFD和fd一一对应,结构体ANFD如下:

typedefstruct

{

WL head; // typedef ev_watcher_list *WL; 关注同一个fd的事件的watcher的链表,一个fd可以有多个watcher监听它的事件

unsigned char events;/* the events watched for */// watcher链表中所有watcher关注的事件的按位与

unsigned char reify; /* flag set when this ANFD needs reification (EV_ANFD_REIFY, EV__IOFDSET) *///当这个结构体需要重新epoll_ctl则设置,说明关注的事件发生了变化

unsignedcharemask; /* the epoll backend stores the actual kernel mask in here *///实际发生的事件

unsignedcharunused;

#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;

维护所有的"所关注的事件发生了的watcher",这些watcher的callback最后都需要被调用

VAR (pendings, ANPENDING *pendings [NUMPRI])  //watcher是有优先级的,libev为每个优先级的watcher维护一个数组

VAR (pendingmax, intpendingmax [NUMPRI]) // 每个优先级watcher数组的容量

VAR (pendingcnt, intpendingcnt [NUMPRI]) //  每个优先级watcher数组实际大小

struct ANPENDING如下:

typedefstruct

{

W w; //typedef ev_watcher *W;

int events;/* the pending event set for the given watcher *///events只指这个watcher关注了的并且已经发生了的还没有处理的事件

} ANPENDING;

从示例程序可以看出,使用libev主要有几个方法:

ev_io_init

ev_io_start

ev_timer_init

ev_timer_start

ev_run

一个个看:

ev_io_init是个宏,主要就是设置ev_io中的各个成员

void ev_io_start(struct ev_loop *loop, ev_io *w) 会做如下几件事情:

1.  将参数w表示的watcher激活(w->active=1)

2.  将watcher w 加入到 w所关注的fd在anfds[](loop中)中相应的位置处的结构体ANFD中的watcher list链表中。

3.  将w所关注的fd加入到int *fdchanges数组中。

void ev_run(struct ev_loop *loop, int flags)最重要的函数,做如下几件事:

1. 调用fd_reify。

遍历fdchanges数组:针对其中的每个fd,做如下事情:

将关注这个fd的所有的watcher都从ANFD的链表中取出来(通过fd去anfds[]中找到相应的ANFD结构体),然后将所有这些watcher关注的events通过epoll_ctl 给设置到

kernel中。然后清空fdchanges数组,其实就是将fdchangecnt置为0。

2. time_update 更新时间,校准时间

3. 计算给epoll_wait()使用的timeout,从维护所有的timer的最小堆中取堆顶(timers [HEAP0]),然后减去当前时间得到timeout。最小堆使用一个数组实现的,每个元素是

struct ANHE。

timers定义如下:

VARx(ANHE *, timers)

  struct ANHE定义如下:

/* a heap element */

typedef struct{

ev_tstamp at;//timer watcher 到期时间

WT w;// typedef ev_watcher_time *WT;

} ANHE;

typedef struct ev_watcher_time

{

EV_WATCHER_TIME (ev_watcher_time)

} ev_watcher_time;

#define EV_WATCHER_TIME(type) \

EV_WATCHER (type) \

ev_tstamp at; /* private */

4. 调用backend_poll(loop, waittime)会做如下几件事:

对于使用epoll的系统来说,它实际上是调用ev_epoll.c 中的epoll_poll()函数,这个函数主要流程如下:

4.1. 调用epoll_wait()

4.2. 遍历每个返回的 struct epoll_event,取出fd,和epoll_event中激活的事件,调用fd_event (loop, fd, got)函数。

这个函数会更新loop的pending数组,将那些已经关注了一些事件的watcher,并且确实发生了的这些watcher给设置到loop的pending数组中

注意这里,got是所有watcher关注的事件的总和。ANPENDING结构体中的events仅仅单个watcher关注的events。

5. time_update 再次更新校准时间

6. timers_reify(loop) 如果最近的定时器已经触发过了,则重新选出下一个最近的定时器,将其置于堆顶。

7.periodics_reify(loop) 和timers处理差不多

8. idle_reify(loop) 没关注

9. EV_INVOKE_PENDING 从loop的pending数组中依次调用各个watcher的回调函数,优先级从高到低

10.如果当前loop中还有被激活的watcher,并且loop_done ==0 并且启动ev_run(flags)的参数没有设置EVRUN_ONCE和EVRUN_NOWAIT,则继续从1开始。

到目前为止,已经将timer和I/O事件进行统一了,现在看信号是如何统一到事件处理框架中的。

看看signal watcher的定义:

/* invoked when the given signal has been received */

/* revent EV_SIGNAL */

typedefstructev_signal

{

EV_WATCHER_LIST (ev_signal)

intsignum;/* ro *///信号id

} ev_signal;

ANSIG signals [EV_NSIG - 1]; // 每个信号的信息用一个ANSIG结构体表示

typedefstruct

{

EV_ATOMIC_T pending;

#if EV_MULTIPLICITY

EV_P;

#endif

WL head;//可以有多个watcher关注同一个信号,使用链表串起来

}ANSIG;

loop中和信号相关的主要成员如下:

VAR (evpipe, intevpipe [2])// 用于将信号处理和事件处理框架结合在一起,evpipe[0]用于读,evpipe[1]用于写

VARx(ev_io, pipe_w)  //这个I/O ev就是用于封装上面的pipe的读端,让epoll监听这个pipe_w,当接收到信号的时候,只需要在信号处理函数中往evpipe[1]中写即可

和I/O事件,timer事件类似,有如下两个过程:

ev_signal_init : 宏,初始化这个ev_signal结构体

void ev_signal_start(struct ev_loop *loop, ev_signal *w)做如下几件事:

1. 激活ev_signal这个watcher(w->active = 1)

2. 将w串到signals数组中相应位置的watcher list中

3. 初始化一对pipe,这对pipe就是用于将信号和事件处理框架结合起来,并且注册一个ev_io pipe_w到epoll中,pipe_w封装的是int evpipe[2]的读端evpipe[0]

4. 注册这个信号的信号处理函数ev_sighandler,信号处理函数会将signals数组中相应的槽位的pending置1,意思是已接收到这个信号,并且往int evpipe[2]的写端

evpipe[1]写入一个字节,这样在pipe_w这个IO event就有了读事件,从而epoll_wait()返回,成功的将信号和事件处理框架结合起来。

至此,libev如何将IO 事件,timer事件和信号统一到事件处理框架下已分析完成。可以看出,libev和libevent的做法是一样的,只是实现不一样,比如,关于继承的实现,libev使用宏来实现相当的难读,但是这样做是高效的,借用霸爷的话说:"在libev的世界中,效率第一"。这可能正是大多数人说,libev比libevent代码难读的原因

时间: 2024-11-03 21:17:23

libev 源码浅析的相关文章

Volley框架源码浅析(一)

尊重原创http://blog.csdn.net/yuanzeyao/article/details/25837897 从今天开始,我打算为大家呈现关于Volley框架的源码分析的文章,Volley框架是Google在2013年发布的,主要用于实现频繁而且粒度比较细小的Http请求,在此之前Android中进行Http请求通常是使用HttpUrlConnection和HttpClient进行,但是使用起来非常麻烦,而且效率比较地下,我想谷歌正式基于此种原因发布了Volley框架,其实出了Voll

PM2源码浅析

PM2工作原理 最近在玩一个游戏,<地平线:黎明时分>,最终Boss是一名叫黑底斯的人,所谓为人,也许不对,黑底斯是一段强大的毁灭进程,破坏了盖娅主进程,从而引发的整个大陆机械兽劣化故事. 为什么要讲这么一段呢,是希望大家可以更好地理解pm2的原理,要理解pm2就要理解god和santan的关系,god和santan的关系就相当于盖娅和黑底斯在pm2中的01世界中,每一行代码每一个字节都安静的工作god就是Daemon进程 守护进程,重启进程,守护node程序世界的安宁,santan就是进程的

Android源码浅析(一)——VMware Workstation Pro和Ubuntu Kylin 16.04 LTS安装配置

Android源码浅析(一)--VMware Workstation Pro和Ubuntu Kylin 16.04 LTS安装配置 最近地方工作,就是接触源码的东西了,所以好东西还是要分享,系列开了这么多,完结 的也没几个,主要还是自己覆盖的太广了,却又不精通,嘿嘿,工作需要,所以写下了本篇博客 一.VMware 12 我选择的虚拟机试VMware,挺好用的感觉,下载VMware就不说了,善用搜索键嘛,这里我提供一个我现在在用的 下载地址:链接:http://pan.baidu.com/s/1k

ReactiveCocoa2 源码浅析

ReactiveCocoa2 源码浅析 标签(空格分隔): ReactiveCocoa iOS Objective-C ? 开车不需要知道离合器是怎么工作的,但如果知道离合器原理,那么车子可以开得更平稳. ReactiveCocoa 是一个重型的 FRP 框架,内容十分丰富,它使用了大量内建的 block,这使得其有强大的功能的同时,内部源码也比较复杂.本文研究的版本是2.4.4,小版本间的差别不是太大,无需担心此问题. 这里只探究其核心 RACSignal 源码及其相关部分.本文不会详细解释里

【Spark Core】任务执行机制和Task源码浅析2

引言 上一小节<任务执行机制和Task源码浅析1>介绍了Executor的注册过程. 这一小节,我将从Executor端,就接收LaunchTask消息之后Executor的执行任务过程进行介绍. 1. Executor的launchTasks函数 DriverActor提交任务,发送LaunchTask指令给CoarseGrainedExecutorBackend,接收到指令之后,让它内部的executor来发起任务,即调用空闲的executor的launchTask函数. 下面是Coars

Volley框架源码浅析(二)

尊重原创 http://write.blog.csdn.net/postedit/25921795 在前面的一片文章Volley框架浅析(一)中我们知道在RequestQueue这个类中,有两个队列:本地队列和网络队列 /** The cache triage queue. */ private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<

Android手势源码浅析-----手势绘制(GestureOverlayView)

Android手势源码浅析-----手势绘制(GestureOverlayView)

【Spark】Stage生成和Stage源码浅析

引入 上一篇文章<DAGScheduler源码浅析>中,介绍了handleJobSubmitted函数,它作为生成finalStage的重要函数存在,这一篇文章中,我将就DAGScheduler生成Stage过程继续学习,同时介绍Stage的相关源码. Stage生成 Stage的调度是由DAGScheduler完成的.由RDD的有向无环图DAG切分出了Stage的有向无环图DAG.Stage的DAG通过最后执行的Stage为根进行广度优先遍历,遍历到最开始执行的Stage执行,如果提交的St

转:Spring FactoryBean源码浅析

http://blog.csdn.net/java2000_wl/article/details/7410714 在Spring BeanFactory容器中管理两种bean 1.标准Java Bean 2,另一种是工厂Bean,   即实现了FactoryBean接口的bean  它不是一个简单的Bean 而是一个生产或修饰对象生成的工厂Bean 在向Spring容器获得bean时  对于标准的java Bean  返回的是类自身的实例 而FactoryBean 其返回的对象不一定是自身类的一