haproxy的中断处理

2013年4月7日 | 分类: 操作系统, 编程技术 | 标签:

本文简单介绍一下haproxy中的中断封装,所谓的中断,就是在程序运行过程中,能够接受中断信号,暂时处理中断函数,然后再返回继续执行程序的过程。

haproxy自定义了很多中断操作,比如:SIGQUIT、SIGUSR1、SIGHUP等。

但是haproxy处理中断又不是每次接收到就立马去处理,而是首先收集中断,然后在一个run_poll_loop的大循环中统一处理一次,这样做一方面这些中断的实时性要求不是特别高,一次循环处理一次(正常情况下1s足够),另外统一处理可以防止中断风暴对程序的影响,即便收到很多次中断信号,也只需要处理一次中断函数即可,可以单进程的haproxy专心处理请求,而不用关心中断事件。

首先看一下中断描述结构:

struct signal_descriptor {
int count; /* number of times raised */
void (*handler)(int sig);
};
int signal_queue_len; 
int signal_queue[MAX_SIGNAL]; 
struct signal_descriptor signal_state[MAX_SIGNAL];

程序按照中断发生的时间顺序,把中断收集到signal_queue中,下标顺序就是先后顺序,signal_queue_len记录目前收集到的中断总数目。每一个中断号对应的发生次数和中断处理函数保存在signal_state中,这样就构成了haproxy的主要中断数据结构。

中断注册时:

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;
	}
//将该中断的状态初始化,首先中断次数为0,如果中断函数为空,那么设置中断函数为SIG_IGN忽略该中断
	signal_state[sig].count = 0;
	if (handler == NULL)
		handler = SIG_IGN;
//假如中断函数handler不为NULL,则将handler设置给全局的中断描述signal_state,
//并设置该中断对应的中断函数为signal_handler,它用来负责收集中断
	if (handler != SIG_IGN && handler != SIG_DFL) {
		signal_state[sig].handler = handler;
		signal(sig, signal_handler);
	}
//如果中断函数是SIG_IGN或者SIG_DFL,则指定处理函数为空,中断处理函数直接为SIG_IGN或SIG_DFL即可
	else {
		signal_state[sig].handler = NULL;
		signal(sig, handler);
	}
}

从上面可以看到,用户自定义的中断函数handler并没有直接设置成signal()中的中断函数,而是用了signal_handler作为中断函数,下面看一下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;
	}
//如果该中断尚未发生过,则把该中断插入到中断队列signal_queue中,指定
signal_queue[signal_queue_len]对应的中断是sig
	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);
	}
//如果该中断号已经发生过,简单的对计数器++,并且重新设置中断sig,signal_handler即可
	signal_state[sig].count++;
	signal(sig, signal_handler); /* re-arm signal */
}

下面看一下haproxy在统一处理中断时候的操作__signal_process_queue:

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);
//对signal_queue中的中断按照发生的顺序,逐个操作,并且重置count计数器
	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);
}

haproxy中的main中注册中断时如下:

void sig_pause(int sig)
{
	pause_proxies();
	pool_gc2();
}
void sig_int(int sig)
{
	fast_stop();
	pool_gc2();
	/* If we are killed twice, we decide to die */
	signal_register(sig, SIG_DFL);
}
……
	signal_register(SIGTTOU, sig_pause);
	signal_register(SIGINT, sig_int);

注意有些自定义中断函数的结尾中是signal_register(sig, SIG_DFL);其作用是,第一次发生SIGINT中断时,调用时执行用户自定义的操作,如sig_int中的fast_stop,pool_gc2,然后设定SIGINT的中断操作为默认,这时又发生了一次SIGINT中断,则直接执行SIGINT的默认操作终止程序。

总之:haproxy中的中断处理,简单高效。

本文的评论功能被关闭了.