libev 源码浅析

news/2024/7/9 18:39:38 标签: c/c++, epoll, 数据结构与算法

libev是一个开源的事件驱动库,基于epoll,kqueue等OS提供的基础设施。其以高效出名,它可以将IO事件,定时器,和信号统一起来,统一放在事件处理这一套框架下处理。

 libev的基本使用方法如下:

 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);// 设置对stdin_watcher这个fd关注读事件,并指定回调函数
     ev_io_start (loop, &stdin_watcher);// 激活stdin_watcher这个fd,将其设置到loop中

     // initialise a timer watcher, then start it
     // simple non-repeating 5.5 second timeout
     ev_timer_init (&timeout_watcher, timeout_cb, 5.5, 0.);//设置一个定时器,并指定一个回调函数,这个timer只执行一次,5.5s后执行
     ev_timer_start (loop, &timeout_watcher);//激活这个定时器,将其设置到loop中

     // now wait for events to arrive
     ev_run (loop, 0);//循环开始

     // break was called, so exit
     return 0;
   }

libev中有一个抽象概念,叫做watcher(ev_watcher),libev中有各种各样的watcher,比如定时器watcher(struct ev_timer),I/O watcher(struct ev_io) , 信号watcher(struct ev_signal)

等等。这三个具体的watcher相当于父类watcher的子类。这种继承关系在C++这种高级语言中已内置,在C中实现就需要一些技巧,libev的作者使用了宏。

"父类"ev_watcher定义如下:

typedef struct ev_watcher
{
   EV_WATCHER(ev_watcher)
} ev_watcher;

宏EV_WATCHER定义如下:

/* shared by all watchers */
 #define EV_WATCHER(type)      \                                                                                                                                                           
    int active; /* private */     \     //该watcher是否被激活,加入到loop中
    int pending; /* private */      \   //该watcher关注的events是否已触发
    EV_DECL_PRIORITY /* private */    \ //int priority; 优先级,watcher是有优先级的
    EV_COMMON /* rw */        \         // void *data;   
    EV_CB_DECLARE (type) /* private */  // void (*cb)(struct ev_loop *loop, type *w, int revents);回调函数

再看一个"父类"ev_watcher_list的定义:

 typedef struct ev_watcher_list
 {
   EV_WATCHER_LIST (ev_watcher_list)
 } ev_watcher_list; 

宏EV_WATCHER_LIST定义如下:

#define EV_WATCHER_LIST(type)     \                                                                                                                                                       
   EV_WATCHER (type)       \
   struct ev_watcher_list *next; /* private */

可以看出,ev_watcher_list 其实也是ev_watcher的一个"子类", 它多了一个成员变量 struct  ev_watcher_list *next;

这个成员变量用于将watcher串起来。

 现在看一个I/O watcher 这个最重要的"子类":

 typedef struct ev_io
 {
   EV_WATCHER_LIST (ev_io)
 
   int fd;     /* ro */ // 显而易见,与io相关联的fd
   int events; /* ro */ // 这个watcher在fd上关注的事件
 } ev_io;

可以看出,ev_io是一种具体的watcher,它有两个自己专有的成员变量fd和events

下面看一下最关键的一个数据结构:

   struct ev_loop
   {
     ev_tstamp ev_rt_now;
     #define ev_rt_now ((loop)->ev_rt_now)
     // 这里decl是declare的意思,ev_vars.h 里面会定义一堆的变量,这些变量
     // 都是本结构的成员,ev_vars.h展开的时候会用到下面这一行VAR的宏
     #define VAR(name,decl) decl;
       #include "ev_vars.h"
     #undef VAR
   };

ev_vars.h中包含很多关键的成员,比如:

epoll相关的成员变量:

#if EV_USE_EPOLL || EV_GENWRAP
VARx(struct epoll_event *, epoll_events)  // 相当于struct epoll_event *epoll_events                                            
VARx(int, epoll_eventmax) //目前epoll_events数组的大小,可以扩充,每次以2倍的大小扩充
VARx(int, backend_fd) // 对于epoll来说,就是epoll使用的fd
//对于epoll来说,实际的函数是ev_epoll.c中的epoll_modify函数,这个函数会执行epoll_ctl
VAR (backend_modify, void (*backend_modify)(EV_P_ int fd, int oev, int nev))
//对于epoll来说,实际的函数是ev_poll.c中的epoll_poll函数,这个函数会执行epoll_wait VAR (backend_poll , void (*backend_poll)(EV_P_ ev_tstamp timeout))

与fd相关的成员变量:

VARx(ANFD *, anfds)//这个数组是以fd为索引
VARx(int, anfdmax) //上面数组的大小
VARx(int *, fdchanges) // fdchangemax大小的数组,每个元素是一个fd,这个数组中存了所有epoll需要poll的fd
VARx(int, fdchangemax) //数组的容量
VARx(int, fdchangecnt) // 数组中实际的元素的大小

ANFD和fd一一对应,结构体ANFD如下:

 typedef struct
 {
   WL head; // typedef ev_watcher_list *WL; 关注同一个fd的事件的watcher的链表,一个fd可以有多个watcher监听它的事件
   unsigned char events; /* the events watched for */ // watcher链表中所有watcher关注的事件的按位与
   unsigned char reify;  /* flag set when this ANFD needs reification (EV_ANFD_REIFY, EV__IOFDSET) */ //当这个结构体需要重新epoll_ctl则设置,说明关注的事件发生了变化
   unsigned char emask;  /* the epoll backend stores the actual kernel mask in here */ //实际发生的事件
   unsigned char unused;
 #if EV_USE_EPOLL
   unsigned int egen;    /* generation counter to counter epoll bugs */
 #endif
 #if EV_SELECT_IS_WINSOCKET || EV_USE_IOCP
   SOCKET handle;
 #endif
 #if EV_USE_IOCP
   OVERLAPPED or, ow;
 #endif
 } ANFD;

维护所有的"所关注的事件发生了的watcher",这些watcher的callback最后都需要被调用

VAR (pendings, ANPENDING *pendings [NUMPRI])  //watcher是有优先级的,libev为每个优先级的watcher维护一个数组
VAR (pendingmax, int pendingmax [NUMPRI]) // 每个优先级watcher数组的容量
VAR (pendingcnt, int pendingcnt [NUMPRI]) //  每个优先级watcher数组实际大小

struct ANPENDING如下:

typedef struct
{
  W w; //typedef ev_watcher *W;
  int events; /* the pending event set for the given watcher */  //events只指这个watcher关注了的并且已经发生了的还没有处理的事件
} ANPENDING;

 

从示例程序可以看出,使用libev主要有几个方法:

ev_io_init

ev_io_start

ev_timer_init

ev_timer_start

ev_run

一个个看:

ev_io_init是个宏,主要就是设置ev_io中的各个成员

void ev_io_start(struct ev_loop *loop, ev_io *w) 会做如下几件事情:

1.  将参数w表示的watcher激活(w->active=1)

2.  将watcher w 加入到 w所关注的fd在anfds[](loop中)中相应的位置处的结构体ANFD中的watcher list链表中。

3.  将w所关注的fd加入到int *fdchanges数组中。

void ev_run(struct ev_loop *loop, int flags)最重要的函数,做如下几件事:
1. 调用fd_reify。
    遍历fdchanges数组:针对其中的每个fd,做如下事情:
    将关注这个fd的所有的watcher都从ANFD的链表中取出来(通过fd去anfds[]中找到相应的ANFD结构体),然后将所有这些watcher关注的events通过epoll_ctl 给设置到
    kernel中。然后清空fdchanges数组,其实就是将fdchangecnt置为0。
2. time_update 更新时间,校准时间
3. 计算给epoll_wait()使用的timeout,从维护所有的timer的最小堆中取堆顶(timers [HEAP0]),然后减去当前时间得到timeout。最小堆使用一个数组实现的,每个元素是
struct ANHE。
    timers定义如下:
    
VARx(ANHE *, timers)

  struct ANHE定义如下:

/* a heap element */
typedef struct {
   ev_tstamp at;//timer watcher 到期时间
   WT w;// typedef ev_watcher_time *WT;
} ANHE;
typedef struct ev_watcher_time
{
   EV_WATCHER_TIME (ev_watcher_time)
} ev_watcher_time;

#define EV_WATCHER_TIME(type) \ 
 EV_WATCHER (type) \
 ev_tstamp at; /* private */

4. 调用backend_poll(loop, waittime)会做如下几件事:

    对于使用epoll的系统来说,它实际上是调用ev_epoll.c 中的epoll_poll()函数,这个函数主要流程如下:

    4.1. 调用epoll_wait()
    4.2. 遍历每个返回的 struct epoll_event,取出fd,和epoll_event中激活的事件,调用fd_event (loop, fd, got)函数。
            这个函数会更新loop的pending数组,将那些已经关注了一些事件的watcher,并且确实发生了的这些watcher给设置到loop的pending数组中
            注意这里,got是所有watcher关注的事件的总和。ANPENDING结构体中的events仅仅单个watcher关注的events。
5. time_update 再次更新校准时间
6. timers_reify(loop) 如果最近的定时器已经触发过了,则重新选出下一个最近的定时器,将其置于堆顶。
7.periodics_reify(loop) 和timers处理差不多
8. idle_reify(loop) 没关注
9. EV_INVOKE_PENDING 从loop的pending数组中依次调用各个watcher的回调函数,优先级从高到低
10.如果当前loop中还有被激活的watcher,并且loop_done ==0 并且启动ev_run(flags)的参数没有设置EVRUN_ONCE和EVRUN_NOWAIT,则继续从1开始。
 
到目前为止,已经将timer和I/O事件进行统一了,现在看信号是如何统一到事件处理框架中的。
看看signal watcher的定义:
 
/* invoked when the given signal has been received */                                                                                                                        
/* revent EV_SIGNAL */
typedef struct ev_signal
{
   EV_WATCHER_LIST (ev_signal)
   int signum; /* ro */ //信号id
 } ev_signal;

ANSIG signals [EV_NSIG - 1]; // 每个信号的信息用一个ANSIG结构体表示
typedef struct
{
  EV_ATOMIC_T pending;
#if EV_MULTIPLICITY
  EV_P;
#endif
  WL head;//可以有多个watcher关注同一个信号,使用链表串起来
}ANSIG;
loop中和信号相关的主要成员如下:
 
VAR (evpipe, int evpipe [2])// 用于将信号处理和事件处理框架结合在一起,evpipe[0]用于读,evpipe[1]用于写
VARx(ev_io, pipe_w) //这个I/O ev就是用于封装上面的pipe的读端,让epoll监听这个pipe_w,当接收到信号的时候,只需要在信号处理函数中往evpipe[1]中写即可

  

和I/O事件,timer事件类似,有如下两个过程:
ev_signal_init : 宏,初始化这个ev_signal结构体
void ev_signal_start(struct ev_loop *loop, ev_signal *w)做如下几件事:
1. 激活ev_signal这个watcher(w->active = 1)
2. 将w串到signals数组中相应位置的watcher list中
3. 初始化一对pipe,这对pipe就是用于将信号和事件处理框架结合起来,并且注册一个ev_io pipe_w到epoll中,pipe_w封装的是int evpipe[2]的读端evpipe[0]
4. 注册这个信号的信号处理函数ev_sighandler,信号处理函数会将signals数组中相应的槽位的pending置1,意思是已接收到这个信号,并且往int evpipe[2]的写端
evpipe[1]写入一个字节,这样在pipe_w这个IO event就有了读事件,从而epoll_wait()返回,成功的将信号和事件处理框架结合起来。
 
 
至此,libev如何将IO 事件,timer事件和信号统一到事件处理框架下已分析完成。可以看出,libev和libevent的做法是一样的,只是实现不一样,比如,关于继承的实现,libev使用宏来实现相当的难读,但是这样做是高效的,借用霸爷的话说:"在libev的世界中,效率第一"。这可能正是大多数人说,libev比libevent代码难读的原因
 
 
 
 
 
 
 

参考资料: 

http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod

转载于:https://www.cnblogs.com/foxmailed/archive/2013/02/04/2891077.html


http://www.niftyadmin.cn/n/1655982.html

相关文章

Disruptor:一种高性能的、在并发线程间数据交换领域用于替换有界限队列的方案

一、摘要 LMAX 计划创建一个高性能的财务交易系统。作为我们工作的一部分,我们评估了多种方案去设计这个系统以求达到高性能目标,最后我们发现在传统的解决方案中遇到了基础上的瓶颈。 许多应用程序通过队列来在不同的处理阶段之间交换数据。我们的性能…

Google Jib 容器化构建工具

一、前言 随着近些年的技术发展,Java 领域微服务已经成为主流的技术方向。随着微服务化,云原生的概念也逐渐火热起来,不了解云原生仿佛就是一个原始人。而在云原生中,应用容器化 是其核心属性之一。 应用容器化,用抽…

1.01 与 0.99 的法则,Python 实现。

首先我们需要介绍一个库,他叫mpmath ,一种无限精度浮点运算库。通过他我们可以简单地得到这个题的高精度答案,直接看代码吧。 from mpmath import * #载入库 mp.dps 1000; mp.pretty True #设置精度 print power(1.01, 365) …

谷歌 已拦截不安全内容_谷歌Chrome将在未来几个月阻止不安全的下载

站长之家(ChinaZ.com) 2月11日 消息:谷歌透露了一些计划,最初警告Chrome用户关于“不安全”的下载,最终彻底封杀它们。Chrome安全团队的Joe DeBlasio在博客中宣布,Chrome将逐步确保安全(HTTPS)页面只下载安全文件。下载不安全的文件会对用户的…

Protobuf 在 Java 中的入门实例

Protobuf1 是一种语言中立、平台无关、可扩展的序列化数据的格式,可用于通信协议,数据存储等。 本文将演示在 Java 语言中如何编写一个 Protobuf 的入级程序,也许你可能并不了解 Protobuf,这没有关系,基于 Protobuf 官…

poj 2060(最小路径覆盖)

1 // File Name: 2060.cpp2 // Author: Missa3 // Created Time: 2013/2/11 星期一 17:11:304 5 //最小路径覆盖数顶点数-二分图最大匹配数6 #include<iostream>7 #include<cstdio>8 #include<cstring>9 #include<algorithm> 10 #include<cmath>…

python打包xp系统_windowx xp sp3平台下Python 2.6.4打包方法

搜索下Python应用程序打包的方式有两种 一.用py2exe打包 该程程序只能支持windows下的打包&#xff0c;打包完成后是一个分发目录&#xff0c; 里面有很依赖库和你的程序 1.安装 py2exe-0.6.9.win32-py2.6.exe 下载Py2exe程序要选择相应的python版本 2.编写setup.py文件 …

Java 编程下 CyclicBarrier 中的线程等待

CyclicBarrier 是 java.util.concurrent.CyclicBarrier 下的一个同步辅助类&#xff0c;它允许一组线程互相等待&#xff0c;直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中&#xff0c;这些线程必须不时地互相等待&#xff0c;此时 Cyclic…