libev I/O事件

libev是来实现reactor模式,主要包含三大部分:

1. watcher:watcher是Reactor中的Event Handler。

作用:1)向事件循环提供了统一的调用接口(按类型区分)

          2)它是外部代码的注入口,维护着具体的watcher信息,如:绑定的回调函数,watcher的优先级,是否激活等。

定义位置:在ev.h中可以看到各种watcher的定义,如:ev_io, ev_timer等

2.ev_loop:充当一个Reactor的角色,是事件循环的上下文环境,像树干一样串起了各个叶子watcher。

定义位置:1)ev_loop定义在ev.c文件中

                 2)ev_vars.h定义了ev_loop的各种属性

                 3)ev_wrap.h定义了与loop相关的各种宏

watcher的管理:在ev_loop中watcher按各自类型进行分类存储

3.ev_run:执行事件的循环引擎,即reactor的select方法,是树的主体部分。

作用:1)通过像ev_run传入一个ev_loop实例,便可以开启一个事件循环

          2)ev_run实际为do-while循环,在循环期间检查loop中注册的各种watcher事件。若事件就绪,就会触发相应的watcher,循环持续直至遇到ev_break或没有就绪的watcher

 

对于libev中的I/O事件的触发来说,也遵循上述所说。

1.I/O事件的watcher定义

typedef struct ev_io
{
EV_WATCHER_LIST (ev_io)

int fd; //文件描述符
int events; //事件类型
} ev_io;

ev_io是一种具体的watcher,它有两个自己专有的成员变量fd和events。在ev_io中有部分参数是从EV_WATCHER_LIST (ev_io)继承而来

#define EV_WATCHER_LIST(type)           
  EV_WATCHER (type)              
  struct ev_watcher_list *next;
#define EV_WATCHER(type)            \
  int active;  //active 表示当前watcher是否被激活。ev_TYPE_start调用后置位,ev_TYPE_stop调用后复位
  int pending;//表示当前watcher有事件就绪,等待处理。pending的值其实就是当前watcher在pendings队列中的下标;
  EV_DECL_PRIORITY //是当前watcher的优先级;
  EV_COMMON  // 在前面有定义为 *data;data: 附加数据指针,用来在watcher中携带额外所需的数据;
  EV_CB_DECLARE (type)//cb:是事件触发后的回调函数定义。

代码定义中嵌套了一些基类和其他一些宏定义,这里直接写出来,方便理解。ev_io的watcher的完整参数如下:

typedef struct ev_io
{
    int active; 
    int pending;
    int priority;
    void *data; 
    void (*cb)(struct ev_loop *loop, struct ev_io *w, int revents);
    struct ev_watcher_list *next;
  
    int fd;     /* 这里的fd,events就是派生类的私有成员,分别表示监听的文件fd和触发的事件(可读还是可写) */
    int events; 
} ev_io;

2、ev_loop

  struct ev_loop
  {
    ev_tstamp ev_rt_now;//其中ev_tstamp 就是时间单位,实际上就是double类型 
#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 *, epoll_eperms)
VARx(int, epoll_epermcnt)
VARx(int, epoll_epermmax)
#endif
VARx(int, backend_fd)// 对于epoll来说,就是epoll使用的fd
VARx(ev_tstamp, backend_mintime) /* assumed typical timer resolution */
//对于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)//anfd 数组的大小

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

ANFD和fd一一对应,结构体ANFD如下(在ev.c中进行定义)

typedef struct
{
  WL head;//关注同一个fd的事件的watcher的链表,一个fd可以有多个watcher监听它的事件
  unsigned char events; /* 所监视的事件*/ 
  unsigned char reify;  /* 标志位,用来标记ANFD需要被重新实例化(EV_ANFD_REIFY, EV__IOFDSET) */
  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;

ANFD表示事件循环中对一个文件描述符fd的监视的基本信息结构体

3 ev_run

在ev_run之前还有有如下几个需要关注的步骤,进行IO事件注册

1)IO事件的初始化和设置(在ev.h中进行定义)

#define ev_io_init(ev,cb,fd,events)          do { ev_init ((ev), (cb)); ev_io_set ((ev),(fd),(events)); } while (0)//初始化
#define ev_io_set(ev,fd_,events_)            do { (ev)->fd = (fd_); (ev)->events = (events_) | EV__IOFDSET; } while (0)//设置

2) ev_io_start:每当有新的IO监视器fd加入,调用wlist_add()添加到anfds[fd]的链表head中。

void noinline
ev_io_start (EV_P_ ev_io *w) EV_THROW//# define EV_P  struct ev_loop *loop    # define EV_P_ EV_P,  
{
  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);//将参数w表示的watcher激活(w->active=1)
  array_needsize (ANFD, anfds, anfdmax, fd + 1, array_init_zero);
  wlist_add (&anfds[fd].head, (WL)w);//将watcher w 加入到 w所关注的fd在anfds[](loop中)中相应的位置处的结构体ANFD中的watcher list链表中。

  /* common bug, apparently */
  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所关注的fd加入到int *fdchanges数组中。
  w->events &= ~EV__IOFDSET;

  EV_FREQUENT_CHECK;
}

3)fd_change:如果一个anfds的元素监控条件发生改变,如何修改这个元素的监控条件呢。anfds的下标可以用fd来表示,这里有一个新的数组,数组元素内容是新添加的要监视的IO事件的fd或者修改监视内容的fd,数组名是fdchanges,也是动态数组。这个数组记录了新加入fd或者修改的fd的值,具体实现函数为“fd_change”inline_size void

fd_change (EV_P_ int fd, int flags)
{
  unsigned char reify = anfds [fd].reify;
  anfds [fd].reify |= flags;//标志,表示fd监视条件被修改了
 
  if (expect_true (!reify))//如果fd最初的监视条件为空,表示新加入的fd
    {
      ++fdchangecnt;//fd计数器加一
      array_needsize (int, fdchanges, fdchangemax, fdchangecnt, EMPTY2);//调整fdchanges数组大小
    //添加到fdchanges数组中 fdchanges [fdchangecnt - 1] = fd; } //如果不是新加入的fd,则fdchanges数组中已经有fd了。表示以前添加过对fd的IO监视 }

一个新的IO事件注册时,首先是进行ev_io_start函数,通过事件的文件描述符fd,调用wlist_add()添加到anfds[fd]的链表head中。若fd的监控条件有改动了(监控条件初始为空则为新加入的时间),则在fdchanges数组中记录下该fd,fdchanges数组中包含所有事情的fd,在后续的步骤中调用系统的接口修改对该fd监控的条件。

 

 

 

3) ev_run

a.调用“fd_reify”:遍历fdchanges数组,如果发现fd的监视条件发生变化了,就会调用epoll_ctl()函数来改变fd的监视状态。

fdchanges数组记录了anfds数组中的watcher监控条件可能被修改的文件描述符,并在适当的时候将调用系统的epoll_ctl或则其他文件复用机制修改系统监控的条件。

inline_size void
fd_reify (EV_P)
{
  int i;
for (i = 0; i < fdchangecnt; ++i) { int fd = fdchanges [i]; //遍历取出可能改变监控条件的fd,得到anfds中的下标 ANFD *anfd = anfds + fd;// ev_io *w;//定义一个ev_io指针 unsigned char o_events = anfd->events; unsigned char o_reify = anfd->reify; anfd->reify = 0; /*if (expect_true (o_reify & EV_ANFD_REIFY)) probably a deoptimisation */ { anfd->events = 0; for (w = (ev_io *)anfd->head; w; w = (ev_io *)((WL)w)->next) //获得fd全部的新的监控事件集合,存放在events成员变量中 anfd->events |= (unsigned char)w->events; if (o_events != anfd->events)//如果新监控事件和旧监控事件不同, o_reify = EV__IOFDSET; /* actually |= *///修改标志位,表示fd监控条件改变 } if (o_reify & EV__IOFDSET)//fd监控条件改变,调用backend_modify也就是epoll_ctl()修改fd的监控条件 backend_modify (EV_A_ fd, o_events, anfd->events); } fdchangecnt = 0;//一次遍历完成,fdchanges数组个数清零 }

 

 

b)time_update 更新时间,校准时间(后续再补充)

c) 调用backend_poll(loop, waittime):对于使用epoll的系统来说,它实际上是调用ev_epoll.c 中的epoll_poll()函数,执行wait 操作:

eventcnt = epoll_wait (backend_fd, epoll_events, epoll_eventmax, timeout * 1e3);

成功的话,返回了响应事件的个数,然后执行了fd_event()(ev_epoll.c文件调用,在ev.c中定义函数)

 

inline_speed void
fd_event (EV_P_ int fd, int revents)
{
  ANFD *anfd = anfds + fd;
  // ! 逻辑反操作
  if (expect_true (!anfd->reify))////reify是0,取反即为1 走进if条件里面
      /*如果reify是0,则表示我们添加了新的事件在fd上*/
    fd_event_nocheck (EV_A_ fd, revents);
}

fd_event_nocheck:

inline_speed void
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)//对fd上的监视器依次做检测,
    {
      int ev = w->events & revents;//相应的事件的条件

      if (ev)//pending条件满足,监控器加入到pendings数组中pendings[pri]上的pendings[pri][old_lenght+1]的位置上
        ev_feed_event (EV_A_ (W)w, ev);
    }
}

ev_feed_event

void noinline
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 - 1].events |= revents;
  else
    {
      w_->pending = ++pendingcnt [pri];
      array_needsize (ANPENDING, pendings [pri], pendingmax [pri], w_->pending, EMPTY2);
      pendings [pri][w_->pending - 1].w      = w_;
      pendings [pri][w_->pending - 1].events = revents;
    }

  pendingpri = NUMPRI - 1;
}

 fd_event做一个监控条件是否改变的判断后到fd_event_nocheck里面对anfds[fd]数组中的fd上的挂的监控器依次做检测,如果pending条件符合,便通过ev_feed_event将该监控器加入到pendings数组中pendings[pri]上的pendings[pri][old_lenght+1]的位置上。这里要介绍一个新的数据结构,他表示pending中的wather也就是监控条件满足了,但是还没有触发动作的状态。

typedef struct
{
  W w;
  int events; /* the pending event set for the given watcher */
} ANPENDING;

pendings就是这个类型的一个二维数组数组。其以watcher的优先级为一级下标,再以该优先级上pengding的监控器数目为二级下标,对应的监控器中的pending值就是该下标加一的结果。其定义为 ANPENDING *pendings [NUMPRI]。同anfds一样,二维数组的第二维 ANPENDING *是一个动态调整大小的数组。

以上这个一系列的操作可以认为是fd_reify的后续操作,xxx_reify目的最后都是将pending的watcher加入到这个pengdings二维数组中。 这里用个图梳理下结构

 

 

d)time_update 再次更新校准时间

timers_reify(loop) 相对时间定时器,如果最近的定时器已经触发过了,则重新选出下一个最近的定时器,将其置于堆顶。(具体分析见Libev——ev_timer 相对时间定时器

periodics_reify(loop) 绝对时间定时器   和timers处理差不多
idle_reify(loop) 
 
e) EV_INVOKE_PENDING 从loop的pending数组中依次调用各个watcher的回调函数,优先级从高到低
 
/* 遍历pendings二维数组,执行事件触发回调函数 */
void noinline
ev_invoke_pending (EV_P)
{
  pendingpri = NUMPRI;//#define NUMPRI (EV_MAXPRI - EV_MINPRI + 1)

  while (pendingpri) /* pendingpri possibly gets modified in the inner loop 外层while循环是按优先级递减*/
    {
      --pendingpri;

      while (pendingcnt [pendingpri])
  //内层while循环同一优先级的ANPENDING按数组顺序进行遍历,遍历到的每一个ANPENDING都对应一个watcher,得到watcher之后就执行该watcher对应的回调函数。
        {
          ANPENDING *p = pendings [pendingpri] + --pendingcnt [pendingpri];//这个是在干吗,触发回调函数了吗

          p->w->pending = 0;//pending的值其实就是当前watcher在pendings队列中的下标,这里将pending值置0即为优先级放到最低
          EV_CB_INVOKE (p->w, p->events);
          EV_FREQUENT_CHECK;
        }
    }

 

f) 如果当前loop中还有被激活的watcher,并且loop_done ==0 并且启动ev_run(flags)的参数没有设置EVRUN_ONCE和EVRUN_NOWAIT,则继续从1开始。
 
posted @ 2021-11-29 21:04  木易白  阅读(656)  评论(0编辑  收藏  举报