libevent源码深度剖析八

——集成信号处理
张亮

现在我们已经了解了libevent的基本框架:事件管理框架和事件主循环。上节提到了libevent中I/O事件和Signal以及Timer事件的集成,这一节将分析如何将Signal集成到事件主循环的框架中。

1 集成策略——使用socket pair

前一节已经做了足够多的介绍了,基本方法就是采用“消息机制”。在libevent中这是通过socket pair完成的,下面就来详细分析一下。
      Socket pair就是一个socket对,包含两个socket,一个读socket,一个写socket。工作方式如下图所示:
                               

创建一个socket pair并不是复杂的操作,可以参见下面的流程图,清晰起见,其中忽略了一些错误处理和检查。



              
Libevent提供了辅助函数evutil_socketpair()来创建一个socket pair,可以结合上面的创建流程来分析该函数。

2 集成到事件主循环——通知event_base

Socket
pair创建好了,可是libevent的事件主循环还是不知道Signal是否发生了啊,看来我们还差了最后一步,那就是:为socket
pair的读socket在libevent的event_base实例上注册一个persist的读事件。
      这样当向写socket写入数据时,读socket就会得到通知,触发读事件,从而event_base就能相应的得到通知了。
前面提到过,Libevent会在事件主循环中检查标记,来确定是否有触发的signal,如果标记被设置就处理这些signal,这段代码在各个具体的I/O机制中,以Epoll为例,在epoll_dispatch()函数中,代码片段如下:

  1. res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout);
  2. if (res == -1) {
  3. if (errno != EINTR) {
  4. event_warn("epoll_wait");
  5. return (-1);
  6. }
  7. evsignal_process(base);// 处理signal事件
  8. return (0);
  9. } else if (base->sig.evsignal_caught) {
  10. evsignal_process(base);// 处理signal事件
  11. }

完整的处理框架如下所示:
         

注1:libevent中,初始化阶段并不注册读socket的读事件,而是在注册信号阶段才会测试并注册;
注2:libevent中,检查I/O事件是在各系统I/O机制的dispatch()函数中完成的,该dispatch()函数在event_base_loop()函数中被调用;

3 evsignal_info结构体

Libevent中Signal事件的管理是通过结构体evsignal_info完成的,结构体位于evsignal.h文件中,定义如下:

  1. struct evsignal_info {
  2. struct event ev_signal;
  3. int ev_signal_pair[2];
  4. int ev_signal_added;
  5. volatile sig_atomic_t evsignal_caught;
  6. struct event_list evsigevents[NSIG];
  7. sig_atomic_t evsigcaught[NSIG];
  8. #ifdef HAVE_SIGACTION
  9. struct sigaction **sh_old;
  10. #else
  11. ev_sighandler_t **sh_old;
  12. #endif
  13. int sh_old_max;
  14. };

下面详细介绍一下个字段的含义和作用:
1)ev_signal, 为socket pair的读socket向event_base注册读事件时使用的event结构体;
2)ev_signal_pair,socket pair对,作用见第一节的介绍;
3)ev_signal_added,记录ev_signal事件是否已经注册了;
4)evsignal_caught,是否有信号发生的标记;是volatile类型,因为它会在另外的线程中被修改;
5)evsigvents[NSIG],数组,evsigevents[signo]表示注册到信号signo的事件链表;
6)evsigcaught[NSIG],具体记录每个信号触发的次数,evsigcaught[signo]是记录信号signo被触发的次数;
7)sh_old记录了原来的signal处理函数指针,当信号signo注册的event被清空时,需要重新设置其处理函数;
    evsignal_info的初始化包括,创建socket pair,设置ev_signal事件(但并没有注册,而是等到有信号注册时才检查并注册),并将所有标记置零,初始化信号的注册事件链表指针等。

4 注册、注销signal事件

注册signal事件是通过evsignal_add(struct event *ev)函数完成的,libevent对所有的信号注册同一个处理函数evsignal_handler(),该函数将在下一段介绍,注册过程如下:
1 取得ev要注册到的信号signo;
2 如果信号signo未被注册,那么就为signo注册信号处理函数evsignal_handler();
3 如果事件ev_signal还没哟注册,就注册ev_signal事件;
4 将事件ev添加到signo的event链表中;
从signo上注销一个已注册的signal事件就更简单了,直接从其已注册事件的链表中移除即可。如果事件链表已空,那么就恢复旧的处理函数;
下面的讲解都以signal()函数为例,sigaction()函数的处理和signal()相似。
处理函数evsignal_handler()函数做的事情很简单,就是记录信号的发生次数,并通知event_base有信号触发,需要处理:

  1. static void evsignal_handler(int sig)
  2. {
  3. int save_errno = errno; // 不覆盖原来的错误代码
  4. if (evsignal_base == NULL) {
  5. event_warn("%s: received signal %d, but have no base configured", __func__, sig);
  6. return;
  7. }
  8. // 记录信号sig的触发次数,并设置event触发标记
  9. evsignal_base->sig.evsigcaught[sig]++;
  10. evsignal_base->sig.evsignal_caught = 1;
  11. #ifndef HAVE_SIGACTION
  12. signal(sig, evsignal_handler); // 重新注册信号
  13. #endif
  14. // 向写socket写一个字节数据,触发event_base的I/O事件,从而通知其有信号触发,需要处理
  15. send(evsignal_base->sig.ev_signal_pair[0], "a", 1, 0);
  16. errno = save_errno; // 错误代码
  17. }

5 小节
本节介绍了libevent对signal事件的具体处理框架,包括事件注册、删除和socket pair通知机制,以及是如何将Signal事件集成到事件主循环之中的。

libevent源码深度剖析八的更多相关文章

  1. libevent 源码深度剖析十三

    libevent 源码深度剖析十三 —— libevent 信号处理注意点 前面讲到了 libevent 实现多线程的方法,然而在多线程的环境中注册信号事件,还是有一些情况需要小心处理,那就是不能在多 ...

  2. libevent源码深度剖析十二

    libevent源码深度剖析十二 ——让libevent支持多线程 张亮 Libevent本身不是多线程安全的,在多核的时代,如何能充分利用CPU的能力呢,这一节来说说如何在多线程环境中使用libev ...

  3. libevent源码深度剖析十一

    libevent源码深度剖析十一 ——时间管理 张亮 为了支持定时器,Libevent必须和系统时间打交道,这一部分的内容也比较简单,主要涉及到时间的加减辅助函数.时间缓存.时间校正和定时器堆的时间值 ...

  4. libevent源码深度剖析十

    libevent源码深度剖析十 ——支持I/O多路复用技术 张亮 Libevent的核心是事件驱动.同步非阻塞,为了达到这一目标,必须采用系统提供的I/O多路复用技术,而这些在Windows.Linu ...

  5. libevent源码深度剖析九

    libevent源码深度剖析九 ——集成定时器事件 张亮 现在再来详细分析libevent中I/O事件和Timer事件的集成,与Signal相比,Timer事件的集成会直观和简单很多.Libevent ...

  6. libevent源码深度剖析七

    libevent源码深度剖析七 ——事件主循环 张亮 现在我们已经初步了解了libevent的Reactor组件——event_base和事件管理框架,接下来就是libevent事件处理的中心部分 — ...

  7. libevent源码深度剖析六

    libevent源码深度剖析六 ——初见事件处理框架 张亮 前面已经对libevent的事件处理框架和event结构体做了描述,现在是时候剖析libevent对事件的详细处理流程了,本节将分析 lib ...

  8. libevent源码深度剖析五

    libevent源码深度剖析五 ——libevent的核心:事件event 张亮 对事件处理流程有了高层的认识后,本节将详细介绍libevent的核心结构event,以及libevent对event的 ...

  9. libevent源码深度剖析四

    libevent源码深度剖析四 ——libevent源代码文件组织 1 前言 详细分析源代码之前,如果能对其代码文件的基本结构有个大概的认识和分类,对于代码的分析将是大有裨益的.本节内容不多,我想并不 ...

随机推荐

  1. js 下获取子元素的方法

    笔记核心: firstElementChild只会获取元素节点对象,从名称就可以看出来,firstChild则可以获取文本节点对象(当然也可以获取元素节点对象),比如空格和换行都被当做文本节点. js ...

  2. 混用Application.LoadLevel 和 PhotonNetwork.LoadLevel

    最近这一周从上周五晚上加完班回家夜里都12点了. 又赶紧送孩子去儿童医院  .. 就肺炎住院了.  真是有啥别有病呢.  悲剧的是我周三夜里陪了一夜后. 转天晚上也发烧了.. 周四 周五输了两天液. ...

  3. 软件测试 Record

    fxcopnunit软件质量 EDW 数据仓库ETL KPI 敏捷 测试计划 单元测试 集成测试 系统测试 对测试结果 阶段性 分析 总结 测试结果报告 环境问题:软硬件用户有问题,我们这边没有有效问 ...

  4. 使用vscode书写博客

    很早就开始使用过vscode了,不过在已经成熟的sublime的碾压下,vscode一直没有成为我的首选,今天为了更好的博客书写体验,我直接放弃了sublime,因为 sublime对中文支持不好,而 ...

  5. Python虚拟环境设置

    Python2环境 首先,我们用pip安装virtualenv: pip3 install virtualenv 然后,假定我们要开发一个新的项目,需要一套独立的Python运行环境,可以这么做: 第 ...

  6. C++11新特性之右值引用(&&)、移动语义(move)、完美转换(forward)

    1. 右值引用 个人认为右值引用的目的主要是为了是减少内存拷贝,优化性能. 比如下面的代码: String Fun() { String str = "hello world"; ...

  7. rhel7+apache+c cgi+动态域名实现web访问

    1. 申请动态域名/安装no-ip客户端 https://blog.csdn.net/lee244868149/article/details/44095835 2. yum安装httpd 两种方法安 ...

  8. bzoj 3598 方伯伯的商场之旅

    Written with StackEdit. Description 方伯伯有一天去参加一个商场举办的游戏.商场派了一些工作人员排成一行.每个人面前有几堆石子.说来也巧,位置在 \(i\) 的人面前 ...

  9. 解决Iframe session过期,登录界面无法全页刷新

    在登录界面增加如下js代码: <script language=”JavaScript”> if (window != top) top.location.href = location. ...

  10. 利用bat合并两个hex文件

    单片机程序如果有IAP功能的话,就会生成两个hex文件,一个是Boot,一个是App,如果给让生产烧录两个文件,就会降低生产效率,所以在烧录前最好将两个文件合并成一个文件,烧录一次即可,合并方法如下: ...