首页 > 代码库 > libev 默认事件循环初始化的解析

libev 默认事件循环初始化的解析

libev第一次进入的是默认的事件循环,这里将源码中执行的默认循环流程解析一下,要进入事件循环,如下例子

intmain (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); 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, 0.); ev_timer_start (loop, &timeout_watcher); // now wait for events to arrive ev_run (loop, 0); // break was called, so exit return 0;}

其中ev_loop上一节已经介绍过,这里EV_DEFAULT是一个宏定义,它是一个函数如下

struct ev_loop * ev_default_loop (unsigned int flags) EV_THROW{  if (!ev_default_loop_ptr)    {#if EV_MULTIPLICITY      EV_P = ev_default_loop_ptr = &default_loop_struct;#else      ev_default_loop_ptr = 1;#endif      loop_init (EV_A_ flags);      if (ev_backend (EV_A))        {#if EV_CHILD_ENABLE          ev_signal_init (&childev, childcb, SIGCHLD);          ev_set_priority (&childev, EV_MAXPRI);          ev_signal_start (EV_A_ &childev);          ev_unref (EV_A); /* child watcher should not keep loop alive */#endif        }      else        ev_default_loop_ptr = 0;    }  return ev_default_loop_ptr;}

 

首先执行 struct ev_loop *main_loop = ev_default_loop(0); 通过跟进代码可以跟到函数 ev_default_loop 里面去,其主要逻辑,就是全局对象指针ev_default_loop_ptr若为空,也就是不曾使用预制的驱动器时,就让他指向全局对象default_loop_struct,同时在本函数里面统一用名字"loop"来表示该预制驱动器的指针。从而与函数参数为 EV_P 以及 EV_A的写法配合。接着对该指针做 loop_init操作,(这里要注意的是init函数里的一些backend等变量全部是全局变量如#define backend ((loop)->backend)这样定义的)即初始化预制的事件驱动器。这里函数的调用了就是用到了 EV_A_ 这样的写法进行简化。初始化之后如果配置中Libev支持子进程,那么通过信号监控器实现了子进程监控器。这里可以先不用去管他,知道这段代码作用即可。 这里再Libev的函数定义的时候,会看到 “EV_THROW” 这个东西,这里可以不用管它,他是对CPP中"try … throw"的支持,和 EV_CPP(extern "C" {)这样不同寻常的 extern “C” 一样是一种编码技巧。现在我们以分析设计思路为主。在了解了总体后,可以再对其编码技巧进行梳理。否则的话看一份代码会非常吃力,而且速度慢。甚至有的时候这些“hacker”并不一定是有益的。

下面看下驱动器的初始化过程中都做了哪些事情。首先最开始的一段代码判断系统的clock_gettime是否支持CLOCK_REALTIME和CLOCK_MONOTONIC。这两种时间的区别在于后者不会因为系统时间被修改而被修改,详细解释可以参考man page 。接着判断环境变量对驱动器的影响,这个在官方的Manual中有提到,主要就是影响默认支持的IO复用机制。接着是一连串的初始值的赋值,开始不用了解其作用。在后面的分析过程中便可以知道。接着是根据系统支持的IO复用机制,对其进行初始化操作。这里可以去"ev_epoll.c” 和"ev_select.c"中看一下。 最后是判断如果系统需要信号事件,那么通过一个PIPE的IO事件来实现,这里暂且不用管他,在理解了IO事件的实现后,自然就知道这里他做了什么操作。

对于"ev_epoll.c” 和"ev_select.c"中的 xxx_init 其本质是一致的,就像插件一样,遵循一个格式,然后可以灵活的扩展。对于epoll主要就是做了一个 epoll_create*的操作(epoll_create1可以支持EPOLL_CLOEXEC)。

backend_mintime = 1e-3; /* epoll does sometimes return early, this is just to avoid the worst */backend_modify  = epoll_modify;backend_poll    = epoll_poll;

这里就可以看成是插件的模板了,在后面会修改的时候调用backend_modify在poll的时候调用backend_poll.从而统一了操作。

epoll_eventmax = 64; /* initial number of events receivable per poll */epoll_events = (struct epoll_event *)ev_malloc (sizeof (struct epoll_event) * epoll_eventmax)

这个就看做为是每个机制特有的部分。熟悉epoll的话,这个就不用说了。

对于select (Linux平台上的)

backend_mintime = 1e-6;backend_modify  = select_modify;backend_poll    = select_poll;

这个和上面一样,是相当于插件接口

vec_ri  = ev_malloc (sizeof (fd_set)); FD_ZERO ((fd_set *)vec_ri);vec_ro  = ev_malloc (sizeof (fd_set));vec_wi  = ev_malloc (sizeof (fd_set)); FD_ZERO ((fd_set *)vec_wi);vec_wo  = ev_malloc (sizeof (fd_set));

同样,这个是select特有的,表示读和写的fd_set的vector,ri用来装select返回后符合条件的部分。其他的如poll、kqueue、Solaris port都是类似的,可以自行阅读。

其中的EV_A_和EV_P定义如下:

struct 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 */

 

这样EV_P就可以作为函数的参数传入,如unsigned int ev_backend (EV_P) EV_THROW,而EV_A可以作为函数调用时传入,ev_backend (EV_A)。这里应该也是作者的技巧。总而言之就是操作的一个全局变量struct ev_loop * loop以及对它的参数进行赋值或者初始化。

libev 默认事件循环初始化的解析