`
xidajiancun
  • 浏览: 455461 次
文章分类
社区版块
存档分类
最新评论

libev源代码分析---Libev源代码结构

 
阅读更多

Libev源代码结构

对于毕业生,尤其是没有接触过一些已有工程代码的新人。拿到一份源码,怎么去熟悉它是首要解决的问题。我一般把会把源码进行分类:一类是产品类的,就比如Redis、Ngnix这一类本身是一个完整的可以运维的成熟产品;另一类就是Libev这样的组件类的。对于组件类的项目,我一般就是分成这样几步:

  1. 有文档看文档,没有文档问相关人员(包括Google),这个组件主要提供什么服务
  2. 结合上述信息使用组件的AIP写个示例程序,跑起来
  3. 大致浏览下源码,分析一下代码的组织结构
  4. 根据使用的API,进到源码中看看主干是怎么样实现的,从而了解整体思路
  5. 再搜刮源码,把一些辅助的功能看下,并在例子中尝试
  6. 之后将整个理解用文字记录下来。提炼两大块内容:实现思想和技巧tips

这里我对Libev的学习就是依照这样的一个逻辑一步一步走的。

ev.c代码结构

“使用Libev” 这篇文章中提到了一个Libev的官方文档,并根据该文档写了个简单的示例,包括了IO事件、定时器事件以及信号事件这3个最常用的事件类型。在本篇文章中将对Libev的代码结构进行分析。

首先下载Libev的源码包,下载回来后进行解压,Libev的源码都放在同一个目录中,除去autoconfig产生的文件,代码文件还是比较直观的。主要的.c和.h文件从命名上也查不多能猜出来干嘛呢。根据我们的例子,主要抽出其中的"ev.c ev_epoll.c ev_select.c ev.h ev_wrap.c ev_vars.c"结合我们的例子进行梳理。

“ev_epoll.c"和"ev_select.c"是对系统提供的IO复用机制“epoll”、“select"的支持,还有"poll”、“kqueue” Solaris的"port"的支持,分别是"ev_poll.c”、“ev_kqueue.c”、“ev_port.c”。具体的框架是类似的,因此只要分析一个其他的就都了解了。

“ev.h” 是对一些API和变量、常量的定义,“ev.c"是Libev的主要逻辑,其中在类型的定义的时候用了一个宏的包装来声明成员变量,在文件"ev_vars.c”
中。为了对成员变量使用的语句进行简化,就又写了一个"ev_wrap.c”。因此我们可以这样去看待这些文件,主要逻辑都在"ev.c”,其中部分常量、变量的定义可以在"ev.h"中,有个结构的成员变量部分的定义在"ev_vars.c"中,同时对该结构成员变量的引用通过"ev_wrap.c"文件做了个简写的宏定义;当需要系统提供底层的事件接口时,按分类分别在"ev_epoll.c”、“ev_select.c"等文件中。

接着打开"ev.c"文件,“ev.h"里面的各种定义,在需要的时候去查询即可,通过IDE或者Vim/Emacs结合cscope/ctag都可以很好的解决。通过浏览可以发现这些代码大概可以分成三部分:

代码结构

因此可以直接跳到代码部分。分隔点有ecb结束的注释。这可以不用担心略过的部分,等需要的时候回过去查阅即可。其中ecb的部分,只要知道其API作用即可,无需深究,如果未来需要的时候可以到这边来做一个参考。

对于逻辑结构可以可以把他分成几个部分:
逻辑结构

这样对整体的布局有个大概的了解,就可以有选择性的逐个突破了。这里还可以结合官方的文档去了解下每个函数作用。从而对Libev的整体提供的服务有个大概的了解。

主要数据结构

浏览的过程中梳理下几个重要的数据结构

1.时间类型

1 typedef double ev_tstamp;

2.坑爹的 EV_XX_

Libev用ev_tstamp表示时间单位,其实质就是一个double类型变量。

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

这里的定义还是比较让人无解的。“EV_XXX” 等同于 EV_XXX,,这样在后续的API使用中,会显的更简洁一些,比如针对第一个参数是struct ev_loop *loop 的回调函数的书写,就可以写成 · void io_action(EV_P ev_io *io_w,int e)· 。这里不知道作者还有没有其他用以,这里我不是很推荐,但是要知道,后面再看代码的时候才更容易理解。

3.各种watcher

基类

首先看一个ev_watcher,这个我们可以用OO思想去理解他,他就相当于一个基类,后续的ev_io什么的都是派生自该机构,这里利用了编译器的一个“潜规则”就是变量的定义顺序与声明顺序一致。这一点在libuv里面也用了,然后大神云风哥还对其吐槽了一番,可以参见云风的blog。这里我尽量吧所有宏包裹的部分都拨出来,方便理解和看。看过Libev的代码,我想在惊叹其宏的高明之余一定也吐槽过。

1 typedef struct ev_watcher
2 {
3 int active;
4 int pending;
5 int priority;
6 void *data;
7 void (*cb)(struct ev_loop *loop, struct ev_watcher *w, int revents);
8 } ev_watcher;

与基类配套的还有个装监控器的List。

1 typedef struct ev_watcher_list
2 {
3 int active;
4 int pending;
5 int priority;
6 void *data;
7 void (*cb)(struct ev_loop *loop, struct ev_watcher_list *w, int revents);
8 struct ev_watcher_list *next;
9 } ev_watcher_list;
IO监控器
01 typedef struct ev_io
02 {
03 int active;
04 int pending;
05 int priority;
06 void *data;
07 void (*cb)(struct ev_loop *loop, struct ev_io *w, int revents);
08 struct ev_watcher_list *next;
09
10 int fd; /* 这里的fd,events就是派生类的私有成员,分别表示监听的文件fd和触发的事件(可读还是可写) */
11 int events;
12 } ev_io;

在这里,通过从宏中剥离出来后,可以看到将派生类的私有变量放在了共有部分的后面。这样,当使用C的指针强制转换后,一个指向 struct ev_io对象的基类 ev_watcher 的指针p就可以通过 p->active 访问到派生类中同样表示active的成员了。

定时器watcher
01 typedef struct ev_watcher_time
02 {
03 int active;
04 int pending;
05 int priority;
06 void *data;
07 void (*cb)(struct ev_loop *loop, struct ev_watcher_time *w, int revents);
08
09 ev_tstamp at; /* 这个at就是派生类中新的自有成员 ,表示的是at时间触发 */
10 } ev_watcher_time;

这里定时器事件watcher和IO的不一样的地方在于,对于定时器会用专门的最小堆去管理。而IO和信号等其他事件的监控器则是通过单链表挂起来的,因此他没有next成员。

信号watcher
01 typedef struct ev_signal
02 {
03 int active;
04 int pending;
05 int priority;
06 void *data;
07 void (*cb)(struct ev_loop *loop, struct ev_signal *w, int revents);
08 struct ev_watcher_list *next;
09
10 int signum; /* 这个signum就是派生类中新的自有成员 ,表示的是接收到的信号,和定时器中的at类似 */
11 } ev_signal;

还有其他的事件watcher的数据结构也是和这个类似的,可以对着"ev.h"的代码找一下,这里不再赘述了。最后看一个可以容纳所有监控器对象的类型:

01 union ev_any_watcher
02 {
03 struct ev_watcher w;
04 struct ev_watcher_list wl;
05 struct ev_io io;
06 struct ev_timer timer;
07 struct ev_periodic periodic;
08 struct ev_signal signal;
09 struct ev_child child;
10 struct ev_stat stat;
11 struct ev_idle idle;
12 struct ev_prepare prepare;
13 struct ev_check check;
14 struct ev_fork fork;
15 struct ev_cleanup cleanup;
16 struct ev_embed embed;
17 struct ev_async async;
18 };

4.最重要的 ev_loop

在上面就已经看到了 struct ev_loop 的前向声明了,那么他到底是怎样的一个结构的?在“ev.c”里面可以看到这样的定义:

1 struct ev_loop
2 {
3 ev_tstamp ev_rt_now;
4 #define ev_rt_now ((loop)->ev_rt_now)
5 #define VAR(name,decl) decl;
6 #include "ev_vars.h"
7 #undef VAR
8 };
9 #include "ev_wrap.h"

之前说过的 “ev_vars.h"和"ev_wrap.h"是为了定义一个数据结构及简化访问其成员的,就是说的这个 ev_loop 结构体。
这里用的宏为:

1 #define VAR(name,decl) decl;
2 #define VARx(type,name) VAR(name, type name)

展开就是

1 #define VARx(type,name) type name

然后再看"ev_vars.h” ,里面都是 类型-变量的 VARx的宏,这样再将其include 到结构体的定义中。这样就可以看成该结构定义为:

1 struct ev_loop
2 {
3 ev_tstamp ev_rt_now;
4 ev_tstamp now_floor;
5 int rfeedmax;
6 ... .........;
7 }

不知道作者的用意何在,目前还没有看到这样做的好处在哪里。

然后 #define ev_rt_now ((loop)->ev_rt_now) 可以和后面的 “ev_warp.h"一起看。实际上就是 #define xxx ((loop)->xxx) 这样在要用struct ev_loop 的一个实例对象loop的成员时,就可以直接写成xxx了,这里再联想到之前的 EV_P EV_P_ EV_A EV_A_ ,就会发现,在Libev的内部函数中,这样的配套就可以使代码简洁不少。不过这样也增加了第一次阅读其的门槛。相信没有看过Libev不说其晦涩的。

5.重要的全局变量

default_loop_struct

在"ev.c"中有

1 static struct ev_loop default_loop_struct;

这个就是strct loop的一个实例对象,表示的是预制事件驱动器。如果在代码中使用的是预制事件驱动器,那么后续的操作就都围绕着这个数据结构展开了。

为了操作方便,还定义了指向该对象的一个全局指针:

1 struct ev_loop *ev_default_loop_ptr

代码的框架和主要的数据结构梳理出来了,还有ANFD、ANHEAP等数据结构在后面分析具体监控器是的时候在详细介绍。后面就要跟进程序的逻辑从而了解其设计思想,这样便可以深入的了解一款组件型的开源软件了。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics