Libev源码分析 -- 整体设计

libev是Marc Lehmann用C写的高性能事件循环库。通过libev,可以灵活地把各种事件组织管理起来,如:时钟、io、信号等。libev在业界内也是广受好评,不少项目都采用它来做底层的事件循环。node.js也是其中之一。 学习和分析libev库,有助于理解node.js底层的工作原理,同时也可以学习和借鉴libev的设计思想。本文是最近在学习libev源码的一些心得总结吧。

libev示例

先上一个例子,看看libev是怎么使用的吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// a single header file is required
#include <ev.h> #include <stdio.h> // for puts // every watcher type has its own typedef'd struct
// with the name ev_TYPE
ev_io stdin_watcher;
ev_timer timeout_watcher; // all watcher callbacks have a similar signature
// this callback is called when data is readable on stdin
static void
stdin_cb (EV_P_ ev_io *w, int revents)
{
puts ("stdin ready");
// for one-shot events, one must manually stop the watcher
// with its corresponding stop function.
ev_io_stop (EV_A_ w); // this causes all nested ev_run's to stop iterating
ev_break (EV_A_ EVBREAK_ALL);
} // another callback, this time for a time-out
static void
timeout_cb (EV_P_ ev_timer *w, int revents)
{
puts ("timeout");
// this causes the innermost ev_run to stop iterating
ev_break (EV_A_ EVBREAK_ONE);
} 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);
ev_io_start (loop, &stdin_watcher); // initialise a timer watcher, then start it
// simple non-repeating 5.5 second timeout
ev_timer_init (&timeout_watcher, timeout_cb, 5.5, 0.);
ev_timer_start (loop, &timeout_watcher); // now wait for events to arrive
ev_run (loop, 0); // break was called, so exit
return 0;
}

这是libev官网文档的例子,其中libev的使用步骤还是比较清晰的。从main开始入手,可以发现代码中主要做了这么几件事情:

  • 获取ev_loop实例。ev_loop,从名字上可以看出,它代表了一个事件循环,也是我们后面代码的主要组织者。

  • 创建和初始化watcher。libev中定义了一系列的watcher,每类watcher负责一类特定的事件。一般可以通过ev_TYPE_init函数来创建一个watcher实例(TYPE是某一种watcher类型,如:io, timer等)。例子中分别创建了io和timer两个watcher,并绑定了相应的回调函数。当感兴趣的事件发生后,对应的回调函数将会被调用。

  • 将watcher注册到ev_loop中。一般可以通过ev_TYPE_start函数来完成。注册成功后,watcher便和loop关联起来了,当loop中检测到感兴趣的事件发生,便会通知相关的watcher。

  • 启动事件循环。 即后面的ev_run函数。事件循环启动后,当前线程/进程将会被阻塞,直到循环被终止。

在上面的例子中,在两个回调函数中的ev_break函数就是终止循环的地方。当5.5秒超时或是标准输入有输入事件,则会进入到相应的回调函数,然后会终止事件循环,退出程序。

libev工作原理

总的来看,libev其实是实现了Reactor模式。当中主要包含了这么几个角色:watcher, ev_loop和ev_run。

watcher

watcher是Reactor中的Event Handler。一方面,它向事件循环提供了统一的调用接口(按类型区分);另一方面,它是外部代码的注入口,维护着具体的watcher信息,如:绑定的回调函数,watcher的优先级,是否激活等。

在ev.h中我们可以看到各种watcher的定义,如:ev_io, ev_timer等。其中,watcher的公共属性定义如下:

1
2
3
4
5
6
7
/* shared by all watchers */
#define EV_WATCHER(type) \
int active; /* private */ \
int pending; /* private */ \
EV_DECL_PRIORITY /* private int priority; */ \
EV_COMMON /* rw void *data; */ \
EV_CB_DECLARE (type) /* private */

其中的宏定义如下:

1
2
3
# define EV_DECL_PRIORITY int priority;
# define EV_COMMON void *data;
# define EV_CB_DECLARE(type) void (*cb)(EV_P_ struct type *w, int revents);
  • active: 表示当前watcher是否被激活。ev_TYPE_start调用后置位,ev_TYPE_stop调用后复位;

  • pending: 表示当前watcher有事件就绪,等待处理。pending的值其实就是当前watcher在pendings队列中的下标;

  • priority: 是当前watcher的优先级;

  • data: 附加数据指针,用来在watcher中携带额外所需的数据;

  • cb:是事件触发后的回调函数定义。

具体的watcher再在此基础上添加额外的属性。 开发者可以根据需要,选择特定类型的watcher,创建实例并进行初始化,然后将实例注册到loop中即可。libev中定义了若干种类型的watcher,每类watcher负责解决某一特定领域的问题(如:io, timer, signal等),可以在ev.h中看到这些watcher的定义。

ev_loop

ev_loop则是一个Reactor的角色,是事件循环的上下文环境,就像一根竹签,把前面的watcher实例像糖葫芦一样串起来。

ev_loop的定义

ev_loop的定义在ev.c中,具体如下:

1
2
3
4
5
6
7
8
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_vars.h中定义了ev_loop的各种属性。在ev_wrap.h中则定义了与loop相关的各种宏,代码中大多都是以宏的形式进行操作。

watcher的管理

在ev_loop中,watcher按各自的类型进行分类存储。如:io的loop->anfds,timer的loop->timers。ev_TYPE_start在激活watcher后,便将它加入到相应的存储结构中(具体的实现在后面介绍watcher的文章再分析)。

在事件循环中,有事件就绪的watcher会被挑拣出来,保存到ev_loop中。这些就绪的watcher主要由loop->pendings和loop->pendingcnt来维护(如下图所示)。这两个东西都是二维数组,第一维都是优先级。pendings中存的是ANPENDING实例,后者的做要作用就是维护就绪的watcher指针; 而pendingcnt中存的则是对应优先级上的pendings元素的数量。在每个就绪的watcher上也会有一个pending字段记录它在pendings列表中的下标,这样就可以通过watcher很方便的找到它在pendings列表中的位置了,这对删除操作很有帮助。

在一轮事件循环结束后,则会根据优先级,依次触发就绪的watcher。

bool ev_run(loop, flag)

ev_run函数是执行事件循环的引擎,即Reactor模式中的select方法。通过向ev_run函数传递一个ev_loop实例,便可以开启一个事件循环。ev_run实际上是一个巨大的do-while循环,期间会检查loop中注册的各种watcher的事件。如果有事件就绪,则触发相应的watcher。这个循环会一直持续到ev_break被调用或者无active的watcher为止。当然,也可以通过传递EVRUN_NOWAIT或EVRUN_ONCE等flag来控制循环的阻塞行为。

ev_run的工作内容,在官方文档的API中有详细说明,通过文档有助于对ev_run的理解。具体的代码有点长,在这里就不贴了,感兴趣的同学可以在ev.c中查看ev_run的实现代码。剔除掉条件检查和一些无关紧要的代码,主要的流程如下图所示。

可以看到,ev_run的主要工作就是按watcher的分类,先后检查各种类型的watcher上的事件,通过ev_feed_event函数将就绪的watcher加入到pending的数据结构中。最后调用ev_invoke_pending,触发pending中的watcher。完了以后会检查,是否还有active的watcher以及是否有ev_break调用过,然后决定是否要继续下一轮循环。

总结

总的来看,libev的结构设计还是非常清晰。如果说,主循环ev_run是libev这棵大树的主干,那么功能强大,数量繁多的watcher就是这棵大树的树叶,而循环上下文ev_loop则是连接主干和树叶的树枝,它们的分工与职责是相当明确的。作为大树的主干,ev_run的代码是非常稳定和干净的,基本上不会掺杂外部开发者的逻辑代码进来。作为叶子的watcher,它的定位也非常明确:专注于自己的领域,只解决某一个类型的问题。不同的watcher以及watcher和主循环之间并没有太多的干扰和耦合,这也是libev能如此灵活扩展的原因。而中间的树枝ev_loop的作用也是不言而喻的,正是因为它在中间的调和,前面两位哥们才能活得这么有个性。

看到这里,libev的主体架构已经比较清楚了,但是似乎还没看到与性能相关的关键代码。与主干代码不一样,这些代码更多的是隐藏在实现具体逻辑的地方,也就是watcher之中的。虽然watcher的使用接口都比较相似,但是不同的watcher,底层的数据结构和处理策略还是不一样的。下面一篇文章我们就来探索一下libev中比较常用的几种watcher的设计与实现。

文章出处:http://codingcat.net/blog/2012/10/09/libev-framework/

[转]Libev源码分析 -- 整体设计的更多相关文章

  1. Spring5源码分析(1)设计思想与结构

    1 源码地址(带有中文注解)git@github.com:yakax/spring-framework-5.0.2.RELEASE--.git Spring 的设计初衷其实就是为了简化我们的开发 基于 ...

  2. SOFA 源码分析 —— 过滤器设计

    前言 通常 Web 服务器在处理请求时,都会使用过滤器模式,无论是 Tomcat ,还是 Netty,过滤器的好处是能够将处理的流程进行分离和解耦,比如一个 Http 请求进入服务器,可能需要解析 h ...

  3. Libev源码分析09:select突破处理描述符个数的限制

    众所周知,Linux下的多路复用函数select采用描述符集表示处理的描述符.描述符集的大小就是它所能处理的最大描述符限制.通常情况下该值为1024,等同于每个进程所能打开的描述符个数. 增大描述符集 ...

  4. 精尽 MyBatis 源码分析 - 整体架构

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  5. Libev源码分析01:Libev中的监视器结构(C结构体实现继承)

    在Libev的源码中,用到了一种用C实现类似C++中继承的技巧,主要是用宏和结构体实现. 在Libev中,最关键的数据结构就是各种监视器,比如IO监视器,信号监视器等等.这些监视器的多数成员都是一样的 ...

  6. Fresco 源码分析 —— 整体架构

    Fresco 是我们项目中图片加载专用框架.虽然我不是负责 Fresco 框架,但是由本人负责组里的图片加载浏览等工作,因此了解 Fresco 的源码有助于我今后的工作,也可以学习 Fresco 的源 ...

  7. Spark ML源码分析之一 设计框架解读

    本博客为作者原创,如需转载请注明参考           在深入理解Spark ML中的各类算法之前,先理一下整个库的设计框架,是非常有必要的,优秀的框架是对复杂问题的抽象和解剖,对这种抽象的学习本身 ...

  8. zepto源码分析·整体架构

    代码数量 1.2.0版本代码量为1650行,去掉注释大概1500左右 代码模块 默认版本只包括核心模块,事件模块,ajax模块,form模块和ie模块,其它模块需要自行拓展加入,其中form模块只包含 ...

  9. Libev源码分析08:Libev中的内存扩容方法

    在Libev中,如果某种结构的数组需要扩容,它使用array_needsize宏进行处理,比如: array_needsize (int, fdchanges, fdchangemax, fdchan ...

随机推荐

  1. javascript系列之DOM(二)

    原文:javascript系列之DOM(二) 原生DOM扩展 我们接着第一部分来说,上文提到了两种常规的DOM操作:创建文档片段和遍历元素节点.我们知道那些雨后春笋般的库,有很大一部分工作就是提供了一 ...

  2. 随手记UIKit Dynamics

    以今年的优势WWDC品行,我记得一些明年的元素.一些博客上找到了新的功能没有被记录.认为iOS 8全力以赴.iOS 7该属性不随手记录为时已晚 :) 参考WWDC 2013的Session Video ...

  3. 如何为ios酷我音乐盒下载导出的音乐文件(使用Java程序设计)

    这个工具已经准备第二版,读者了解编程软件,可以直接使用,请阅读和使用这个场地 http://blog.csdn.net/jzj1993/article/details/44459983 本文所涉及内容 ...

  4. hdu To and Fro

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1200 代码: #include <stdio.h> #include <string ...

  5. windows编ffmpeg2.2.4和插件h265

    0.前言 据说新出来了h265的视频,在迅雷看看上面看到的.网上查看了一下简单介绍,貌似h265的视频比h264的视频压缩率要高.并且能做4K的视频. 同一时候看到网上有人试过ffmpeg在编译的时候 ...

  6. 为WebBrowser的WEB页的Document注册事件的问题

    原文:为WebBrowser的WEB页的Document注册事件的问题 当使用WebBrowser,并对其装载的Web页建立Document的事件后,WebBrowser里的页面元素都变得难于操作了, ...

  7. 左右lcm,gcd一些性质

    两个整数a,b  他们的最大公约数为n  最小公倍数为m  则有 a,b都能分解为有限个素数的积               12 = 2^2 * 3^1 * 5^0 , 30 = 2^1 * 3^1 ...

  8. POJ1320 Street Numbers【佩尔方程】

    主题链接: http://poj.org/problem?id=1320 题目大意: 求解两个不相等的正整数N.M(N<M),使得 1 + 2 + - + N = (N+1) + - + M.输 ...

  9. 【Android开发日记】第一个任务Android Service!Service靴+重力感应器+弹出窗口+保持执行

    前言: 近期在写一个小程序,需求是手机摇一摇就弹窗出来.第一次使用了Service,学习了两天,实现了Service弹窗,开机启动,Service启动和销毁,Service保持一直执行. 满足了自己的 ...

  10. GIMP也疯狂之动态图的制作(二)

    首先看下效果: (素材丢失,无法提供) 所用工具:GIMP.GIMP-GAP(在源中直接搜索安装) 文后会添加一个从U2B上搬运过来的视频教程,效果不错,值得一看本想也制作个人物变换,但几次实验,相同 ...