当前位置: 首页 > 工具软件 > libev > 使用案例 >

libev学习

沈栋
2023-12-01

一. libev简介

https://hottaro.com/index.php?mid=OpenSourceLib&document_srl=7957

 

二.安装及运行

https://www.cnblogs.com/charlesblc/p/6078802.html

 

三.原理学习

1.基于reactor模式: 线程池+事件驱动(基于I/O多路复用

           reactor通过I/O多路复用机制, 监听多个事件处理器, 一旦该种事件发生, 调用对应的callback)

2.用宏实现了类似继承的功能

3.用宏和函数指针实现了类似多态的功能

http://oserror.com/backend/libev-analysis/

4.手册

https://phenix3443.github.io/notebook/c/3rd-libs/libev-manual.html

5.libev, libevent, libuv比较

https://blog.csdn.net/weixin_34198797/article/details/89701939

 

四.相关知识:

1. 各平台的IO操作API:

Linux: select, poll, epoll

Freebsd mac OS: kqueue

Solaris 10: event port

Windows: IOCP(IO complete port, 使用了重叠IO(异步IO技术, 通过线程池去完成IO操作))

2.linux上其他的API:

(1) inotify,允许监控程序打开一个独立文件描述符,并针对事件集监控一个或者多个文件,例如打开、关闭、移动/重命名、删除、创建或者改变属性。

(2) Linux 新的API signalfd、timerfd、eventfd使用说明:

https://www.linuxprobe.com/new-three-fd.html

 

五.源码学习

总结:

各个IO多路复用API的封装 ----------> (default 或者 new )loop_init(初始化了各个变量, 还绑定了pending和pipe的cb和优先级)    ------------>ev_XXX_init(设置cb, 优先级, fd, 关注的事件等等)  ---------> ev_io_start(调整anfds, 将fd加到anfds中) ----------->ev_run(更新事件结构体, 调用IO多路复用,EV_PREPARE, 将准备好的事件放入pending, 按优先级调用pending中的watcher的cb)

 

 

IO多路复用的封装:

Init():

  backend_mintime = 1e-3;

  backend_modify  = modify;

  backend_poll    = poll;

后续通过调用backend_modify 和backend_poll来选择策略, 通过宏+函数指针实现多态的感觉

Destroy(): 释放内存

Modify(): 修改fd_set or poll struct 等

Poll(): 执行select or poll or epoll 等

 

 

Ev_loop的创建:

ev_default_loop(): 全局只有static的loop, 然后调用loop_init(loop, flag)(初始化了各个变量, 还绑定了pending和pipe的cb和优先级), 以及确定了backend的enum值, 为backend相关的函数指针赋值,  线程不安全

 

ev_loop_new(): 每个线程malloc一个loop, 然后调用loop_init(loop, flag), 线程安全的

ev_loop_destroy(): 

Cleanup: queue_events()把关注的cleanup事件添加进去并pending, 然后invoke pending

Child: 如果是default loop且child watcher(其实是ev_signal)还在活跃, reference +1, ev_signal_stop()

(将该signal取消pending, 从signal的watcherList上删除, 设置并使用signalfd并从当前进程的信号屏蔽字中去掉该信号, 或 将该信号的处理函数设置为default)

Pipe: 如果pipe watcher还在活跃, 则关掉 pipe

Signalfd: 如果还在活跃, 关掉

Inotify: 关掉

Backend_fd: 关掉

多路复用的API(IOCP或EV PORT或KQUEUE或EPOLL 或POLL或SELECT): destroy

资源释放: free anfds(watcher list 等)

 

Ev_loop的执行:

ev_run(): 检查fork, prepare, 修改fd, 调用backend_poll, 检查timers, periodics, idle, check

Int ev_run (EV_P_ int flags)
{
  ...
  //一直循环....
  do
    {
      ...
      //如果这个进程是新fork出来的,执行ev_fork事件的回调
      ...
      //执行ev_prepare回调,也就是每次poll前执行的函数
      ...
      //执行监听有改变的事件,修改fd

      ...
      //计算poll应该等待的时间,这个时间和设置以及定时器超时时间有关,

min(timer, periodic)>= waittime >= max(timeout_blocktime, backend_mintime, io_blocktime)


      ...
      //调用后台I/O复用端口等待事件触发,并将触发的事件放入pending数组中

      backend_poll (EV_A_ waittime);
      ...
      //将定时器事件放入pending数组中
      ...
      //将ev_check事件翻入pending数组中
      ...
      //执行pending数组中所有的回调
      EV_INVOKE_PENDING;
    }
  while (条件成立);
}

 

ev_run的逻辑可以说还是比较清晰的。程序首先会先执行一些需要在poll之前执行的回调,接着根据最先超时的计时器算出poll需要wait的时间,之后调用poll等待I/O事件发生,最后执行发生事件的回调。

具体的代码中,程序使用queue_events将要运行的事件放入一个叫做pending的二维数组中,其第一维是优先级,第二维是动态分配的,存放具体事件。之后程序会在适当的地方调用宏EV_INVOKE_PENDING,将pending数组中的事件按优先级从高到低依次执行。

From <https://hottaro.com/index.php?mid=OpenSourceLib&document_srl=7957>

 

watcher的创建及初始化:

ev_init(): active = pending = priority = 0, set cb

ev_io_set(): set io的fd 和events

ev_io_init(): ev_init(), ev_io_set()

其他watcher类似

 

 

watcher通过start和stop添加到loop:

三大基础事件:

Ev_io:

ev_io_start(): ev_start()(设置优先级, active置为number, reference +1), 调整anfds(是anfd的list)的大小, 将this watcher视作WL插入到anfds的list的头部, 改变fd的event

ev_io_stop(): clear_pending, 将this watcher从anfds的list的头部删除, ev_stop中eference -1, 置为不活跃, 改变fd的event

 

Ev_timer:

ev_timer_start(): ev_start()(设置优先级, active置为number, reference +1), 调整timers的大小, 将this watcher视作放到heap的尾部,最小堆升序

ev_timer_stop(): clear_pending, 将堆里的最后一个元素给该位置, 然后调整堆结构, reference-1, active置为0

ev_timer_again():clear_pending, 如果active,  重设时间, 调整堆, 否则, 重新开始

ev_timer_remaining(): 返回定时器的剩余时间

 

Ev_signal:

ev_signal_start(): 如果可以使用signalfd, 最后通过ev_io实现, 如果不能则通过信号回调处理函数实现, 头插法插到signals [w->signum - 1] (底层是通过loop监控pipe的io做到的),

ev_signal_stop(): clear_pending, 将this watcher从signals [w->signum - 1]的list的头部删除, ev_stop中eference -1, 置为不活跃, 将该信号attach的loop置为0, 从signalfd的信号集中去掉该信号或将该信号的处理函数置为default

Signal 和 pipe什么关系? signal的处理函数里write_pipe(), pipe调用pipecb将signal watcher放入pending, 所以pipe只是传递一个信号而已, pipe对应的watcher优先级最高, signal watcher还是存在signals [w->signum - 1]

 

其余事件:

Ev_periodic:

ev_periodic_start(): 计算并设置watcher的at时间戳, ++periodiccnt, ev_start()(设置优先级, active置为number, reference +1), 调整periodics的大小, 将this watcher视作放到heap的尾部,最小堆升序

ev_periodic_stop(): clear_pending, --periodiccnt, 将堆里的最后一个元素给该位置, 然后调整堆结构, reference-1, active置为0

ev_periodic_again(): 先stop, 再start

 

Ev_child:

ev_child_start():ev_start()(设置优先级, active置为number, reference +1), 头插法插到childs [w->pid & ((EV_PID_HASHSIZE) - 1)]

ev_child_stop():clear_pending, 将this watcher从childs [w->pid & ((EV_PID_HASHSIZE) - 1)]的list的头部删除, ev_stop中eference -1, 置为不活跃

实现:

Ev_child是在ev_default loop()里向loop里添加了子进程信号的signal watcher, ev_child_init, ev_child_start记录了child watcher, signal watcher的处理函数会把pid对应的child watcher加入pending

 

Ev_stat:

Ev_stat是通过ev_io(inotify)或ev_timer(没有 inotify)实现的, 如果是通过ev_io, 在ev_io的cb里将stat watcher加入pending, 如果是通过ev_timer, 则在ev_timer的cb函数里检查文件属性有无变动, 如果有的话, 将stat watcher加入pending

ev_stat_start(): ev_stat_stat(调用lstat拿到属性), w->timer = ev_timer_init(), 如果使用了inotify, 则最后通过ev_io实现,否则记录在 fs_hash [w->wd & ((EV_INOTIFY_HASHSIZE) - 1)].head , ev_start()(设置优先级, active置为number, reference +1)

ev_stat_stop(): clear_pending, 将this watcher从 fs_hash [w->wd & ((EV_INOTIFY_HASHSIZE) - 1)].head的list的头部删除, 并remove掉fs_fd对应的wd.  ev_timer_stop(), ev_stop中eference -1, 置为不活跃, 将该信号attach的loop置为0, 从signalfd的信号集中去掉该信号或将该信号的处理函数置为default

 

Ev_prepare:

ev_prepare_start(): ev_start()(设置优先级, active置为number, reference +1), prepares [preparecnt - 1] = w

ev_prepare_stop(): clear_pending,  prepares [active - 1] = prepares [--preparecnt], ev_stop中eference -1, 置为不活跃

实现: 在loop的每次循坏里, poll之前, 将prepare watcher放入pending里并马上调用prepare cb(外部传入的)

 

Ev_fork:

ev_fork_start(): ev_start()(设置优先级, active置为number, reference +1), forks [forkcnt - 1] = w

ev_fork_stop(): clear_pending,  forks [active - 1] = forks [--forkcnt], ev_stop中eference -1, 置为不活跃

使用:自动检测 ev_default_loop(EVFLAG_FORKCHECK)(读取pid记作curpid, 之后可以读取pid与curpid比较将postfork置为1), 也可以在fork()的子进程里调用ev_loop_fork()(将postfork置为1)函数

实现: 在loop的每次循坏里, poll之前, 如果postfork == 1, 将fork watcher放入pending里并马上调用fork cb(外部传入的), 并且在后面会调用loop_fork()重置内核状态

 

Ev_check:

ev_check_start(): ev_start()(设置优先级, active置为number, reference +1), checks [checkcnt - 1] = w

ev_check_stop(): clear_pending,  checks [active - 1] = checks [--checkcnt], ev_stop中eference -1, 置为不活跃

实现: 在loop的每次循坏里, poll之后, 将check watcher放入pending里并优先调用check cb(外部传入的), 优先执行是如何实现的?

 

Ev_cleanup:

ev_cleanup_start(): ev_start()(设置优先级, active置为number, reference +1), cleanups [cleanupcnt - 1] = w

ev_cleanup_stop(): clear_pending,  cleanups [active - 1] = cleanups [--cleanupcnt], ev_stop中eference -1, 置为不活跃

实现: 在ev_loop_destroy的时候将cleanup watcher加入pending并执行

 

Ev_async:

ev_async_start():  evpipe_init(), ev_start()(设置优先级, active置为number, reference +1), asyncs [asynccnt - 1] = w

ev_async_stop(): clear_pending,  asyncs [active - 1] = asyncs [--asynccnt], ev_stop中eference -1, 置为不活跃

实现: 在loop_init的时候, 如果signal和async使能, 则初始化pipe_w, 之后在ev_async_start()中通过调用evpipe_init() 将pipe_w用ev_io 的方式监听起来,

之后用户通过ev_async_send()发送异步事件的时候, 调用evpipe_write(), pipecb被调到之后会把async watcher加入到pending

每个线程都有自己的loop及管道, 想给哪个线程发异步消息, 则调用ev_async_send()写对应loop的管道

 

 

Ev_embed:

ev_embed_start(): ev_io_start(w->io), ev_prepare_start(w->prepare), ev_fork_start(w->fork), ev_start()(设置优先级, active置为number, reference +1)

ev_embed_stop(): clear_pending,  ev_io_stop(), ev_prepare_stop(), ev_fork_stop(), ev_stop中eference -1, 置为不活跃

实现:大致的想法, 是将另一个loop的backend_fd 作为ev_io进行监听

使用: 新建一个loop, 初始化给ev_embed watcher, 然后start ev_embed使新的loop嵌入到之前的loop中

 

Ev_loop的暂停和继续:

ev_suspend(): 更新时间, 使ev_timers全部延迟, ev_periodic重新调整, 导致select,poll,epoll等暂时不会返回

ev_resume():重新调整ev_timers,  ev_periodic

 类似资料: