haproxy的中断处理
本文简单介绍一下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中的中断处理,简单高效。