[记录]libev代码分析[初稿,内容不完善]


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


评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值