libev是一个事件驱动库,底层是基于select、epoll、kqueue等I/O复用接口。所谓事件驱动库,就是用户定义一个事件以及改事件发生时调用的函数,该库会监听该事件,并在事件发生时调用相应的函数。

libev提供了很多事件监听器(watcher),最主要的有IO、时间以及信号监听器。当某一个文件的读事件或者写事件发生时,周期时间到了时,进程接收到某个信号时,就会调用用户定义的回调函数。

下面以IO事件为例,讲述libev的工作原理:

1、实例

 #include<stdio.h>
#include <ev.h>
// 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 OK!");
// 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*/ , 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, .);
ev_timer_start (loop, &timeout_watcher); // now wait for events to arrive
ev_run (loop, );
//
// break was called, so exit
return ;
}

可以看出,libev库的使用简单、方便。我们只要定义个事件的监听器对象,初始化,开始,最后调用ev_run。不同事件监听器初始化的内容也不一样,比如,IO事件监听器需要初始化监听的文件描述符,事件以及回调函数。

2、事件监听器

typedef ev_watcher *W;
typedef ev_watcher_list *WL;
typedef ev_watcher_time *WT; typedef struct ev_watcher
{
int active;
int pending;
int priority;
void *data;
void (*cb)(EV_P_ struct type *w, int revents);
} ev_watcher; typedef struct ev_watcher_list
{
int active;
int pending;
int priority;
void *data;
void (*cb)(EV_P_ struct ev_watcher_list *w, int revents);
struct ev_watcher_list *next;
} ev_watcher_list; typedef struct ev_io
{
int active;
int pending;
int priority;
void *data;
void (*cb)(EV_P_ struct ev_io *w, int revents);
struct ev_watcher_list *next; int fd; /* ro */
int events; /* ro */
} ev_io;
ev_watcher是一个基础监听器,包括回调函数cd;监听器列表(ev_watcher_list)是在监听器的基础上添加指向下一个监听器的指针next;IO监听器是在监听器列表的基础上加上了其特有的文件描述符和事件类型。
ev_io_init (&stdin_watcher, stdin_cb, /*STDIN_FILENO*/ 0, EV_READ);该函数就是初始化stdin_watcher这个监听器的回调函数,文件描述符,以及事假类型。
3、struct ev_loop
struct ev_loop;
# define EV_P struct ev_loop *loop
# define EV_P_ struct ev_loop *loop,
# define EV_A loop
# define EV_A_ loop, //这4个宏用于形参 struct ev_loop{ //部分参数
ANFD *anfds
int anfdmax int *fdchanges
int fdchangemax
int fdchangecnt ANPENDING *pendings [NUMPRI]
int pendingmax [NUMPRI]
int pendingcnt [NUMPRI] int backend
int backend_fd
void (*backend_modify)(EV_P_ int fd, int oev, int nev)
void (*backend_poll)(EV_P_ ev_tstamp timeout)
} typedef struct
{
WL head; //ev_watcher_list *head;
unsigned char events; /* the events watched for */
unsigned char reify; /* flag set when this ANFD needs reification (EV_ANFD_REIFY, EV__IOFDSET) */
unsigned char emask; /* the epoll backend stores the actual kernel mask in here */
unsigned char unused; unsigned int egen; /* generation counter to counter epoll bugs */
SOCKET handle;
OVERLAPPED or, ow;
} ANFD; typedef struct
{
W w; //ev_watcher *w;
int events; /* the pending event set for the given watcher */
} ANPENDING;
ev_run函数主要是一个while循环,在这个while循环中不断检测各个事件是否发生,如果发生就调用其回调函数。而这个过程中,主要用到的对象就是struct ev_loop结构体对象,检测哪些事件,回调哪个函数都存放在该对象中。
struct ev_loop结构体中的字段很多,以IO事件为例介绍几个主要的:
anfds是一个数组,数组元素是结构体ANFD,ANFD有一个成员是监听器列表。数组下标是文件描述符,而列表成员是监听该文件的事件监听器。所以,anfds有点类似散列表,以文件描述符作为键,以监听器作为值,采用开链法解决散列冲突。
该字段的初始化在ev_io_start函数中,主要目的是用户定义的监听器告诉ev_loop。
fdchanges是一个int数组,也是在ev_io_start中初始化。存放的是监听了的文件描述符。这样ev_run每次循环的时候,要先从fdchanges中取出已经监听的文件描述符,再以该描述符为下标,从anfds中取出监听器对象。这样就得到文件描述符以及监听
的事件。 pendings是一个二维数组,第一维是优先级,第二维是监听器。这个数组是用于执行相应的回调函数,根据优先级,遍历所有监听器,调用监听器的回调函数。 3、ev_io_start
ev_io_start (EV_P_ ev_io *w) EV_THROW
{
ev_start (EV_A_ (W)w, ); //w->active = active;
array_needsize (ANFD, anfds, anfdmax, fd + , array_init_zero); //when fd + 1 > anfdmax : 重新分配数组大小
//anfds = (type *)array_realloc(sizeof (ANFD), (anfds), &(anfdmax), (fd + 1));
wlist_add (&anfds[fd].head, (WL)w); // w->next = *head;*head = w; fd_change (EV_A_ fd, w->events & EV__IOFDSET | EV_ANFD_REIFY); //nfds [fd].reify |= flags;
//++fdchangecnt;
//array_needsize (int, fdchanges, fdchangemax, fdchangecnt, EMPTY2);
//fdchanges [fdchangecnt - 1] = fd;
} array_realloc (int elem, void *base, int *cur, int cnt)
{
*cur = array_nextsize (elem, *cur, cnt);
return ev_realloc (base, elem * *cur);
} //分配当前数组大小的两倍内存,如果大于4096,则为4096的倍数
int array_nextsize (int elem, int cur, int cnt)
{
int ncur = cur + ; do
ncur <<= ;
while (cnt > ncur); /* if size is large, round to MALLOC_ROUND - 4 * longs to accommodate malloc overhead */
if (elem * ncur > MALLOC_ROUND - sizeof (void *) * ) //#define MALLOC_ROUND 4096
{
ncur *= elem;
ncur = (ncur + elem + (MALLOC_ROUND - ) + sizeof (void *) * ) & ~(MALLOC_ROUND - );
ncur = ncur - sizeof (void *) * ;
ncur /= elem;
} return ncur;
}

ev_io_start函数主要就是对ev_loop的anfds和fdchanges字段操作,上面已介绍。array_needsize函数实现当数组大小不够时,要重新分配内存,分配方式与stl::vector有些类似,都是新分配的内存为当前内存的2倍,然后移动原先数据到新内存,释放旧内存。

4、ev_run
ev_run (EV_P_ int flags)
{
...
do{
fd_reify (EV_A);
backend_poll (EV_A_ waittime);
if (expect_false (checkcnt))
queue_events (EV_A_ (W *)checks, checkcnt, EV_CHECK);
EV_INVOKE_PENDING;
}
while(...)
...
}
ev_run函数代码比较多,以上是以IO事件为例,进行的精简。下面是以epoll作为IO多路复用的机制进行ev_run说明
1、ev_loop对象的初始化:
ev_loop对象初始化:
、# define EV_DEFAULT ev_default_loop () 、
static struct ev_loop default_loop_struct;
EV_API_DECL struct ev_loop *ev_default_loop_ptr = ; ev_default_loop (unsigned int flags) EV_THROW
{
if (!ev_default_loop_ptr)
{
EV_P = ev_default_loop_ptr = &default_loop_struct;
loop_init (EV_A_ flags);
} return ev_default_loop_ptr;
} 、
static void noinline ecb_cold
loop_init (EV_P_ unsigned int flags) EV_THROW
{
flags = atoi (getenv ("LIBEV_FLAGS"));
#if EV_USE_IOCP
if (!backend && (flags & EVBACKEND_IOCP )) backend = iocp_init (EV_A_ flags);
#endif
#if EV_USE_PORT
if (!backend && (flags & EVBACKEND_PORT )) backend = port_init (EV_A_ flags);
#endif
#if EV_USE_KQUEUE
if (!backend && (flags & EVBACKEND_KQUEUE)) backend = kqueue_init (EV_A_ flags);
#endif
#if EV_USE_EPOLL
if (!backend && (flags & EVBACKEND_EPOLL )) backend = epoll_init (EV_A_ flags);
#endif
#if EV_USE_POLL
if (!backend && (flags & EVBACKEND_POLL )) backend = poll_init (EV_A_ flags);
#endif
#if EV_USE_SELECT
if (!backend && (flags & EVBACKEND_SELECT)) backend = select_init (EV_A_ flags);
}
EV_USE_EPOLL 、EV_USE_SELECT等宏是在调用./configure时,搜索sys/epoll.h sys/select.h等文件,如果文件存在,就将宏设置为1.
__cplusplus宏是g++编译器定义的 、
int inline_size
epoll_init (EV_P_ int flags)
{
backend_fd = epoll_create (); if (backend_fd < )
return ; fcntl (backend_fd, F_SETFD, FD_CLOEXEC); backend_mintime = 1e-; /* epoll does sometimes return early, this is just to avoid the worst */
backend_modify = epoll_modify;
backend_poll = epoll_poll; epoll_eventmax = ; /* initial number of events receivable per poll */
epoll_events = (struct epoll_event *)ev_malloc (sizeof (struct epoll_event) * epoll_eventmax); return EVBACKEND_EPOLL;//即 EVBACKEND_EPOLL = 0x00000004U
}
epoll_init返回值为非0,所以不会调用后面的poll_init、select_init。

   所以,在初始化时,调用了epoll_create初始化backend_fd。

 2、fd_reify

fd_reify (EV_P)
{
for (i = ; i < fdchangecnt; ++i)
{
int fd = fdchanges [i];
ANFD *anfd = anfds + fd; if (o_reify & EV__IOFDSET)
backend_modify (EV_A_ fd, o_events, anfd->events); //即poll_modify
}
fdchangecnt = ;
}
epoll_modify (EV_P_ int fd, int oev, int nev)
{
struct epoll_event ev; ev.data.u64 = (uint64_t)(uint32_t)fd
| ((uint64_t)(uint32_t)++anfds [fd].egen << );
ev.events = (nev & EV_READ ? EPOLLIN : )
| (nev & EV_WRITE ? EPOLLOUT : ); if (expect_true (!epoll_ctl (backend_fd, oev && oldmask != nev ? EPOLL_CTL_MOD : EPOLL_CTL_ADD, fd, &ev)))
return;
}

  在fd_reify中将andfs中监听的事件添加到backend_fd中。

  3、backend_poll

backend_poll (EV_A_ waittime); // 即epoll_poll
epoll_poll (EV_P_ ev_tstamp timeout)
{
eventcnt = epoll_wait (backend_fd, epoll_events, epoll_eventmax, timeout * 1e3);
for (i = ; i < eventcnt; ++i)
{
struct epoll_event *ev = epoll_events + i; int fd = (uint32_t)ev->data.u64; /* mask out the lower 32 bits */
int want = anfds [fd].events;
int got = (ev->events & (EPOLLOUT | EPOLLERR | EPOLLHUP) ? EV_WRITE : )
| (ev->events & (EPOLLIN | EPOLLERR | EPOLLHUP) ? EV_READ : );
fd_event (EV_A_ fd, got); //将watcher设置到loop的pending数组中
}
}
fd_event (EV_P_ int fd, int revents)
{
ANFD *anfd = anfds + fd;
if (expect_true (!anfd->reify))
fd_event_nocheck (EV_A_ fd, revents);
}
fd_event_nocheck (EV_P_ int fd, int revents)
{
ANFD *anfd = anfds + fd;
ev_io *w; for (w = (ev_io *)anfd->head; w; w = (ev_io *)((WL)w)->next)
{
int ev = w->events & revents; if (ev)
ev_feed_event (EV_A_ (W)w, ev);
}
}
ev_feed_event (EV_P_ void *w, int revents) EV_THROW
{
W w_ = (W)w;
int pri = ABSPRI (w_); if (expect_false (w_->pending))
pendings [pri][w_->pending - ].events |= revents;
else
{
w_->pending = ++pendingcnt [pri];
array_needsize (ANPENDING, pendings [pri], pendingmax [pri], w_->pending, EMPTY2);
pendings [pri][w_->pending - ].w = w_;
pendings [pri][w_->pending - ].events = revents;
}
}

在backend_poll中会调用epoll_wait,通过fd_event函数,将就绪的文件描述符对应的监听器添加到ev_loop对象的pendings字段中

  4、EV_INVOKE_PENDING

void noinline
ev_invoke_pending (EV_P)
{
pendingpri = NUMPRI; while (pendingpri) /* pendingpri possibly gets modified in the inner loop */
{
--pendingpri; while (pendingcnt [pendingpri])
{
ANPENDING *p = pendings [pendingpri] + --pendingcnt [pendingpri]; p->w->pending = ;
EV_CB_INVOKE (p->w, p->events);
}
}
}
# define EV_CB_INVOKE(watcher,revents) (watcher)->cb (EV_A_ (watcher), (revents))

EV_INVOKE_PENDING会一次调用pendings中的监听器的回调函数。

至此,ev_run大体介绍完毕。

小结:

总的来说,对于IO事件驱动,libev是先将监听器存放在一个数组,每次遍历都将监听器监听的文件描述符添加到epoll_wait进行监听,然后将eopll_wait返回的就绪描述符对应的监听器添加到pendings,最后调用pendings中监听器的回调函数。

 
												

libev实现分析的更多相关文章

  1. libev loop_init分析

    尼玛 C语言学不好真是桑心呐! 看了libev的代码有一种想死的感觉,但是还是要硬着头皮看下去,一定看完! /* initialise a loop structure, must be zero-i ...

  2. libev ev_init分析

    /* these may evaluate ev multiple times, and the other arguments at most once */ /* either use ev_in ...

  3. [转]Libev源码分析 -- 整体设计

    Libev源码分析 -- 整体设计 libev是Marc Lehmann用C写的高性能事件循环库.通过libev,可以灵活地把各种事件组织管理起来,如:时钟.io.信号等.libev在业界内也是广受好 ...

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

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

  5. Libev源码分析02:Libev中的IO监视器

    一:代码流程 在Libev中,启动一个IO监视器,等待该监视器上的事件触发,然后调用该监视器的回调函数.整个的流程是这样的: 首先调用ev_default_loop初始化struct  ev_loop ...

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

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

  7. 开源网络库的分析libev libevent nginx ....

    最经看关于网络编程的一些书,对于网络编程中的一些基本东西,开源库已经封装的很好了,但是库归根结底还是使用的基本API,所以就想着分析一下,尤其是在看了各个库的介绍以后,所以这段时间想在这个方向投入一点 ...

  8. Libev源码分析08:Libev中的信号监视器

    Libev中的信号监视器,用于监控信号的发生,因信号是异步的,所以Libev的处理方式是尽量的将异步信号同步化.异步信号的同步化方法主要有:signalfd.eventfd.pipe.sigwaiti ...

  9. Libev源码分析05:Libev中的绝对时间定时器

    Libev中的超时监视器ev_periodic,是绝对时间定时器,不同于ev_timer,它是基于日历时间的.比如如果指定一个ev_periodic在10秒之后触发(ev_now() + 10),然后 ...

随机推荐

  1. [设计模式-创建型]工厂方法(Factory Method)

    概括 名称 Factory Method 结构 动机 定义一个用于创建对象的接口,让子类决定实例化哪一个类.Factory Method 使一个类的实例化延迟到其子类. 适用性 当一个类不知道它所必 ...

  2. 最近修bug的一点感悟

    写在前面话 项目从13年1月份,现场开发,4月中旬,项目开发接近尾声,三个开发,留两个在现场,我被调回公司,5月份现场一同事离职,只有一个同事在开发,结果PM想让这一个同事承担余下的开发和bug工作, ...

  3. X86 IO端口和MMIO

    X86 IO端口和MMIO I/O作为CPU和外设交流的一个渠道,主要分为两种,一种是Port I/O,一种是MMIO(Memory mapping I/O).前者就是我们常说的I/O端口,它实际上的 ...

  4. ASP.NET MVC with Entity Framework and CSS一书翻译系列文章之第四章:更高级的数据管理

    在这一章我们将学习如何正确地删除分类信息,如何向数据库填充种子数据,如何使用Code First Migrations基于代码更改来更新数据库,然后学习如何执行带有自定义错误消息的验证. 注意:如果你 ...

  5. iscroll实现移动端下拉刷新,上拉加载更多

    js菜鸡-------自我记录 html页面: <!DOCTYPE html> <html> <head> <meta charset="UTF-8 ...

  6. Oracle-11g 中创建物化视图

    html,body { font-size: 15px } body { font-family: Helvetica, "Hiragino Sans GB", "微软雅 ...

  7. labview 调用 matlab script的神坑! Error 1050 occurred at LabVIEW

    显示变量没有被定义,原因是clear 关键字的问题,去掉即可!!! 未找到 文件路径,定位: 文件路径中不能有中文路径

  8. 主成分分析 R语言

    主成分分析(Principal Component Analysis,PCA), 是一种统计方法.通过正交变换将一组可能存在相关性的变量转换为一组线性不相关的变量,转换后的这组变量叫主成分. 原理: ...

  9. MATLAB中的微积分运算(数值&符号)

    显然这个函数是单词differential(微分)的简写,用于计算微分.实际上准确来说计算的是差商. 如果输入一个长度为n的一维向量,则该函数将会返回长度为n-1的向量,向量的值是原向量相邻元素的差, ...

  10. hdu 1536 S-Nim (简单sg函数)

    题意:首先输入K 表示一个集合的大小  之后输入集合 表示对于这对石子只能去这个集合中的元素的个数 之后输入 一个m 表示接下来对于这个集合要进行m次询问 之后m行 每行输入一个n 表示有n个堆  每 ...