Intro
Libev是一个基于Reactor模式的事件库,效率较高(Benchmark)并且代码精简(4.15版本8000多行),是学习事件驱动编程的很好的资源。
本文不会介绍Reactor模式,也不会介绍Libev的API,主要内容是我学习libev后的一些总结,介绍了Livev的设计方法和实现方法,并对一部分核心代码进行了注解。
如需更详尽的API介绍,可以参见Libev的手册。
Feature
Libev是一个用C编写的功能齐全的高性能的轻量级事件驱动库,其支持多种后台IO复用接口,并且可以注册多达十几种事件。
支持的后台IO复用接口:
1
2
3
4
5
|
select
poll
epoll
kqueue
solaris event port
|
支持的事件类型:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
ev_io
ev_stat
ev_signal
ev_timer
ev_periodic
ev_child
ev_fork
ev_cleanup
ev_idle
ev_embed
ev_prepare
ev_check
ev_async
|
Sample
在介绍Libev的代码结构之前,先看一个Libev手册中自带的例子,其注册了两个事件,一个timeout事件,一个io事件,任何一个事件发生后都会调用回调函数并终止主循环。这也是事件驱动编程的标准模式——注册事件后等待其触发并调用回调函数。
代码中的注释已经十分详细,我就不再赘述。要编译这段代码(evtest.c),首先在Libev主页下载Libev源码编译安装,然后使用gcc evtest.c -libev
编译(记得ldconfig)。
evtest.c
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
|
#include <ev.h>
#include <stdio.h>
ev_io stdin_watcher;
ev_timer timeout_watcher;
static
void
stdin_cb (EV_P_ ev_io *w,
int revents)
{
puts (
"stdin ready");
ev_io_stop (EV_A_ w);
ev_break (EV_A_ EVBREAK_ALL);
}
static
void
timeout_cb (EV_P_ ev_timer *w,
int revents)
{
puts (
"timeout");
ev_break (EV_A_ EVBREAK_ONE);
}
int
main (
void)
{
struct ev_loop *loop = EV_DEFAULT;
ev_io_init (&stdin_watcher, stdin_cb,
0, EV_READ);
ev_io_start (loop, &stdin_watcher);
ev_timer_init (&timeout_watcher, timeout_cb,
5.5,
0.);
ev_timer_start (loop, &timeout_watcher);
ev_run (loop,
0);
return
0;
}
|
Main Structures
Wather
事件驱动库中,很重要的一部分就是对事件的封装。在Libev中,事件被封装在Watcher结构体中,通过注册Watcher并指定对应的回调函数等参数,就可以将事件添加到主循环中。
先解释一下EV_P
,EV_P_
,EV_A
,EV_A_
这几个宏,在代码中几乎随处可见,主要是为了简化单线程模式下的函数调用的接口,这几个宏定义如下。
1
2
3
4
5
6
7
8
9
10
11
12
|
#if EV_MULTIPLICITY
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 */
#else
# define EV_P void
# define EV_P_
# define EV_A
# define EV_A_
#endif
|
ev_loop
是主循环,而EV_MULTIPLICITY
是一个条件编译的宏,表明是否支持有多个ev_loop
实例存在,一般来说,每个线程中有且仅有一个ev_loop
实例。如果整个程序是单线程的,程序中使用全局默认的ev_loop
即可,不需要在函数中传参。而在多线程中调用函数很多时候都要指定函数操作的loop。比如启动一个io事件,调用的函数是void ev_io_start (EV_P_ ev_io *w)
,如果没有定义EV_MULTIPLICITY
,将会编译成ev_io_start(io *w)
,否则会编译成ev_io_start(struct ev_loop *loop, ev_io *w)
。
对于每一种事件,都有结构体ev_TYPE
与之对应,比如ev_io
,ev_timer
等。为了统一事件结构,libev在C中使用结构体布局实现了多态,可以将ev_watcher
结构体看做所有ev_TYPE
结构体的基类,它包含了所有ev_TYPE
中相同的字段。
代码中对这些结构体的定义如下,为了便于理解,我对部分宏进行了还原。之所以只还原部分宏而不是全部,是因为这些宏体现了作者设计这些结构体的思路。
相关的宏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
#define EV_WATCHER(type) \
int active;
\
int pending;
\
int priority;
\
void *data;
\
void (*cb)(EV_P_
struct type *w,
int revents);
#define EV_WATCHER_TIME(type) \
EV_WATCHER (type) \
ev_tstamp at;
#define EV_WATCHER_LIST(type) \
EV_WATCHER (type) \
struct ev_watcher_list *next;
|
“基类”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
typedef
struct ev_watcher
{
EV_WATCHER (ev_watcher)
} ev_watcher;
typedef
struct ev_watcher_time
{
EV_WATCHER_TIME (ev_watcher_time)
} ev_watcher_time;
typedef
struct ev_watcher_list
{
EV_WATCHER_LIST (ev_watcher_list)
} ev_watcher_list;
|
“派生类”,这里只列举了ev_io
,ev_timer
和ev_signal
,这三种是比较常用的事件,其它事件结构的代码都差不多,具体可以见源码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
typedef
struct ev_io
{
EV_WATCHER_LIST (ev_io)
int fd;
int events;
} ev_io;
typedef
struct ev_signal
{
EV_WATCHER_LIST (ev_signal)
int signum;
} ev_signal;
typedef
struct ev_timer
{
EV_WATCHER_TIME (ev_timer)
ev_tstamp repeat;
} ev_timer;
|
从以上代码可以看出来,每个事件结构体中的共有字段表示了这个事件的状态,优先级,参数,以及回调函数,而私有字段则是该类型事件的特有信息,比如io事件有对应的fd、定时器事件有发生时间等。
另外还有一个叫做ev_any_watcher的union可以容纳所有的事件类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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;
struct ev_stat stat;
struct ev_idle idle;
struct ev_prepare prepare;
struct ev_check check;
struct ev_fork fork;
struct ev_cleanup cleanup;
struct ev_embed embed;
struct ev_async async;
};
|
ev_loop
ev_loop
是个十分重要也非常庞大的结构体,可以称其为事件控制器,事件的调度基本都是由它控制的。
该结构体的定义十分晦涩,从下面的代码可以看出,代码会根据EV_MULTIPLICITY
是否定义进行条件编译,在单线程环境下,因为只有一个loop,所以所有变量直接作为全局变量使用,而在多线程模式下会有多个loop实例,因此需要将变量封装在ev_loop
结构体中,调用函数时要指定所操作的loop。这些变量定义在ev_vars.h
中,通过include展开。
另外,在多线程模式下,定义了ev_loop
结构体之后,还include了ev_wrap.h
,这个文件中对ev_vars.h
中的所有变量定义了一堆形如#define anfds ((loop)->anfds)
的宏,这个宏的目的是为了统一代码的编写,在未开启EV_MULTIPLICITY
时anfds
表示的就是全局变量anfds
,而在开启了EV_MULTIPLICITY
后,函数一般会传一个struct ev_loop *loop
,
anfds
也会展开成((loop)->anfds)
。这使得代码中不用再写一堆的#if #else #endif
,但也让代码变的更加晦涩难懂。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#if EV_MULTIPLICITY
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
};
#include "ev_wrap.h"
static
struct ev_loop default_loop_struct;
EV_API_DECL
struct ev_loop *ev_default_loop_ptr =
0;
#else
EV_API_DECL ev_tstamp ev_rt_now =
0;
#define VAR(name,decl) static decl;
#include "ev_vars.h"
#undef VAR
static
int ev_default_loop_ptr;
#endif
|
ANFD
在管理io事件的时候,如何根据fd快速找到与其相关的事件,是一个需要考虑的问题。Libev的方法是用一个数组来存所有fd信息的结构体,然后以fd值为索引直接找到对应的结构体,这个结构体就是下面的ANFD结构体(省略了有关Windows系统的变量)。这种方法可以在O(1)复杂度内进行索引,问题是它占的空间有多少?假如我们同时开了一百万个fd,所占空间一共是10^6*sizeof(ANFD)
,大约12M左右,这完全是可以接受的。
结构体中字段的含义在后面介绍Libev流程时会逐渐提到。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
typedef ev_watcher_list *WL;
typedef
struct
{
WL head;
unsigned
char events;
unsigned
char reify;
unsigned
char emask;
unsigned
char unused;
#if EV_USE_EPOLL
unsigned
int egen;
#endif
} ANFD;
|
How it works?
这节主要介绍了libev的代码是怎样工作的,主要分为事件注册,事件调度与后台I/O复用三部分。
事件注册
事件注册,也就是告诉事件驱动器程序要关注某个事件的发生。这里以io事件为例来分析怎样去注册以及销毁一个事件,其它事件的代码逻辑基本上都是一样的,就不再赘述。
从上面Sample里的代码中我们可以看到,启动一个io事件调用了了以下这两个函数:
1
2
3
|
ev_io stdin_watcher;
ev_io_init (&stdin_watcher, stdin_cb,
0, EV_READ);
ev_io_start (loop, &stdin_watcher);
|
首先看一下ev_io_init
,与其相关的代码主要有以下几个宏。基本就是初始化了ev_io
结构体中各个字段的值,优先级会被初始化为0,如需改变需要单独调用ev_set_priority
。上面的ev_io_init
,实际上就是注册了一个关注读的IO事件,相应的fd为0也就是标准输入。
1
2
3
4
5
6
7
8
|
#define ev_io_init(ev,cb,fd,events) do { ev_init ((ev), (cb)); ev_io_set ((ev),(fd),(events)); } while (0)
#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)
#define ev_io_set(ev,fd_,events_) do { (ev)->fd = (fd_); (ev)->events = (events_) | EV__IOFDSET; } while (0)
|
然后是ev_io_start
,核心工作就是将ev_io
添加到相关fd的事件链表上去,下面是主要代码,我在代码中给出了较为详细的注释。其中noinline等都是作者因为编译器差异定义的一些宏,而EV_FREQUENT_CHECK
也是为了验证程序正确性添加的宏,在生产环境下不会生成任何内容,这些宏现在都可以忽略,我们只关心主要逻辑。
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
typedef ev_watcher *W;
typedef ev_watcher_list *WL;
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);
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;
}
inline_speed
void
ev_start (EV_P_ W w,
int active)
{
pri_adjust (EV_A_ w);
w->active = active;
ev_ref (EV_A);
}
inline_size
void
wlist_add (WL *head, WL elem)
{
elem->next = *head;
*head = elem;
}
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;
}
}
|
最后看一下ev_io_stop
,逻辑基本就是ev_io_start
的反过程。
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
|
void noinline
ev_io_stop (EV_P_ ev_io *w) EV_THROW
{
clear_pending (EV_A_ (W)w);
if (expect_false (!ev_is_active (w)))
return;
assert ((
"libev: ev_io_stop called with illegal fd (must stay constant after start!)", w->fd >=
0 && w->fd < anfdmax));
EV_FREQUENT_CHECK;
wlist_del (&anfds[w->fd].head, (WL)w);
ev_stop (EV_A_ (W)w);
fd_change (EV_A_ w->fd, EV_ANFD_REIFY);
EV_FREQUENT_CHECK;
}
inline_size
void
wlist_del (WL *head, WL elem)
{
while (*head)
{
if (expect_true (*head == elem))
{
*head = elem->next;
break;
}
head = &(*head)->next;
}
}
|
Main Loop
ev_io
系列函数所做的操作基本就是填充ev_io
结构体并将其放在对应fd的事件链表上,而将监听事件状态发生改变的fd存在fdchanges数组中,驱动控制器会根据该数组更改后台监听的事件,当事件发生时驱动控制器会自动调用相应事件的回调函数。在整个过程中,驱动控制器也就是ev_loop
就像胶水一样将事件和后台复用机制粘在一起。
整个事件的调度过程基本都在函数ev_run
中,整个函数较长,大概有两百多行,这里就不列出代码了,只把程序的主要逻辑写了出来。
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
|
int
ev_run (EV_P_
int flags)
{
...
do
{
...
...
...
...
...
backend_poll (EV_A_ waittime);
...
...
...
EV_INVOKE_PENDING;
}
while (条件成立);
}
|
ev_run
的逻辑可以说还是比较清晰的。程序首先会先执行一些需要在poll之前执行的回调,接着根据最先超时的计时器算出poll需要wait的时间,之后调用poll等待I/O事件发生,最后执行发生事件的回调。
具体的代码中,程序使用queue_events
将要运行的事件放入一个叫做pending的二维数组中,其第一维是优先级,第二维是动态分配的,存放具体事件。之后程序会在适当的地方调用宏EV_INVOKE_PENDING
,将pending数组中的事件按优先级从高到低依次执行。
I/O复用
Libev使用函数指针来实现支持多种I/O复用机制,每种复用机制要实现init, modify, poll, destroy这几个函数,也就是初始化、修改关注事件、等待事件发生、销毁这几个功能。这部分代码我只看了epoll的实现,感觉实现的还是很巧妙的,读者可以根据自己熟悉的I/O复用机制去选择看哪部分代码。
More
至此,Libev的主要设计方法和实现思路基本介绍的差不多了,限于篇幅,还有很多细节无法在一篇文章中叙述完,如果有时间的话,我会尽量完善这篇文章。
作为一个事件库,Libev的设计可以说是十分精良,代码中的各种tips也让我受益良多。但是,Libev几乎不涉及网络编程,如果要在此基础上实现网络库,还是有大量工作要做的。
摘自: http://outofmemory.cn/wr/?u=http%3A%2F%2Fc4fun.cn%2Fblog%2F2014%2F03%2F06%2Flibev-study%2F