数据结构
[include/types/signal.h]
struct signal_descriptor {
int count; /* number of times raised */
void (*handler)(int sig);
};
struct signal_descriptor signal_state[MAX_SIGNAL];
int signal_queue_len; /* length of signal queue, <= MAX_SIGNAL (1 entry per signal max) */
int signal_queue[MAX_SIGNAL]; /* in-order queue of received signals */
sigset_t blocked_sig;
信号描述符包括两个成员,一个是信号触发次数;另一个是信号的实际处理函数。signal_state是一个信号描述符的数组。
信号安装
[src/signal.c]signal_register
void signal_register(int sig, void (*handler)(int))
{
if (sig < 0 || sig > MAX_SIGNAL) {
qfprintf(stderr, "Failed to register signal %d : out of range [0..%d].\n", sig, MAX_SIGNAL);
return;
}
signal_state[sig].count = 0;
if (handler == NULL)
handler = SIG_IGN;
if (handler != SIG_IGN && handler != SIG_DFL) {
signal_state[sig].handler = handler;
signal(sig, signal_handler);
}
else {
signal_state[sig].handler = NULL;
signal(sig, handler);
}
}
从代码里能够看出,安装时会将信号已出现次数清零,然后检查给定的捕捉函数是否是NULL,若是,那么则将信号捕捉函数设置为忽略。当信号处理函数不是NULL也不是SIG_IGN以及SIG_DFL时,那么将给定的信号捕捉函数存入信号描述符中,再将signal_handler安装到系统的处理机制中。
[src/signal.c]signal_handler
void signal_handler(int sig)
{
if (sig < 0 || sig > MAX_SIGNAL || !signal_state[sig].handler) {
/* unhandled signal */
qfprintf(stderr, "Received unhandled signal %d. Signal has been disabled.\n", sig);
signal(sig, SIG_IGN);
return;
}
if (!signal_state[sig].count) {
/* signal was not queued yet */
if (signal_queue_len < MAX_SIGNAL)
signal_queue[signal_queue_len++] = sig;
else
qfprintf(stderr, "Signal %d : signal queue is unexpectedly full.\n", sig);
}
signal_state[sig].count++;
signal(sig, signal_handler); /* re-arm signal */
}
只有当信号在此之前还没出现的时候,signal_handler才会将信号入队,否则只是将信号的出现次数增加而已,最后重新安装信号处理函数。
从以上过程大致能推出作者是要将信号处理机制从系统中提到程序中来。
信号处理
[src/signal.c]
void __signal_process_queue()
{
int sig, cur_pos = 0;
struct signal_descriptor *desc;
sigset_t old_sig;
/* block signal delivery during processing */
sigprocmask(SIG_SETMASK, &blocked_sig, &old_sig);
for (cur_pos = 0; cur_pos < signal_queue_len; cur_pos++) {
sig = signal_queue[cur_pos];
desc = &signal_state[sig];
if (desc->count) {
if (desc->handler)
desc->handler(sig);
desc->count = 0;
}
}
signal_queue_len = 0;
/* restore signal delivery */
sigprocmask(SIG_SETMASK, &old_sig, NULL);
}
在此函数中,具体的信号处理之前,先将信号阻塞掉,然后轮询信号队列,并根据信号出现次数调用信号处理程序。最后将信号状态恢复成原始状态。
总结
作者将信号处理机制从系统级提升到程序中,好处至少有两个方面。首先是加强对信号处理的控制,增强程序的健壮性;再者是将可能比较繁杂的信号处理放到指定的较空闲时间来处理,一面影响程序效率。前者的例子就比如当管理员发送一个信号用于杀死进程的时候,假设此时正在处理一些业务,那么直接终止掉程序,这可能会导致一些问题,因此只简单的将信号入队,然后处理完业务再来处理,那么就会避免可能出现的问题。那为什么说这个也可能对效率产生影响呢?假如,信号处理比较复杂,并且信号的出现次数比较多,那么就有可能在系统忙的时候出现的多次的信号处理,这肯定会降低系统的响应时间,那么将信号提出来处理怎么能提升效率呢?很简单,因为程序员知道,程序在哪一部分繁忙,在哪一部分会闲一点。为了说明此问题,先看下程序的run loop。
[src/haproxy.c]
void run_poll_loop()
{
int next;
tv_update_date(0,1);
while (1) {
/* check if we caught some signals and process them */
signal_process_queue();
/* Check if we can expire some tasks */
wake_expired_tasks(&next);
/* Process a few tasks */
process_runnable_tasks(&next);
/* maintain all proxies in a consistent state. This should quickly
* become a task because it becomes expensive when there are huge
* numbers of proxies. */
maintain_proxies(&next);
/* stop when there's no connection left and we don't allow them anymore */
if (!actconn && listeners == 0)
break;
/* The poller will ensure it returns around <next> */
cur_poller.poll(&cur_poller, next);
}
}
程序在每一轮循环开始的时候做一次信号处理,其他的是程序的业务相关的代码。现在我们可以这样看,在程序的一轮业务处理完成的时候,按照常理来讲,此时程序是处于比较空闲的时候的。因为若程序还是处于很忙的状态,那么他就不会从业务处理中转回来。