Chinaunix首页 | 论坛 | 博客
  • 博客访问: 19972
  • 博文数量: 2
  • 博客积分: 45
  • 博客等级: 民兵
  • 技术积分: 35
  • 用 户 组: 普通用户
  • 注册时间: 2012-07-29 10:16
文章分类
文章存档

2013年(1)

2012年(1)

我的朋友

分类: LINUX

2012-07-29 10:18:48

libevent 静态基础组件: event, event容器.
libevent 动态逻辑: 由 base 持有所有容器的入口和一个后端, time 容器的内容由前端管理, 后端管理其他所有容器的内容。这是所有复杂性的根源: 维持 event 在前后端容器的一致性. 即前后端互为生产消费者.


所谓前端: 就是维持 loop, 最终所有 event callback 的执行由他来落实的逻辑概念.
所谓后端: 就是监控事件并将其加入容器的逻辑概念.
本来很直观自然的概念, 由于 time 管理的需要, 导致前端也"伪监控"time事件(本质是靠循环)将其加入容器, 迁就实现混杂了概念, 复杂了代码. 下一个版本的 libevent 应该在新内核上支持 timerfd, 这样就能真正利用内核定时器事件, 自己代码变简单。
前后端仅仅是逻辑概念, 不存在对应的物理实体, 这与 event, event容器不同。 base 是前后端逻辑概念的实现(implementation)。


这样在多线程环境下: 可以将静态组件分离, 容器在一个线程, 而 event 的诞生与消亡在另一个线程.

libevent容器有五种:
event queue: 所有的add到base的 event 都会被插入这个queue, 并在向 ev_flags 追加 EVLIST_INSERTED 以作为标志.
acvtive queues: 优先级 queue, 所有被所监控事件激活的event都会被插入这个 queue, 并在向 ev_flags 追加 EVLIST_ACTIVE 以作为标志.
event_io_map: 一个 hashtable, key是fd, value是fd对应的事件list. 如果对一个fd监控可读或可写时间的话, 那么这个fd绑定的 event 就会被加入到 value 的list 中. 同理如果 event_del 一个监控可读|可写事件的event, 那么也会从 key(fd)->value->list 中删除对应事件.
event_signal_map: 和 event_io_map 相似,也可以认为是一个 hashtable, key 是 signal number(signo), value 是这个 signo 对应的 event list.(一个信号可以对应多个event, 信号发生后会挨个调用这些event 的回调).
min_heap: 小根堆用于超时event管理. min_heap[0] 存放在第一个(最快)要超时的 event (的指针).
event 结构里很多 *next, 这是 event 接入不同容器的接入点, 能接入多少种容器就有多少种接入点.


EV_* 一族表示的是这个 event 关注什么事件,由 ev_event 成员保存. 
由于一个 event 可以在多个队列, min_heap, signal 队列(read|write 不能和 signal 一起出现). 那么用一系列标志标明当前一个 event 都在哪些地方存在, 标志就是 EVLIST_* 系列,EVLIST_* 一族是表示在那个容器,由 ev_flags 成员保存。
    #define EV_TIMEOUT  0x01
    #define EV_READ     0x02
    #define EV_WRITE    0x04
    #define EV_SIGNAL   0x08
    #define EV_PERSIST  0x10

    EVLIST_TIMEOUT, 在 min_heap 中
    EVLIST_INSERTED, 在 base queue 中
    EVLIST_SIGNAL  在 event_signal_map 中.
    EVLIST_ACTIVE   在 base activequeue[i] 中
    EVLIST_INTERNAL 内部事件, 忽略
    EVLIST_INIT 则表示这个 event 哪儿都不在,外头游离着。
    注意在 event_io_map 容器没有专门标志, 如果 ev_event 有 EV_READ|EV_WRITE, event_add 后一定在 event_io_map 中.

对于信号处理, 稍有点绕:
在 epoll_init(其他后端类似)时初始化信号处理机制: 创建一个监控 read 的 event, 与 base->pipe_pair 读端 fd 绑定. 回调设为: ev_sigcb. 将这个event加入 base_loop
在 event_add 一个 signal event时, 将 signo 和 event 加入 event_signal_map, 并为 signal 通过 sigaction 设置信号处理函数为 evsig_handler.
当一个信号被触发时, evsig_handler被调用, 该函数只是往 base->pipe_pair 写端 fd 写一个字节(实际是signo发生几次写几个字节), 字节内容就是signo.
base_loop 发现base->pipe_pair的可读事件, 调用相应的回调 ev_sigcb, 此回调从 base->pipe_pair 中读出所有signo, 根据 signo 从 event_signal_map 中找到他对应的 event, 将其加入 active 容器.



容器关系:
libevent 提供了三个改变容器内容的接口, 分别是 event_add, event_del, event_active. 第三个其实只准备给 bufferevent 用. 现在可以忽略.
外部 event_add时, 根据指定的标志, 将 event 指针链入相关容器. event_del 同理.



而 base_loop 本身调整容器数据关系的逻辑为: 
在 epoll->dispatch 时会将监控到 read|write 事件的 event 都挂入 active 容器.

然后在 timeout_process 中处理超时, 将所有已经超时的 event 用 event-del 将其从各相应容器中删掉(即使前面的 dispatch 已经将其挂入 active 队列.). 然后将这个 event 挂入active队列(但是 ev_res 被置为 EV_TIMEOUT). 注:删除时会抹去所有队列标志.
所以如果 epoll->dispatch 将其激活一次, 紧接着 timeout_process 又激活一次, 那么回调中传入的时间原因将是 EV_TIMEOUT.

最后才遍历 active 容器, 如果 event 没指定 persist 标志, 那么从所有相应容器删除了 event, 然后调用 event 的回调. 如果指定了 persist 标志, 那么仅从 actvie 容器中删除 event. 然后经由 event_persist_closure 处理,  此函数对于指定了超时时间的 event, 会重新调用 event_add 加入相应容器(如果 event 被前面的 timeout_process删掉则加入, 如果没有被删, event_add 发现有了 EV_LISTINSERT 标志就更新一下时间就返回). 最终event_persist_closure 调用 event 的回调.

对于 singal 由 event_signal_closure 最终完成回调. 由于 event_signal_closure event_persist_closure 在 switch 只经过其中一个, 避免了回调的多次被调用.


libevent 的锁:
所有 EVENT_BASE_ASSERT_LOCKED 的函数都表示调用这个函数的时候必须持有 base 的锁.time相关的函数(涉及到修改min_heap, 校准时间等)需要加锁后执行.
加锁策略: event_base_loop 开始加锁,然后是时间校准,更新,然后在 epoll_dispatch 上等待事件。epoll_wait 前解锁,wait 返回后加锁,这样持锁调用 event_active_nolock 修改 active 容器。epoll_dispatch 返回后 base_loop 继续持有锁,进行超时处理timeout_process,拿着锁修改min_heap,从各个容器删除 ev。返回至base_loop后继续持有锁处理每个 active 容器,如果不是persist ev, 会从各容器伤处 ev. 最后会调用 ev 的回调函数, 在调用回调函数前释放锁, 以允许回调函数中执行 ev_add ev_del 这些命令. 回调后重新加锁.

因为在到了回调的那一步时, base_loop 已经不会再读写所有容器及相关数据, 回调函数中和其他线程可以安全的竞争着修改容器(base_loop已经退出竞争).这时候外部可以通过接口 event_active event_add event_del 操作容器. 当 base_loop 要再次访问容器(取下一个acvive event)时, 就会先把锁抢回来,这就是为何回调后要加锁.
阅读(5154) | 评论(0) | 转发(0) |
0

上一篇:没有了

下一篇:重新开始

给主人留下些什么吧!~~