libev源码解析——I/O模型

时间:2022-06-17
本文章向大家介绍libev源码解析——I/O模型,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

        在《libev源码解析——总览》一文中,我们介绍过,libev是一个基于事件的循环库。本文将介绍其和事件及循环之间的关系。(转载请指明出于breaksoftware的csdn博客)

        目前ibev支持如下IO事件模型:

  • select模型。对应文件是ev_select.c。
  • poll模型。对应文件是ev_poll.c。
  • epoll模型。对应的文件是ev_epoll.c。
  • port模型。对应文件是ev_port.c。
  • kqueue模型。对应的文件是ev_kqueue.c。
  • iocp模型。即IO完成端口模型(I/O Completion Port)。

        这些模型并不是我们这个系列介绍的重点。如果想了解select、poll、epoll模型,可以参阅《朴素、Select、Poll和Epoll网络编程模型实现和分析》系列博文。(下图是select模型的调用逻辑图)

        此处我们只要知道它们是libev可选的事件模型即可。至于选择什么模型。要视loop_init的入参flags。

static void noinline ecb_cold
loop_init (EV_P_ unsigned int flags) EV_THROW
{
……
#if EV_USE_IOCP
      if (!backend && (flags & EVBACKEND_IOCP  )) backend = iocp_init   (EV_A_ flags);
#endif
#if EV_USE_PORT
      if (!backend && (flags & EVBACKEND_PORT  )) backend = port_init   (EV_A_ flags);
#endif
#if EV_USE_KQUEUE
      if (!backend && (flags & EVBACKEND_KQUEUE)) backend = kqueue_init (EV_A_ flags);
#endif
#if EV_USE_EPOLL
      if (!backend && (flags & EVBACKEND_EPOLL )) backend = epoll_init  (EV_A_ flags);
#endif
#if EV_USE_POLL
      if (!backend && (flags & EVBACKEND_POLL  )) backend = poll_init   (EV_A_ flags);
#endif
#if EV_USE_SELECT
      if (!backend && (flags & EVBACKEND_SELECT)) backend = select_init (EV_A_ flags);
#endif
……
}

        backend是一个用于记录libev使用的是哪种IO模型的标记位。

        在每个模型初始化函数中,都需要指定两个模型相关的函数指针。比如epoll模型的初始化函数epoll_init中

int inline_size
epoll_init (EV_P_ int flags)
{
……
  backend_mintime = 1e-3; /* epoll does sometimes return early, this is just to avoid the worst */
  backend_modify  = epoll_modify;
  backend_poll    = epoll_poll;
……
}

        而在select模型中则是

int inline_size
select_init (EV_P_ int flags)
{
  backend_mintime = 1e-6;
  backend_modify  = select_modify;
  backend_poll    = select_poll;
……
}

        backend_mintime是需要等待事件的超时秒数;backend_modify是轮询中修改事件信息的函数。backend_poll则是等待事件的函数。libev通过上述四个变量,隔离了不同模型选择导致不同函数调用的问题。

        但是这儿需要指出的是,libev并没有将这种隔离做彻底。因为在关闭IO模型时,它仍然依靠backend的值,调用了不同函数(ev_loop_destroy中)

#if EV_USE_IOCP
  if (backend == EVBACKEND_IOCP  ) iocp_destroy   (EV_A);
#endif
#if EV_USE_PORT
  if (backend == EVBACKEND_PORT  ) port_destroy   (EV_A);
#endif
#if EV_USE_KQUEUE
  if (backend == EVBACKEND_KQUEUE) kqueue_destroy (EV_A);
#endif
#if EV_USE_EPOLL
  if (backend == EVBACKEND_EPOLL ) epoll_destroy  (EV_A);
#endif
#if EV_USE_POLL
  if (backend == EVBACKEND_POLL  ) poll_destroy   (EV_A);
#endif
#if EV_USE_SELECT
  if (backend == EVBACKEND_SELECT) select_destroy (EV_A);
#endif

        个人认为,可以在各个模型的初始化中,将其对应的销毁函数指针赋值给一个叫backend_destory的变量。这样上述代表就可以变成一行了。

        结合《libev源码解析——调度策略》的内容,我们可以用下图表达出libev运转的大体流程。

        针对上图,可能有人会问:为什么backend_poll函数需要指定超时?我们让其一直等待到有事件发生不是更好么?

        还有人会问:“符合条件的监视器”是否可以表述为“本次触发事件对应的监视器”?

        对于这些问题,我们将在之后章节给出答案。