libevent2对信号的响应也进行了封装,使之与socket操作一样对外提供统一的接口。这里的信号一般指linux的信号。由于信号与socket相关的编程接口有较大的不同,因此在内部实现也有一些区别。

与IO操作(socket算作是IO操作)的evsel类似,在event_base中也定义了信号的操作变量。

[event.h, event_base]

const struct eventop *evsigsel;

定义对信号的操作。

[signal.c]

static const struct eventop evsigops = {

"signal",

NULL,

evsig_add,

evsig_del,

NULL,

NULL,

0, 0, 0

};

信号只有add和del两个操作。在evsig_init中,用上面定义的变量对event_base->evsigsel进行了赋值。这个实现在 evsig_init中的最后几行代码。

libevent如何将信号的通知与IO就绪的通知进行封装,对外提供统一的编程模型呢?答案是用socketpair. socketpair是一对socket,实现全双工的通讯,如果向一端的socketpair[0]写入数据,在另一端的socketpair[1]中可读取。作为信号传递的工具,在一端写入信号的值,另一端则读取。

初始化调用顺序: event_base_new() -> event_base_new_with_config() -> base->evsel->init()(即select_init) -> evsig_init()

[signal.c]

int evsig_init(struct event_base *base)

{

/*

* Our signal handler is going to write to one end of the socket

* pair to wake up our event loop.  The event loop then scans for

* signals that got delivered.

*/

if (evutil_socketpair(

AF_UNIX, SOCK_STREAM, 0, base->sig.ev_signal_pair) == -1) {

event_sock_err(1, -1, "%s: socketpair", __func__);

return -1;

}

evutil_make_socket_closeonexec(base->sig.ev_signal_pair[0]);

evutil_make_socket_closeonexec(base->sig.ev_signal_pair[1]);

base->sig.sh_old = NULL;

base->sig.sh_old_max = 0;

evutil_make_socket_nonblocking(base->sig.ev_signal_pair[0]);

evutil_make_socket_nonblocking(base->sig.ev_signal_pair[1]);

// 初始化,并设置event的内部回调

event_assign(&base->sig.ev_signal, base, base->sig.ev_signal_pair[1],

EV_READ | EV_PERSIST, evsig_cb, base);

base->sig.ev_signal.ev_flags |= EVLIST_INTERNAL;

event_priority_set(&base->sig.ev_signal, 0);

base->evsigsel = &evsigops;

return 0;

}

上面的代码中,event_assign将socketpair[1]作为触发事件的内部fd, 并将回调关联到 evsign_cb 函数。可见利用了IO的底层实现,所有的事件的响应都会回调到evsign_cb这个函数。再看一下evsig_cb这个内部回调函数的实现。

[signal.c]

/* Callback for when the signal handler write a byte to our signaling socket */

static void

evsig_cb(evutil_socket_t fd, short what, void *arg)

{

static char signals[1024];

ev_ssize_t n;

int i;

int ncaught[NSIG];

struct event_base *base;

base = arg;

memset(&ncaught, 0, sizeof(ncaught));

while (1) {

/// 获取数据消息

n = recv(fd, signals, sizeof(signals), 0);

...

for (i = 0; i < n; ++i) {

ev_uint8_t sig = signals[i];

if (sig < NSIG)

ncaught[sig]++;

}

}

/// 激活通知

EVBASE_ACQUIRE_LOCK(base, th_base_lock);

for (i = 0; i < NSIG; ++i) {

if (ncaught[i])

evmap_signal_active(base, i, ncaught[i]);

}

EVBASE_RELEASE_LOCK(base, th_base_lock);

}

主要工作就是,从socket中读取数据,然后将读取到的数据作为信号,通过evmap_signal_active通知到上层。从上面的代码分析,这是socket接收方的处理。那么作为信号的捕获者并通过socket发送消息的发送方在哪里?又做了什么些事情呢?

要响应一个linux信号,必需通过signal或sigation关联一个信号量和一个响应函数。建立这种关联是在evsig_add函数中完成的。

[signal.c]

static int

evsig_add(struct event_base *base, evutil_socket_t evsignal, short old, short events, void *p)

{

struct evsig_info *sig = &base->sig;

...

/// 初始化静态变量

evsig_base = base;

evsig_base_n_signals_added = ++sig->ev_n_signals_added;

evsig_base_fd = base->sig.ev_signal_pair[0];

...

///

if (_evsig_set_handler(base, (int)evsignal, evsig_handler) == -1) {

goto err;

}

...

}

int

_evsig_set_handler(struct event_base *base,

int evsignal, void (__cdecl *handler)(int))

{

...

sig->sh_old[evsignal] = mm_malloc(sizeof *sig->sh_old[evsignal]);

...

#ifdef _EVENT_HAVE_SIGACTION

memset(&sa, 0, sizeof(sa));

sa.sa_handler = handler;

sa.sa_flags |= SA_RESTART;

sigfillset(&sa.sa_mask);

if (sigaction(evsignal, &sa, sig->sh_old[evsignal]) == -1) {

event_warn("sigaction");

mm_free(sig->sh_old[evsignal]);

sig->sh_old[evsignal] = NULL;

return (-1);

}

#else

if ((sh = signal(evsignal, handler)) == SIG_ERR) {

event_warn("signal");

mm_free(sig->sh_old[evsignal]);

sig->sh_old[evsignal] = NULL;

return (-1);

}

*sig->sh_old[evsignal] = sh;

#endif

return (0);

}

在evsig_add中定义了与信号量关联的响应函数是evsig_handler,其实现如下:

static void __cdecl

evsig_handler(int sig)

{

...

/* Wake up our notification mechanism */

msg = sig;

send(evsig_base_fd, (char*)&msg, 1, 0);

errno = save_errno;

...

}

evsig_base_fd是socketpair[0],它是在evsig_add函数中被赋值的。

流程是这样的:evsig_add作为入口函数,其调用流程将某个linux信号通过sigaction或signal关联到evsig_handler这个回调函数,在回调函数的内部将信号量作为数据写到socketpair[0],发送给另一端。另一端通过 socketpair[1]读取到信号后,回调保存在 base->sig.ev_signal 中的回调函数,即 evsig_cb. 在这个函数的内部,完成了将事件通知到上层,到这时候,其实现与IO的事件触发完全一样了。

libevent2源码分析之三:信号的初始化流程的更多相关文章

  1. libevent2源码分析之二:初始化流程

    本文并不很详细地分析初始化的各个细节,而重点分析如何将底层操作关联到event_base的相关字段.初始化工作主要是针对event_base的.libevent2支持多种底层实现,有epoll, se ...

  2. zookeeper源码分析之三客户端发送请求流程

    znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...

  3. 《深入理解Spark:核心思想与源码分析》——SparkContext的初始化(叔篇)——TaskScheduler的启动

    <深入理解Spark:核心思想与源码分析>一书前言的内容请看链接<深入理解SPARK:核心思想与源码分析>一书正式出版上市 <深入理解Spark:核心思想与源码分析> ...

  4. 【spring源码分析】IOC容器初始化(总结)

    前言:在经过前面十二篇文章的分析,对bean的加载流程大致梳理清楚了.因为内容过多,因此需要进行一个小总结. 经过前面十二篇文章的漫长分析,终于将xml配置文件中的bean,转换成我们实际所需要的真正 ...

  5. 【spring源码分析】IOC容器初始化(二)

    前言:在[spring源码分析]IOC容器初始化(一)文末中已经提出loadBeanDefinitions(DefaultListableBeanFactory)的重要性,本文将以此为切入点继续分析. ...

  6. 【spring源码分析】IOC容器初始化(三)

    前言:在[spring源码分析]IOC容器初始化(二)中已经得到了XML配置文件的Document实例,下面分析bean的注册过程. XmlBeanDefinitionReader#registerB ...

  7. 【spring源码分析】IOC容器初始化(四)

    前言:在[spring源码分析]IOC容器初始化(三)中已经分析了BeanDefinition注册之前的一些准备工作,下面将进入BeanDefinition注册的核心流程. //DefaultBean ...

  8. 【spring源码分析】IOC容器初始化(七)

    前言:在[spring源码分析]IOC容器初始化(六)中分析了从单例缓存中加载bean对象,由于篇幅原因其核心函数 FactoryBeanRegistrySupport#getObjectFromFa ...

  9. 【spring源码分析】IOC容器初始化(十)

    前言:前文[spring源码分析]IOC容器初始化(九)中分析了AbstractAutowireCapableBeanFactory#createBeanInstance方法中通过工厂方法创建bean ...

随机推荐

  1. yii2 项目初始化

    yii 项目目录下执行.composer self-updatecomposer global require "fxp/composer-asset-plugin:^1.4.1" ...

  2. Vim求生

    [TOC] Vim 是从 vi 发展出来的一个文本编辑器.其代码补完.编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用.和 Emacs 并列成为类 Unix 系统用户最喜欢的编辑器. —— ...

  3. 贮油点问题(C++)

    贮油点问题…..一道送命系列的递推… 描述 Description 一辆重型卡车欲穿过S公里的沙漠,卡车耗汽油为1升/公里,卡车总载油能力为W公升.显然卡车装一次油是过不了沙漠的.因此司机必须设法在沿 ...

  4. RabbitMQ (四) 工作队列之公平分发

    上篇文章讲的轮询分发 : 1个队列,无论多少个消费者,无论消费者处理消息的耗时长短,大家消费的数量都一样. 而公平分发,又叫 : 能者多劳,顾名思义,处理得越快,消费得越多. 生产者 public c ...

  5. AOJ 2266 Cache Strategy(费用流)

    [题目链接] http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=2266 [题目大意] 有M个桶,N个球,球编号为1到N,每个球都有重量 ...

  6. 【点分治】【哈希表】bzoj2599 [IOI2011]Race

    给nlog2n随便过的跪了,不得已弄了个哈希表伪装成nlogn(当然随便卡,好孩子不要学)…… 不过为啥哈希表的大小开小点就RE啊……?必须得超过数据范围一大截才行……谜 #include<cs ...

  7. 【枚举】bzoj1709 [Usaco2007 Oct]Super Paintball超级弹珠

    由于子弹的轨迹是可逆的,因此我们可以枚举所有敌人的位置,然后统计他们能打到的位置,这些位置也就是能打到他们的位置咯. O(n*k). #include<cstdio> using name ...

  8. python3-os模块中的os.walk(目录树生成器)

    #先看源码 def walk(top, topdown=True, onerror=None, followlinks=False): """Directory tree ...

  9. 记一次深刻的教训-----将mat数据转化为SequenceFile

    深刻的体会就是,“java.lang.NullPointer.Exception”就是空指针异常可能是由于数组部分元素未被初始化引起的. 1)使用jmatio将mat数据转化为SequenceFile ...

  10. freedom isn't free

    财务自由(除去房和车) 第一阶段: 个人存款达到50万以上 第二阶段 个人存款100~200万 第三阶段 个人存款400万以上 第三阶段以上才能算实现了相对较好的财务自由!come on , boys ...