概述
1、原理性介绍:
1、Linux操作系统,对于信号的处理,都是调用先前注册给系统的回调函数,例如通过sigaction(evsignal, &sa, sig->sh_old[evsignal])
注册回调了sa
里面的回调函数。当对应的信号到来时候,将调用相应的回调函数。
2、Libevent为了将信号事件和IO事件统一起来,即对于信号的处理也通过epoll
可以检测到。因此Libevent采用Unix域套接字的方法。创建一个域套接字socketpair[0]、socketpair[1]
分别对应读和写。对于socketpair[1]
Libevent创建一个内部socketpair[1]
可读的事件ev_signal
并设定其回调函数是evsig_cb
,同时通过epoll监听这个事件。
3、当用户调用event_add
添加信号事件的时候,会注册对应的信号回调函数evsig_handler
,这个回调函数仅仅是往socketpair[0]
里面写入对应的信号值。这时候,这时候epoll
可以检测socketpair[1]
可读,通过其事件回调函数evsig_cb
读取出对应信号的值,并信号对应的事件,添加到就绪队列。然后执行对应的回调函数。通过添加间接层,可以很好的体现Libevent的事件驱动机制,这时候信号也是一个事件。
4、具体过程如下图:
统一事件源能够工作的一个原因是:多路IO复用函数都是可中断的。即处理完信号后,会从多路IO复用函数中退出,并将errno赋值为EINTR。
2、简单例子
将使用这个简单例子分析全部信号处理流程:
#include <event.h>
#include <signal.h>
void sigintEventCB1(int fd, short event, void *argc)
{
printf("CB1n");
}
void sigintEventCB2(int fd, short event, void *argc)
{
printf("CB2n");
}
void sigintEventCB3(int fd, short event, void *argc)
{
printf("CB3n");
}
int main(void)
{
struct event_base *base;//Reactor
struct event *sigintEvent1;//事件
struct event *sigintEvent2;
struct event *sigintEvent3;
base = event_base_new();
event_base_priority_init(base , 3);//设定三个优先级别
//在SIGINT中断信号上面监听三个事件,并且优先级各部相同
sigintEvent1 = event_new(base , SIGINT , EV_SIGNAL|EV_PERSIST , sigintEventCB1 , NULL);
sigintEvent1->ev_pri = 2;
sigintEvent2 = event_new(base , SIGINT , EV_SIGNAL|EV_PERSIST , sigintEventCB2 , NULL);//信号事件,并且触发后仅仅从就绪队列删除,继续监听EV_PERSIST属性。
sigintEvent2->ev_pri = 1;
sigintEvent3 = event_new(base , SIGINT , EV_SIGNAL|EV_PERSIST , sigintEventCB3 , NULL);
sigintEvent3->ev_pri = 0;
event_add(sigintEvent1 , NULL);
event_add(sigintEvent2 , NULL);
event_add(sigintEvent3 , NULL);
event_base_dispatch(base);//循环监听。
event_base_free(base);//退出释放内存。
event_free(sigintEvent1);//因为用的event_new,所以必须手动释放内存。
event_free(sigintEvent2);
event_free(sigintEvent3);
return 0;
}
以上代码在SIGINT
上面注册三个回调函数,并且各自优先级为0、1、2。
1、首先第一点需要注意,就是需要手动初始化event_base_priority_init
的优先级链表个数,也就是初始化struct event_list *activequeues
这个数组大小。否则Libevent默认初始化大小为0,为我们定义了优先级,最后肯定会收到系统的SIGSEGV
信号,导致程序终止。
int
event_base_priority_init(struct event_base *base, int npriorities)
{
int i;
if (N_ACTIVE_CALLBACKS(base) || npriorities < 1
|| npriorities >= EVENT_MAX_PRIORITIES)
return (-1);
if (npriorities == base->nactivequeues)//
return (0);
if (base->nactivequeues) {
mm_free(base->activequeues);
base->nactivequeues = 0;
}
/* Allocate our priority queues 动态分配优先队列所需要的内存 */
base->activequeues = (struct event_list *)
mm_calloc(npriorities, sizeof(struct event_list));//分配npriorities个struct event_list
if (base->activequeues == NULL) {
event_warn("%s: calloc", __func__);
return (-1);
}
base->nactivequeues = npriorities;
for (i = 0; i < base->nactivequeues; ++i) {//继续初始化nactivequeues个队列,用来分别存储不同优先级的event
TAILQ_INIT(&base->activequeues[i]);
}
return (0);
}
此函数很简单,就是分配一个数组,数组里面的元素存储队列头。每一个事件都有一个优先级变量ev_pri
,当事件发生时候,通过里面的这个变量,将其加入到activequeues[ev_pri]
对应的就绪链表中,实现事件的优先级调用。
2、在同一个事件上面是支持优先级的和IO操作一样,当信号发生时候,优先级高的事件对应的回调函数优先运行。
3、当注册了信号函数,如果用户自己再次重新通过sigaction
注册回调函数,那么信号发生,将直接覆盖Libevent帮助我们注册ev_signal
,导致统一信号事件源失效。
3、源代码分析
首先安装的Libevent的debug版本,在运行时候,会打印debug信息。其次通过strace -p pid 追踪Libevent运行时候对应的系统调用。可以很清楚的看出Libevent对于信号处理是如何进行的,对于分析代码有重要作用。下面给出跟踪信息及注释。
Libevent自带的调试输出:
//通过strace跟踪Libevent_client,并将跟踪文件输出,以下是Libevent调试输出的结果:
$ strace -o output.txt ./Libevent_client
//第一次调用event_add(sigintEvent1 , NULL);
[debug] event_add: event: 0x15064e0 (fd 2), call 0x4008e6 //调用event_add(sigintEvent1 , NULL);输出的调试信息,因为SIGINT = 2
[debug] evsig_add: 2: changing signal handler //event_add->event_add_internal->evmap_signal_add->evsig_add 注册SIGINT回调函数 evsig_handler
[debug] _evsig_set_handler: evsignal (2) >= sh_old_max (0), resizing//_evsig_set_handler中sig->sh_old扩容,因为对于每一个信号需要存储一个struct sigaction变量
[debug] event_add: event: 0x1506068 (fd 5), EV_READ call 0x7f813c939909//信号事件第一次监听,将pair[1]读端的统一事件源ev_signal加入到epoll。
[debug] Epoll ADD(1) on fd 5 okay. [old events were 0; read change was 1; write change was 0]//ev_signal调用epoll的调试信息,epoll add(指令码为1)操作,pair[1]=5
//第二三次调用event_add(sigintEvent2 , NULL); 仅仅将sigintEvent2加入到信号事件队列
[debug] event_add: event: 0x1506570 (fd 2), call 0x40090a//再次添加SIGINT
[debug] event_add: event: 0x1506600 (fd 2), call 0x40092e//再次添加SIGINT
//调用event_base_dispatch(base);//循环监听
[debug] epoll_dispatch: epoll_wait reports 1 //由于发送了SIGINT信号,回调函数被调用,所以epoll_dispatch里面epoll_wait监听pair[1]返回可读,就绪一个
[debug] event_active: 0x1506068 (fd 5), res 2, callback 0x7f813c939909//event_active_nolock,将pair[1]的回调函数加入就绪队列。res代表触发事件为read
//此时激活队列上就一个事件evsig_cb
[debug] event_process_active: event: 0x1506068, EV_READ call 0x7f813c939909//处理就绪事件,事件的首地址 ,事件可读,调用回调函数evsig_cb(回调函数首地址)
//evsig_cb优先级为0,所以最先被执行,然后将其他三个信号注册函数激活。
//evsig_cb激活三个信号事件后,进而继续执行激活队列上面的事件。
[debug] event_active: 0x15064e0 (fd 2), res 8, callback 0x4008e6//evsig_cb里面将三个信号事件激活,res为8代表信号
[debug] event_active: 0x1506570 (fd 2), res 8, callback 0x40090a
[debug] event_active: 0x1506600 (fd 2), res 8, callback 0x40092e
//在先激活的事件的回调函数,可以激活一些事件。
[debug] event_process_active: event: 0x1506600, call 0x40092e//处理优先级最高的,在第0号优先队列
CB3
[debug] epoll_dispatch: epoll_wait reports 0//为什么此处返回0?
[debug] event_process_active: event: 0x1506570, call 0x40090a//处理优先级第二高的,在第1号优先队列
CB2
[debug] epoll_dispatch: epoll_wait reports 0
[debug] event_process_active: event: 0x15064e0, call 0x4008e6//处理优先级第三高的 在第2号优先队列
CB1
Hangup//用户发送SIGHUB信号终止Libevent
strace追踪的output.txt:
execve("./Libevent_client", ["./Libevent_client"], [/* 63 vars */]) = 0
brk(NULL) = 0x1506000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/home/wangjun/Qt5.7.0/5.7/gcc_64/lib/tls/x86_64/libevent-2.0.so.5", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/home/wangjun/Qt5.7.0/5.7/gcc_64/lib/tls/x86_64", 0x7ffe290072f0) = -1 ENOENT (No such file or directory)
open("/home/wangjun/Qt5.7.0/5.7/gcc_64/lib/tls/libevent-2.0.so.5", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/home/wangjun/Qt5.7.0/5.7/gcc_64/lib/tls", 0x7ffe290072f0) = -1 ENOENT (No such file or directory)
open("/home/wangjun/Qt5.7.0/5.7/gcc_64/lib/x86_64/libevent-2.0.so.5", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/home/wangjun/Qt5.7.0/5.7/gcc_64/lib/x86_64", 0x7ffe290072f0) = -1 ENOENT (No such file or directory)
open("/home/wangjun/Qt5.7.0/5.7/gcc_64/lib/libevent-2.0.so.5", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/home/wangjun/Qt5.7.0/5.7/gcc_64/lib", {st_mode=S_IFDIR|0775, st_size=20480, ...}) = 0
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=95323, ...}) = 0
mmap(NULL, 95323, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f813cd72000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
open("/usr/lib/libevent-2.0.so.5", O_RDONLY|O_CLOEXEC) = 3
read(3, "177ELF211 3 >