ev_loop相关函数

libev的事件循环对象由struct ev_loop定义.

ev_default_loop

struct ev_loop *ev_default_loop (unsigned int flags);

此函数将返回”默认“的ev_loop对象并初始化, 如果您不知道使用哪个事件循环, 请使用这个函数返回的ev_loop对象(或通过EV_DEFAULT宏).

如果ev_loop已经被初始化, 那么再(多)次调用都只会简单返回同一个对象(即使flags不同). 如果尚未初始化, 那么将会根据flags创建它.

注意: 此函数不是线程安全的. 所以, 要在多线程使用的时候必须加上互斥锁保证操作的原子性(虽然这种情况极少). 同时”缺省“的ev_loop对象才可以处理ev_child.

例子

if (!ev_default_loop (0))
    fatal ("could not initialise libev, bad $LIBEV_FLAGS in environment?");

限制libev仅使用select与poll后端同时忽略环境配置的示例:

ev_default_loop (EVBACKEND_POLL | EVBACKEND_SELECT | EVFLAG_NOENV);

flags参数的描述会在ev_loop_new中会详细说明.

ev_loop_new

struct ev_loop *ev_loop_new (unsigned int flags);

此函数创建并初始化一个新的事件循环对象.如果不能初始化循环, 则返回NULL. 函数的调用是线程安全的. 通常我们会为每一个线程创建一个ev_loop, 在主线程中使用”缺省/默认“的ev_loop.

flags参数可以用来指定要使用的特殊行为或特定后端, 而通常情况下可以被指定为0(或EVFLAG_AUTO 宏);

以下是flags支持的标志位:

flags 说明
EVFLAG_AUTO 默认标志位. 如果您不知道如何选择, 那么最好选择使用它.
EVFLAG_NOENV 默认情况下, libev会在环境中寻找该环境变量并且覆盖其它特殊设置. 如果指定了此标志位, libev则不会再使用LIBEV_FLAGS环境变量. 这个标志配置对于开发期间的性能测试、Bug检查等配置尤为有用.
EVFLAG_FORKCHECK 通过设置此标志位让libev在每次事件迭代中检查fork; 通常使用的是getpid来进行检查, 这可能会因为内核(系统)的不同对迭代速度有些许影响. 优点则是无需再为fork检查担心.
注意: 此标志不能被LIBEV_FLAGS重写或指定.
EVFLAG_NOINOTIFY 当指定了此标志位, ev_stat不再尝试使用inotify来进行检查. 启用inotify则可以让ev_stat保存inotify的句柄(handle), 这通常能减少内部消耗.
EVFLAG_SIGNALFD 当指定了此标志位, libev将使用signalfd的API来优化ev_signal实现信号处理. 这能串行化处理信号数据, 简化线程间的信号处理. 默认情况下signalfd不会被使用, 因为这会改变你的信号掩码.
EVFLAG_NOSIGMASK 当指定了此标志位, libev将避免修改信号掩码. 这意味着当你想接收信号时它们不会被阻塞.
当您希望自己处理信号或希望在特定的线程中处理信号, 它将变得非常有用.
EVFLAG_NOTIMERFD 当指定了此标志位, libev将不会使用timerfd来检查时间. 虽然libev仍能检查时间, 但是这会需要花费更多的时间.
当前会在第一个周期定时器创建的时候开始使用timerfd, 如果因为各种原因失败, 则会退回到其它方法中完成.
EVBACKEND_SELECT 使用标准的select(2)后端, 但是libev会尝试自己调整fd_set以达到避免fds数量限制. 如果失败, 那么使用select后端对fd的监控数量会非常低且它非常低效(O(highest_fd)). 不过, 在监视少量文件描述符事件的后端中它通常是最快的.
此后端将EV_READ映射到readfds结合上, 将EV_WRITE映射到writefds集合.
EVBACKEND_POLL 使用标准的poll(2)后端, 它的复杂度比select更高, 但是能解决fd_set的稀疏数组与fds的文件描述符数量限制. 不过它在拥有大量不活跃fd的时候事件通知效率毅然很低O(total_fds).
此后端将EV_READ映射为POLLIN | POLLERR | POLLHUP, 将EV_WRITE映射为POLLOUT | POLLERR | POLLHUP.
EVBACKEND_EPOLL 使用特定于Linux的epoll(7)接口(适用于2.6之后的内核). 对于很少的fdsselectpoll稍微慢一点, 但它的扩展性则会更好. 相较于前者的O(total_fds), epoll则是O(active_fds).
此后端映射EV_READEV_WRITE的方式与EVBACKEND_POLL相同.
EVBACKEND_LINUXAIO 在4.18之后的内核中可以使用特定的Linux AIO(不是aio而是io_submit)事件接口(但libev只会在4.19中启动它). 如果这个后端可用, 那么可能值得使用它. 否则, 最好忽略回退选择使用epoll较好.
EVBACKEND_KQUEUE kqueue,Linux无此机制
EVBACKEND_DEVPOLL 未实现
EVBACKEND_PORT 不用

如果上述一个或多个后端标志被添加到标志值中,那么只有这些后端会被尝试(以相反的顺序). 如果没有指定, 那将尝试 ev_recommended_backends()中的所有后端。

这个示例尝试创建一个仅使用epoll的事件循环:

struct ev_loop *epoller = ev_loop_new (EVBACKEND_EPOLL | EVFLAG_NOENV);
if (!epoller)
    fatal ("no epoll found here, maybe it hides under your chair");

同样的, 如果可以则希望使用Linux AIO. 否则, 使用其它后端:

struct ev_loop *loop = ev_loop_new (ev_recommended_backends () | EVBACKEND_LINUXAIO);

ev_is_default_loop

int ev_is_default_loop (loop);

如果是”缺省/默认“的loop返回true, 否则返回false.

ev_iteration

unsigned int ev_iteration (loop);

返回当前loop的迭代次数.

ev_set_userdata

void ev_set_userdata (loop, void data);

设置与获取loop的用户自定义对象(void* data), 这通常用来让loop携带一些特殊的对象(上下文).

ev_verify

ev_verify (loop);

这个函数根据EV_VERIFY宏在内部做一些健壮性、可靠性的检查与验证, 如果发现错误会立即抛出错误消息并调用abort().

这通常在开发、调试期间尤为有用, 有利于协助我们排查问题. 而在生产环境中最好避免使用, 过多的检查会影响整体性能.

ev_break

ev_break (loop, how);

可以用来调用ev_run提前返回(但必须在处理完所有未处理的事件之后).

  • how为EVBREAK_ONE会返回一层ev_run嵌套.

  • how为EVBREAK_ALL会返回所有ev_run嵌套.

ev_run返回之后再次调用ev_run则会清除break state.

ev_run(外部)调用ev_break不会产生任何影响.

ev_backend

unsigned int ev_backend (loop);

返回正在使用的后端标志位.

ev_run

ev_run (loop, int flags);

此函数通常在初始化完成所有的观察者并且想开始处理事件之后被调用. 它将向操作系统询问任何新事件<->调用观察者回调, 然后无限期地重复这个过程.

如果flags参数为0,它将在内部持续处理事件,直到不再有事件处于活动状态或主动调用ev_break. 如果没有更多活动的观察者, 那么此函数将会返回. 请注意, 显式调用ev_break来停止事件循环通常是最好的方式.

  • EVRUN_ONCE: 查找并处理任何已触发(pending)但未完成的事件. 在至少处理了1个事件后ev_run将会直接返回.

  • EVRUN_NOWAIT:查找并检查所有事件, 在经过一次迭代后如果没有已触发(pending)但未完成的事件则ev_run返回.

下面则是ev_run内部大致的运行流程(不保证将来不会改变):

  • 递增loop深度.

  • 重置ev_break状态.

  • 在第1次迭代之前,调用所有pending中的观察者。

开始循环(LOOP):

  • 如果定义了EVFLAG_FORKCHECK宏, 则每次都检查fork.

  • 如果检测到了fork, 则根据队列调用所有fork watcher.

  • 根据队列调用所有prepare watcher.

  • 如果调用了ev_break, 则直接结束事件循环.

  • 如果fork已经被调用, 分离并且重新创建内核状态避免进程干扰.

  • 更新未修改的内核状态.

  • 更新event loop time (ev_now()).

  • 如果有必要, 计算休眠和阻塞时间(EVRUN_NOWAIT或者没有活跃的观察者则不会导致sleep).

  • 如果指定了I/O休眠时间, 则这里会执行.

  • 递增循环迭代计数器.

  • 阻塞进程等待事件来来临.

  • 根据队列调用所有活跃I/O事件

  • 更新event loop time (ev_now()) 避免时间跳跃.

  • 根据队列处理超时定时器(ev_timer).

  • 根据队列处理周期定时器(ev_periodic).

  • 处理具有高优先级的idle事件.

  • 根据队列处理所有(ev_check).

  • signalasyncchild 被作为I/O观察者实现并串行化执行.

  • 如果调用ev_breakflagsEVRUN_ONCEEVRUN_NOWAIT或没有活跃事件, 则结束事件循环. 否则重复上述步奏.

结束(FINISH):

  • 根据情况重置EV_BREAK的状态.

  • 递减loop深度

  • ev_run返回.