libev是高性能事件循环/事件模型的网络库,并且包含大量新特性[1]。其home-pages如是介绍:
Afull-featured and high-performance eventloop that is loosely modelled after libevent, but without its limitations andbugs.
网上有大量的代码分析,我在学习libev的过程中也阅读了这些博客。对我分析libev代码有很大帮助,这里就不一一列举这些优秀的博文了。本文旨在记录学习的心得,锤炼表达能力与加深理解。
本文较为详细分析libev的实现细节。首先,通过一个例子[1]开始分析旅程吧。
// a single header file is required
#include <ev.h>
#include <stdio.h> // for puts
// every watcher type has its own typedef'dstruct
// with the name ev_TYPE
ev_io stdin_watcher;
ev_timer timeout_watcher;
// all watcher callbacks have a similarsignature
// this callback is called when data isreadable on stdin
static void
stdin_cb (EV_P_ ev_io *w, int revents)
{
puts ("stdin ready");
// for one-shot events, one must manuallystop the watcher
// with its corresponding stop function.
ev_io_stop (EV_A_ w);
// this causes all nested ev_run's to stopiterating
ev_break (EV_A_ EVBREAK_ALL);
}
// another callback, this time for atime-out
static void
timeout_cb (EV_P_ ev_timer *w, int revents)
{
puts ("timeout");
// this causes the innermost ev_run tostop iterating
ev_break (EV_A_ EVBREAK_ONE);
}
int
main (void)
{
// use the default event loop unless youhave special needs
struct ev_loop *loop = ev_loop_new(0);
// initialise an io watcher, then start it
// this one will watch for stdin to becomereadable
ev_io_init (&stdin_watcher, stdin_cb,/*STDIN_FILENO*/ 0, EV_READ);
ev_io_start (loop, &stdin_watcher);
// initialise a timer watcher, then startit
// 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的man-pages。在例stdin上注册一个可读watcher, 当stdin上有数据可读后,就会触发watcher的可读事件,相应的回调函数(例子中的回调函数是stdin_cb)就会被调用。
1)ev_loop_new
该函数返回一个ev_loop的结构体。简单分析下该结构体的成员变量,见注释。
struct ev_loop {
// 与时间相关的变量
ev_tstampev_rt_now;
ev_tstampnow_floor;
ev_tstamp mn_now;
ev_tstamprtmn_diff;
//
ev_tstampio_blocktime;
ev_tstamptimeout_blocktime;
intbackend;//IO复用机制类型,epoll, select或者其他
int activecnt;//感兴趣描述符总数
sig_atomic_t volatile loop_done;//事件循环结束标志
//描述符,linux中一切皆文件
intbackend_fd;
ev_tstampbackend_fudge;
//当在描述符上注册事件,或者修改已注册事件,均会调用该函数
void(*backend_modify)(struct ev_loop *loop, int fd, int oev, int nev);
void(*backend_poll)(struct ev_loop *loop, ev_tstamp timeout);//阻塞当前线程,等待事件
//
ANFD*anfds;//记录有关描述符的信息,每一个描述符可以有多个watcher, 每个watcher可以有不同的事件类型
int anfdmax;//是anfds数组的长度
//
ANPENDING*pendings [NUMPRI];//有事件触发的描述符集合,libev将事件具有优先级熟悉,用户可以设置优先级
intpendingmax [NUMPRI];//每一个优先级下触发描述符的最大数量
intpendingcnt [NUMPRI];//本次触发事件的描述符总数,按优先级存放
//
ev_preparepending_w;
//
structpollfd *polls;
intpollmax;
intpollcnt;
int*pollidxs;
intpollidxmax;
//使用epoll作为底层IO复用机制时,所需的变量
structepoll_event *epoll_events;
intepoll_eventmax;
int*fdchanges;//有改动的描述符
int fdchangemax;
intfdchangecnt;
//
unsignedint loop_count;
unsignedint loop_depth;
//
void*userdata;
void(*release_cb)(struct ev_loop *loop);
void(*acquire_cb)(struct ev_loop *loop);
void(*invoke_cb) (struct ev_loop *loop);
};
ev_loop_new函数首先创建一个ev_loop的结构体,并将每个成员变量初始化为0. 然后调用loop_init函数对成员变量设置默认值,其中成员变量invoke_cb设置为ev_invoke_pending函数。后文会再次介绍该函数。那libev是在那里创建epoll描述符的呢? 答案是,loop_init函数调用epoll_init函数来完成这个事情的。那我们看看epoll_init作了哪些事情吧,上代码:
int epoll_init(ev_loop *loop, int flags)
{
loop->backend_fd = epoll_create (256); //调用耳熟能详的epoll_create函数
if(loop->backend_fd < 0)
return0;
fcntl(loop->backend_fd, F_SETFD, FD_CLOEXEC);
loop->backend_fudge = 0.; /* kernel sources seem to indicate thisto be zero */
loop->backend_modify = epoll_modify;
//注册函数指针,当描述符发生变化,该函数就会被调用
loop->backend_poll =epoll_poll;//阻塞,等待事件触发
//中2个变量拥有epoll_wait函数的参数,想必都熟悉吧
loop->epoll_eventmax = 64; /* initial number of events receivable perpoll */
loop->epoll_events = (struct epoll_event *)ev_malloc (sizeof (structepoll_event) * loop->epoll_eventmax);
//返回底层IO复用机制的类型,此处是EPOLL
returnEVBACKEND_EPOLL;
}
从代码中可以看出,ev_loop_new函数,创建了ev_loop结构体,记录了事件循环所需的各种变量,并且创建了epoll描述符。
2)ev_io_init
该函数接受2个参数,ev_loop结构体和watcher。在本例中,对stdin注册的watcher是ev_iowatcher。首先,我们还是看看这个watcher的数据结构定义吧:
typedef struct ev_io
{
intactive; //若为1,表示该watcher是活动的,即没有被显示stop掉
intpending; //若为1,表示该watcher感兴趣的事件触发,但还没被处理
intpriority; //优先级
void*data; //回调函数所需的数据
void(*cb)(struct ev_loop *loop, struct ev_io *w, int revents); //回调函数
struct ev_watcher_list *next;//同一个描述符上可以注册多个watcher,这些watcher挂在一个链表中,
intfd; //描述符
intevents; //事件类型
} ev_io;
一个watcher可以注册多种有效的事件,这些事件存储在成员变量events中。对ev_io结构了解后,那ev_io_init函数的功能就很明显了。ev_io_init函数接受4个参数分别是代表iowatcher的ev_io结构体,文件描述符,回调函数,事件类型。ev_io_init的所做的事情就是根据参数初始化ev_io结构体,其他成员变量设置为默认值。该函数所作的事情较少。此时,其成员变量active 为0.
3)ev_io_start
该函数接受两参数,分别是ev_loop* loop, ev_io*watcher。该函数将已经初始化完成的watcher注册到事件循环中。当相应的事件触发后,该Watcher的回调函数被执行。这里我就不粘贴该函数内容了。这里,我主要想描述loop如何管理用户感兴趣描述符,以及描述符对应的感兴趣事件。
图 ev_loop 管理多个文件描述符,每个文件描述符可以注册多个Watcher
ev_io_start将watcher添加到fd对应的watcher链表中,然后将watcher的active字段置为1.
4) ev_loop
该函数循环调用epoll_wait函数,若有事件发生,对应的回调函数会被调用。为了简化代码分析,我将ev_loop中非ev_io类型的watcher相关的代码先人为去掉。上代码先:
void ev_run (EV_P_ int flags)
{
++loop_depth;
loop_done = EVBREAK_CANCEL;
EV_INVOKE_PENDING; /* in case we recurse, ensure ordering stays nice and clean */
do {
if (expect_false (loop_done))
break;
/* update fd-related kernel structures */
fd_reify (EV_A);
/* calculate blocking time */
{
ev_tstamp waittime = 0.;
ev_tstamp sleeptime = 0.;
/* remember old timestamp for io_blocktime calculation */
ev_tstamp prev_mn_now = mn_now;
/* update time to cancel out callback processing overhead */
time_update (EV_A_ 1e100);
++loop_count;
backend_poll (EV_A_ waittime);
/* update ev_rt_now, do magic */
time_update (EV_A_ waittime + sleeptime);
}
EV_INVOKE_PENDING;
} while (expect_true (
activecnt
&& !loop_done
&& !(flags & (EVRUN_ONCE | EVRUN_NOWAIT))
));
if (loop_done == EVBREAK_ONE)
loop_done = EVBREAK_CANCEL;
--loop_depth;
}
宏EV_INVOKE_PENDING会调用ev_loop结构体中的ev_invoke_pending函数,该函数作的事情就是,对于已经有事件触发的描述符,执行描述符对应的回调函数。在ev_run函数中,进入循环处,就执行一次ev_invoke_pending函数,是希望任何已经触发的事件尽快得到处理。
进入循环体后,fd_reify函数检查是否有新的watcher添加到ev_loop中,某个watcher被删除掉了。这个函数代码代码很简洁,就不上代码了。对于采用EPOLL机制来说,在某文件描述符上添加或者删除事件,是通过调用epoll_ctl函数来实现的。由此可见,用户向某个描述符注册了一个watcher后,该watcher所感兴趣的事件在loop循环中完成的。接下来,计数本次循环的wait_time。然后,调用backend_poll函数,参数wait_time已经计算。对于底层是epoll机制来说,backend_poll函数指针指向的是epoll_wait函数。若在wait_time时间内,所监控的文件描述符上有事件触发,则将events数据存放到ev_loop结构体的epoll_events数组中,epoll_eventmax是该数组的长度。
大家或许大概了解如何使用epoll(若不了解,建议先看看如何使用epoll, 在分析libev会比较容易),epoll_wait返回一个代表事件的数组,ev_loop也需要扫描该数组,对于每一个事件,根据其fd, 以及事件类型,将注册在fd上的watcher结构的指针,存放在ev_loop结构的pendings数组中。如果一个fd上注册了2个watcher, 那么pendings数组中会占两项,也就是说pendings是按照watcher来组织的,而不是按照fd来组织的。总之,backend_poll函数会将所有触发了的watcher添加到ev_loop结构的pendings数组中。
接下来需要作什么事情,或许大家已经猜到了吧。对,就是扫描pendings数组,调用每一个Watcher的回调函数。这个任务有ev_invoke_pending函数执行。
5)ev_io_stop
当用户调用该函数时,表示停用该Watcher。该函数首先去清除watcher的状态,即将pending置为0。通过代码可以看到,若该watcher上已触犯了一个事件,但对应的回调函数还未执行,此时peding为0后,该事件的回调函数永远不会执行了。接着,从对应的fd的watcher链表中删除该watcher, 将该watcher的active变量置为0,再调用epoll_ctl删除相应的事件。至此,该watcher的生命就结束了。
参考文献:
[1] http://software.schmorp.de/pkg/libev.html