一. 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) ... min(timer, periodic)>= waittime >= max(timeout_blocktime, backend_mintime, io_blocktime)
backend_poll (EV_A_ waittime); |
|
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