Libev 官方文档学习笔记(1)——概述和 ev_loop

请注意这是 libev 而不是 libevent 的文章!

自从接触到 libev 之后,就深深赞同作者精简的设计理念,于是就爱上了 libev 这样简单的I/O库。此外,libev 的大小也比 libevent 小得多并且自由得多。在我个人设计的项目中,我往往用的是 libev 而不是 libevent。
可惜的是,貌似是因为 libev 是单人维护,而且不支持 Windows 等原因,并不如 libevent 甚至是 libuv 等受欢迎,国内的研究资料也并不多。

但是呢,老子是 Linux / BSD 开发者,我就喜欢!

阅读本文最好有 libevent 基础,因为很多概念和 libevent 是类似的,在这里我就不重复了。
关于 libevent 请参见我的文章:Libevent官方文档学习笔记

本文地址:https://segmentfault.com/a/1190000006173864

Reference

libev官网
libev - a high performance full featured event loop written in c
[Linux 网络编程] Libev 初步

概述

Features

ev_io:支持 Linux 的select、poll、epoll;BSD 的kqueue;Solaris 的event port mechanisms
ev_signal:支持各种信号处理、同步信号处理
ev_timer:相对事件处理
ev_periodic:排程时间表
ev_child:进程状态变化事件
ev_start:监视文件状态
ev_fork:有限的fork事件支持

时间显示

Libev 使用一个ev_tstamp数据类型来表示1970年以来的秒数,实际类型是 C 里面的double类型。

错误事件

Libev 使用三种层级的错误:

  1. 操作系统错误:调用ev_set_syserr_cb所设置的回调。默认行为是调用abort()

  2. 参数错误:调用assert

  3. 内部错误(bug):内部调用assert

全局(配置)函数

以下函数可以在任意时间调用,用于配置 libev 库:

ev_tstamp ev_time ();

返回当前的时间。

void ev_sleep (ev_tstamp interval);

休眠一段指定的时间。如果interval小于等于0,则立刻返回。最大支持一天,也就是86400秒

int ev_version_major ();int ev_version_minor ();

可以调用这两个函数,并且与系统与定义的EV_VERSION_MAJOR和EV_VERSION_MINOR作对比,判断是否应该支持该库

unsigned int ev_supported_backends ();unsigned int ev_recommand_backends ();unsigned int ev_embeddable_backends ();

返回该 libev 库支持的和建议的后端列表

void ev_set_allocator ( void *(*cb)(void *ptr, long size)throw() );

重新设置realloc函数。对于一些系统(至少包括 BSD 和 Darwin)的 realloc 函数可能不正确,libev 已经给了替代方案。

void ev_set_syserr_cb ( void (*cb)(const char *msg)throw() );

设置系统错误的 callback。默认调用perror()并abort()

void ev_feed_signal (int signum)

模拟一个signal事件出来

控制 event loops 的函数

Event loop 用一个结构体struct ev_loop *描述。Libev 支持两类 loop,一是 default loop,支持 child process event;动态创建的 event loops 就不支持这个功能

struct ev_loop *ev_default_loop (unsigned int flags);

初始化 default loops。如果已经初始化了,那么直接返回并且忽略 flags。注意这个函数并不是线程安全的。
  只有这个 loop 可以处理ev_child事件。

struct ev_loop *ev_loop_new (unsigned int flags);

这个函数是线程安全的。一般而言,每个 thread 使用一个 loop。以下说明 flag 项的各个值:

  1. EVFLAG_AUTO:默认值,常用

  2. EVFLAG_NOENV:指定 libev 不使用LIBEV_FLAGS环境变量。常用于调试和测试

  3. EVFLAG_FORKCHECK:与ev_loop_fork()相关,本文暂略

  4. EVFLAG_NOINOTIFY:在ev_stat监听中使用inotify API

  5. EVFLAG_SIGNALFD:在ev_signal监听中使用signalfd API

  6. EVFLAG_NOSIGMASK:使 libev 避免修改 signal mask。这样的话,你要使 signal 是非阻塞的。在未来的 libev 中,这个 mask 将会是默认值。

  7. EVBACKEND_SELECT:通用后端

  8. EVBACKEND_POLL:除了 Windows 之外的所有后端都可以用

  9. EVBACKEND_EPOLL:Linux 后端

  10. EVBACKEND_KQUEUE:大多数 BSD 的后端

  11. EVBACKEND_DEVPOLL:Solaris 8 后端

  12. EVBACKEND_PORT:Solaris 10 后端

void ev_loop_destroy (struct ev_loop *loop);

销毁ev_loop。注意这里要将所有的 IO 清除光之后再调用,因为这个函数并不中止所有活跃(active)的 IO。
  部分 IO 不会被清除,比如 signal。这些需要手动清除。
  这个函数一般和ev_loop_new一起出现在同一个线程中。

void ev_loop_fork (struct ev_loop *loop);

这个函数导致ev_run的子过程重设已有的 backend 的 kernel state。重用父进程创建的 loop。可以和pthread_atfork()配合使用。
  需要在每一个需要在 fork 之后重用的 loop 中调用这个函数。必须在恢复之前或者调用ev_run()之前调用。
  如果是在fork之后创建的 loop,不需要调用。
  使用 pthread 的代码例如下:

static void post_fork_chuild (void){    ev_loop_fork (EV_DEFAULT);}...pthread_atfork (NULL, NULL, post_fork_child);
int ev_is_default_loop (struct ev_loop *loop);

判断当前 loop 是不是 default loop。

unsigned int ev_iteration (struct ev_loop *loop);

返回当前的 loop 的迭代数。等于 libev pool 新事件的数量(?)。这个值对应ev_prepare和ev_check调用,并在 prepare 和 check 之间增一。

unsigned int ev_depth (struct ev_loop *loop);

返回ev_run()进入减去退出次数的差值。
  注意,导致ev_run异常退出的调用(setjmp / longjmp, pthread_cancel, 抛出异常等)均不会导致该值减一。

unsigned int ev_backend (struct ev_loop *loop);

返回EVBACKEND_*值

ev_tstamp ev_now (loop)

得到当前的“event loop time”。在 callback 调用期间,这个值是不变的。

void ev_new_update (loop)

更新从ev_now()中返回的时间。不必要的话,不要使用,因为这个函数的开销相对是比较大的。

void ev_suspend (struct ev_loop *loop);void ev_resume (struct ev_loop *loop);

暂停当前的 loop,使其刮起当前的所有工作。同时其 timeout 也会暂停。如果恢复后,timer 会从上一次暂停状态继续及时——这一点对于实现一些要连同时间也一起冻结的功能时,非常有用。
  注意已经 resume 的loop不能再 resume,反之已经 suspend 的 loop 不能再 suspend。

bool ev_run (struct ev_loop *loop, int flags);

初始化 loop 结束后,调用这个函数开始 loop。如果 flags == 0,直至 loop 没有活跃的时间或者是调用了 ev_bread 之后停止。
  Loop 可以是异常使能的,你可以在 callback 中调用longjmp来终端回调并且跳出 ev_run,或者通过抛出 C++ 异常。这些不会导致 ev_depth 值减少。
  EVRUN_NOWAIT会检查并且执行所有未解决的 events,但如果没有就绪的时间,ev_run 会立刻返回。EVRUN_ONCE会检查所有的 events,在至少每一个 event 都执行了一次事件迭代之后才返回。但有时候,使用ev_prepare/ev_check更好。
  以下是ev_run的大致工作流程:

  1. loop depth ++

  2. 重设ev_break状态

  3. 在首次迭代之前,调用所有 pending watchers

LOOP:

  1. 如果置了EVFLAG_FORKCHECK,则检查 fork,如果检测到 fork,则排队并调用所有的 fork watchers

  2. 排队并且调用所有 ready 的watchers

  3. 如果ev_break被调用了,则直接跳转至 FINISH

  4. 如果检测到了 fork,则分离并且重建 kernel state

  5. 使用所有未解决的变化更新 kernel state

  6. 更新ev_now的值

  7. 计算要 sleep 或 block 多久

  8. 如果指定了的话,sleep

  9. loop iteration ++

  10. 阻塞以等待事件

  11. 排队所有未处理的I/O事件

  12. 更新ev_now的值,执行 time jump 调整

  13. 排队所有超时事件

  14. 排队所有定期事件

  15. 排队所有优先级高于 pending 事件的 idle watchers

  16. 排队所有 check watchers

  17. 按照上述顺序的逆序,调用 watchers (check watchers -> idle watchers -> 定期事件 -> 计时器超时事件 -> fd事件)。信号和 child watchers 视为 fd watchers。

  18. 如果ev_break被调用了,或者使用了EVRUN_ONCE或者EVRUN_NOWAIT,则如果没有活跃的 watchers,则 FINISH,否则 continue

未完待续……

关键字:linux, c, 异步编程, 异步io


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部