libevent2源码分析之三:信号的初始化流程
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源码分析之三:信号的初始化流程的更多相关文章
- libevent2源码分析之二:初始化流程
本文并不很详细地分析初始化的各个细节,而重点分析如何将底层操作关联到event_base的相关字段.初始化工作主要是针对event_base的.libevent2支持多种底层实现,有epoll, se ...
- zookeeper源码分析之三客户端发送请求流程
znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...
- 《深入理解Spark:核心思想与源码分析》——SparkContext的初始化(叔篇)——TaskScheduler的启动
<深入理解Spark:核心思想与源码分析>一书前言的内容请看链接<深入理解SPARK:核心思想与源码分析>一书正式出版上市 <深入理解Spark:核心思想与源码分析> ...
- 【spring源码分析】IOC容器初始化(总结)
前言:在经过前面十二篇文章的分析,对bean的加载流程大致梳理清楚了.因为内容过多,因此需要进行一个小总结. 经过前面十二篇文章的漫长分析,终于将xml配置文件中的bean,转换成我们实际所需要的真正 ...
- 【spring源码分析】IOC容器初始化(二)
前言:在[spring源码分析]IOC容器初始化(一)文末中已经提出loadBeanDefinitions(DefaultListableBeanFactory)的重要性,本文将以此为切入点继续分析. ...
- 【spring源码分析】IOC容器初始化(三)
前言:在[spring源码分析]IOC容器初始化(二)中已经得到了XML配置文件的Document实例,下面分析bean的注册过程. XmlBeanDefinitionReader#registerB ...
- 【spring源码分析】IOC容器初始化(四)
前言:在[spring源码分析]IOC容器初始化(三)中已经分析了BeanDefinition注册之前的一些准备工作,下面将进入BeanDefinition注册的核心流程. //DefaultBean ...
- 【spring源码分析】IOC容器初始化(七)
前言:在[spring源码分析]IOC容器初始化(六)中分析了从单例缓存中加载bean对象,由于篇幅原因其核心函数 FactoryBeanRegistrySupport#getObjectFromFa ...
- 【spring源码分析】IOC容器初始化(十)
前言:前文[spring源码分析]IOC容器初始化(九)中分析了AbstractAutowireCapableBeanFactory#createBeanInstance方法中通过工厂方法创建bean ...
随机推荐
- hdu 3666(差分约束,手动栈解决超时问题)
THE MATRIX PROBLEM Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Other ...
- 洛谷 P2678 跳石头【经典二分答案/贪心】
题目描述 这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石.组委会已经选择好了两块岩石作为比赛起点和终点.在起点和终点之间,有 NN 块岩石(不含起点和终点的岩石).在比赛过程中,选手们将从 ...
- 51nod 1265 四点共面【计算几何+线性代数】
1265 四点共面 基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题 收藏 关注 给出三维空间上的四个点(点与点的位置均不相同),判断这4个点是否在同一个平面内(4点共 ...
- 树莓派编译nginx,支持rtmp直播
树莓派3B+ 系统更新至最新 下载依赖 sudo apt-get update sudo apt-get install libxslt1-dev libgd-dev libgeoip-dev lib ...
- HTTP隧道工具HTTPTunnel
HTTP隧道工具HTTPTunnel 在很多服务器上,防火墙会限制主机的出站流量,只允许80之类的端口.如果要使用其他端口,只能通过HTTP隧道方式实现.Kali Linux提供一款HTTP隧道工 ...
- 【博弈论】【SG函数】【枚举】bzoj1188 [HNOI2007]分裂游戏
因为第i个瓶子里的所有豆子都是等价的,设sg(i)表示第i个瓶子的sg值,可以转移到sg(j)^sg(k)(i<j<n,j<=k<n)的状态. 只需要考虑豆子数是奇数的瓶子啦, ...
- C语言实现括号配对问题
代码如下: #include<stdio.h> #include<string.h> #include<stdlib.h> // 写一个判断的括号是否匹配的函数 i ...
- asp.net 域名注册查询接口 支持批量后缀查询
最近在完成公司网站www.xuhongkj.com的时候,需要用到域名查询的功能,网上查了一些资料,几乎都是ASP版的,而且功能有限,不能满足我的要求. 百度后,结合网上的例子,整理出了该功能! as ...
- delphi 获取USB口拔出和插入的状态
unit USBDeviceNotify;//USB Device arrival or removeinterface uses Windows, Messages, SysUtils, Clas ...
- SpringBoot拦截器中service或者redis注入为空的问题
原文:https://my.oschina.net/u/1790105/blog/1490098 这两天遇到SpringBoot拦截器中Bean无法注入问题.下面介绍我的思考过程和解决过程: 1.由于 ...