struct evsignal_info {
struct event ev_signal; /* 整体作为一个事件,加入epoll中 */
int ev_signal_pair[2]; /* 信号套接字 */
int ev_signal_added; /* 是否加入epoll */
volatile sig_atomic_t evsignal_caught; /* 是否有信号到来 */
struct event_list evsigevents[NSIG]; /* 信号链表 */
sig_atomic_t evsigcaught[NSIG]; /* 每个信号触发次数 */
struct sigaction **sh_old; /* 原信号 数组,对应sigaction第三个参数 */
int sh_old_max; /* 原信号 数组大小 */
};
1. evsigevents 与 evsigcaught
evsigevents 数组存放信号注册的事件,当发生该信号的时候,激活该信号下注册的信号事件。数组下标代表信号。
上图中,信号1注册了event1、event2两个事件。
evsigcaught 记录信号发生的次数,上图中,信号1发生了2次,而信号4发生了4次。信号事件在未被激活前,该信号发生了
4次,这样,在进行事件处理时,event3、event4的回调函数会分别被执行4次。这样,防止信号丢失。
2. sh_old、sh_old_max
sh_old_max 表示数字sh_old的大小。而,sh_old 为sigaction类型数组。
int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact); 注册新信号回调函数的时候,
记录oldact,即原信号的相关参数,以便于后续就行恢复工作。
libevent需要将所有事件(IO、信号,定时器)统一处理,因此不能在信号发生的时候立即处理该信号事件,而是
应该让epoll_wait返回,将信号对应的事件加入激活事件列表中。其一,我们知道,信号能够打断系统调用,
因此,epoll_wait 返回的时候,进行检查,是否有信号发生。其二,如果epoll_wait返回后,信号到来,此时,
我们需要一种记录,记录有信号到来,否则,信号就会丢失;另外,再次调用epoll_wait的时候,由于无法通知
epoll_wait此时已经有信号,所以,epoll_wait无法立即返回,导致信号无法及时处理。
res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout);
if (res == -1) {
if (errno != EINTR) {
return (-1);
}
evsignal_process(base); /* epoll_wait被信号中断 */
return (0);
}
/* do something */
当处于/* do something */ 的时候,有信号到来,在注册的信号回调函数中,通过evsignal_caught置1,标示
有信号到来,而再次调用epoll_wait 的时候,可能会阻塞很长时间,这就导致信号得不到及时处理。
创建一个socketpair,当有信号到来的时候,向socketpair中写数据,当调用epoll_wait的时候,由于有数据可读,
因此,epoll_wait可以立即返回,检测信号事件。 这样,需要向epoll中加入一个持久的读事件,上述结构体中,
ev_signal即为这个事件,而,该事件仅需要加入一次,且需要在有信号的时候加入,因此,通过ev_signal_added来
记录事件是否已经加入epoll。通过这种方式,在有信号的时候,让epoll_wait 尽快返回。
res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout);
if (res == -1) {
if (errno != EINTR) {
return (-1);
}
evsignal_process(base); /* 被信号中断 */
return (0);
} else if (base->sig.evsignal_caught) { /* epoll 返回,检测是否有信号到来 */
evsignal_process(base);
}
通过这种设计思路,只有有信号发生,能够保证epoll_wait能够立即返回,处理信号事件。
evsignal_init
1. 首先创建一个socketpair,用于向epoll_wait发送消息,及时返回,处理信号。
2. 将socket设置closeonexec,防止文件表无法被删除,参照《文件描述符》
3. 初始化evsigevents 队列,将收到的信号挂在对应的队列上,下标即为信号标示。
4. socket设置为非阻塞
5. ev_signal信号事件回调函数,仅仅用于读取发送端写入的一个字符。作用仅仅是让epoll_wait尽快返回。
6. 防止信号事件超时,将其设置为永久事件。
回调函数:
static void
evsignal_cb(int fd, short what, void *arg)
{
static char signals[1];
ssize_t n;
n = recv(fd, signals, sizeof(signals), 0);
}
添加信号事件
1. 对于信号事件,event中的fd代表信号,取得信号
2. 如果该信号对应的数组为空,则说明,没有对该信号注册回调函数,通过sigaction注册
3. 信号事件整个处理过程中需要一个event加入epoll来处理整个信号,所以判断该event是否已经加入,确保仅加入一次。ev_sginal,有信号事件的时候,才需要加入。
4. 如果信号已经被注册了回调函数,则又加入新信号事件,允许加入,且查到信号队列队尾。也就是说,同一信号,可能会注册不同的信号事件,有多个信号事件回调函数。
信号回调函数
1. 分配sh_old, 用于sigaction最后一个参数,记录原有信号信息,用于后续信号恢复。
2. 设置信号回调函数,仅仅是想socketpair中写入一个字节的数据,让epoll返回。
3. sigaction 进行注册。
普通事件回调函数:
该回调函数为sigaction 注册的回调函数:
static void
evsignal_handler(int sig)
{
int save_errno = errno;
if (evsignal_base == NULL) {
return;
}
evsignal_base->sig.evsigcaught[sig]++;
evsignal_base->sig.evsignal_caught = 1;
/* Wake up our notification mechanism */
send(evsignal_base->sig.ev_signal_pair[0], "a", 1, 0);
errno = save_errno;
}
这里,记录信号发生的次数,控制回调函数调用次数,如果信号发生n次,则调用回调函数n次。通知epoll_wait有信号啦。
信号事件处理函数:
1. 通过evsigcaught查找发生的信号。(元素非0,代表发生过)
2. 将evsigcaught 信号对应的元素清0
3. 若是非持久事件,则删除。即只响应一次后,还原。注意,若对应信号下没有事件了,恢复之前的信号处理函数。
4. 加入激活事件队列中
int
event_del(struct event *ev)
{
if (ev->ev_flags & EVLIST_TIMEOUT)
event_queue_remove(base, ev, EVLIST_TIMEOUT);
if (ev->ev_flags & EVLIST_ACTIVE)
event_queue_remove(base, ev, EVLIST_ACTIVE);
if (ev->ev_flags & EVLIST_INSERTED) {
event_queue_remove(base, ev, EVLIST_INSERTED);
return (evsel->del(evbase, ev));
}
return (0);
}
信号事件属于EVLIST_INSERTED队列,从该队列删除后,需要在epoll中移除。
static int
epoll_del(void *arg, struct event *ev)
{
if (ev->ev_events & EV_SIGNAL)
return (evsignal_del(ev));
epoll_del调用了evsignal_del,参见如下事件删除。
事件删除:
找到信号对应的事件队列,删除该事件,如果删除后,队列为空,则恢复之前的信号处理函数。记录内容在sh_old中。