libev 学习笔记之主体事件循环

来源:互联网 发布:甲醛无色无味 知乎 编辑:程序博客网 时间:2024/05/17 06:40

一. epoll简介

作为linux下的IO多路复用神器,一经问世便得到了众多程序员的赞赏,其出色的绑定 fd 监听事件能力使其在有 x 个事件同时触发时能够在O(x)时间内处理到激活的事件,此非select和poll所能比。
在linux环境下,使用epoll作为libev的后端支持是极好的选择。

二. libev中对epoll的封装

1. ev_epoll.c文件

libev能够在编译时依赖系统的宏定义来自动选择恰当的后端事件循环支撑。linux下,首选epoll。libev对epoll进行了一次简单的封装,代码位于ev_epoll.c文件。

2. 主要使用的epoll相关函数

  1. epoll_create

    epoll_create() returns a file descriptor referring to the new epoll instance. This file descriptor is used for all the sub‐sequent calls to the epoll interface. When no longer required, the file descriptor returned by epoll_create() should be closed by using close(2). When all file descriptors referring to an epoll instance have been closed, the kernel destroys the instance and releases the associated resources for reuse.

    一言以蔽之: 这个系统调用会在创建一个epoll实例,返回一个与该实例相关连的 epfd 供后续epoll类函数调用

  2. epoll_ctl

    This system call performs control operations on the epoll(7) instance referred to by the file descriptor epfd. It requests that the operation op be performed for the target file descriptor, fd.

    一言以蔽之: 这个系统调用用来往 epoll_create 返回的 epfd 相关联的epoll实例内 添加/修改/删除监听事件

  3. epoll_wait

    The epoll_wait() system call waits for events on the epoll(7) instance referred to by the file descriptor epfd. The memory area pointed to by events will contain the events that will be available for the caller. Up to maxevents are returned by epoll_wait(). The maxevents argument must be greater than zero.

    一言以蔽之: 此系统调用用来等待通过 epoll_ctl 往 epfd 相关的 epoll 实例内添加的事件就绪

3. epoll封装函数分析

a. epoll_modify

函数原型: void epoll_modify (EV_P_ int fd, int oev, int nev)
函数说明: 用来往epoll中添加或修改 fd关联的事件
参数说明: EV_P_宏留在下文介绍,fd 即为往epoll中待添加或修改事件所关联的fd,oev为old event,nev为new event
功能实现: 调用epoll_ctl来实现添加/修改
核心代码:

struct epoll_event ev;.../* store the generation counter in the upper 32 bits, the fd in the lower 32 bits */ev.data.u64 = (uint64_t)(uint32_t)fd              | ((uint64_t)(uint32_t)++anfds [fd].egen << 32);ev.events   = (nev & EV_READ  ? EPOLLIN  : 0)              | (nev & EV_WRITE ? EPOLLOUT : 0);if (expect_true (!epoll_ctl (backend_fd, oev && oldmask != nev ? EPOLL_CTL_MOD : EPOLL_CTL_ADD, fd, &ev)))    return;

可以看到,待添加或修改的事件相关联的 fd 被设置在struct epoll_event结构体的data.u64的低32位,后续代码中会使用同样的方式来获取就绪事件关联的 fd。另外,值得关注的是此处的epoll只关注EPOLLINEPOLLOUT两种事件

b. epoll_poll

函数原型: void epoll_poll (EV_P_ ev_tstamp timeout)
函数说明: 设定超时时间为timeout,等待所监听的事件就绪
参数说明: timeout,单位为s,类型为double
功能实现: 调用epoll_wait来实现事件监听

c. epoll_init

函数原型: int epoll_init (EV_P_ int flags)
函数说明: 生成epoll系列函数所需的与epoll实例相关的 epfd,初始化相关参数
参数说明: flags,未使用
功能实现: 调用epoll_create来创建 epfd

d. epoll_destroy

函数原型: void epoll_destroy (EV_P)
函数说明: 释放空间

e. epoll_fork

函数原型: void epoll_fork (EV_P)
函数说明: 关闭原有 epfd,生成新的 epfd,清空老的epfd上管理的所有 fd上监听的事件

4. 多事件循环机制

a. EV_P系列宏定义:

/* support multiple event loops? */#if EV_MULTIPLICITYstruct ev_loop;# define EV_P  struct ev_loop *loop               /* a loop as sole parameter in a declaration */# define EV_P_ EV_P,                              /* a loop as first of multiple parameters */# define EV_A  loop                               /* a loop as sole argument to a function call */# define EV_A_ EV_A,                              /* a loop as first of multiple arguments */# define EV_DEFAULT_UC  ev_default_loop_uc_ ()    /* the default loop, if initialised, as sole arg */# define EV_DEFAULT_UC_ EV_DEFAULT_UC,            /* the default loop as first of multiple arguments */# define EV_DEFAULT  ev_default_loop (0)          /* the default loop as sole arg */# define EV_DEFAULT_ EV_DEFAULT,                  /* the default loop as first of multiple arguments */#else# define EV_P void# define EV_P_# define EV_A# define EV_A_# define EV_DEFAULT# define EV_DEFAULT_# define EV_DEFAULT_UC# define EV_DEFAULT_UC_# undef EV_EMBED_ENABLE#endif

可见,libev支持多个事件循环,这在多线程环境下能够支持经典的多线程并发模型per thread per loop。而在不至此多时间循环时,EV_P系列宏定义为空,即此时在编译时,原有的函数都将自动去掉第一个struct ev_loop *loop使得其成为仅支持单时间循环的函数。

b. ev_loop 结构

#if EV_MULTIPLICITY  struct ev_loop  {    ev_tstamp ev_rt_now;    #define ev_rt_now ((loop)->ev_rt_now)    #define VAR(name,decl) decl;      #include "ev_vars.h"    #undef VAR  };  #include "ev_wrap.h"  static struct ev_loop default_loop_struct;#else  EV_API_DECL ev_tstamp ev_rt_now = 0; /* needs to be initialised to make it a definition despite extern */  #define VAR(name,decl) static decl;    #include "ev_vars.h"  #undef VAR                                                                                                                             static int ev_default_loop_ptr;#endif

由以上宏定义可知:

  • 在可使用多事件循环机制的情况下,会定义出struct ev_loop结构体,并且注意到,在该结构体内借由#include "ev_vars.h"机制定义了诸多的成员变量,之后再通过#include "ev_wrap.h"来将诸多成员变量使用宏重定义
  • 在不可使用多事件循环机制的情况下,则是直接通过#include "ev_vars.h"机制来将之前定义在struct ev_loop结构体内的成员变量直接定义为全局变量

这样做是何意图,相信读者心中早已明了。就是为了使libev相关函数,即支持多事件循环机制(ev_vars.h里的所有变量均为struct ev_loop成员变量)的编译又支持单事件循环机制(ev_vars.h里的所有变量均为全局变量)的编译。而struct ev_loop的成员访问方式和全局变量的访问方式必定不同,libev又是如何解决这个问题的呢?

5. ev_wrap.h文件

ev_wrap.h文件中使用宏重定义了许多struct ev_loop *loop结构体的成员,借以实现支持多事件循环(ev_vars.h里的所有变量均为struct ev_loop成员变量)和支持单事件循环(ev_vars.h里的所有变量均为全局变量)两种编译模式。

其中部分定义如下:

...#define anfdmax ((loop)->anfdmax)#define anfds ((loop)->anfds)#define async_pending ((loop)->async_pending)#define asynccnt ((loop)->asynccnt)#define asyncmax ((loop)->asyncmax)#define asyncs ((loop)->asyncs)#define backend ((loop)->backend)#define backend_fd ((loop)->backend_fd)#define backend_mintime ((loop)->backend_mintime)#define backend_modify ((loop)->backend_modify)#define backend_poll ((loop)->backend_poll)...

可见,如此一来,对于支持多事件循环(ev_vars.h里的所有变量均为struct ev_loop成员变量)的libev而言,在libev与loop相关函数中便可使用struct ev_loop成员变量的变量名来直接操作;而对于不支持多事件循环的libev(ev_vars.h里的所有变量均为全局变量)而言,使用相同的变量名即为直接操作全局变量。

因此,在libev函数中遇到“未定义”的变量直接使用的情况,多半是作为struct ev_loop的成员变量(支持多事件循环机制)或全局变量(不支持多事件循环机制)来使用。

6. 主体事件循环

经过上述分析,主体事件循环流程可谓显而易见。即通过不断的epoll_wait来实现事件等待及分发。通过支持多事件循环的struct ev_loop结构或不支持多事件循环的众多全局变量来实现loop中所有监听事件的管理。

0 0
原创粉丝点击