nginx中master进程来管理整个nginx工作的全过程,运行时其通过接收外部信号输入的方式来对内部进行相关调整。本文对照nginx来梳理一下linux中信号常用API的使用。
目录
1.函数sigaction和signal
2.关于信号集sigset_t
2.1 测试程序1
2.2 测试程序1
3.信号屏蔽和不屏蔽的效果
3.1信号不屏蔽
3.2信号屏蔽
4.nginx中master进程信号处理分析
5.模拟nginx信号捕获处理
信号的本质:内核实现的一种软中断
sigaction是比signal功能更加强大的信号捕捉函数,其依赖于结构体struct sigaction,定义如下:
/* Type of a signal handler. */
typedef void (*__sighandler_t) (int);struct sigaction
{
/* Signal handler. */
#if defined __USE_POSIX199309 || defined __USE_XOPEN_EXTENDED
union
{
/* Used if SA_SIGINFO is not set. */
__sighandler_t sa_handler;
/* Used if SA_SIGINFO is set. */
void (*sa_sigaction) (int, siginfo_t *, void *);
}
__sigaction_handler;
# define sa_handler __sigaction_handler.sa_handler
# define sa_sigaction __sigaction_handler.sa_sigaction
#else
__sighandler_t sa_handler;
#endif/* Additional set of signals to be blocked. */
__sigset_t sa_mask;/* Special flags. */
int sa_flags;/* Restore handler. */
void (*sa_restorer) (void);
};
sigaction可以定义信号处理函数执行时别的信号到来后的动作,结构sigaction中sa_flags赋值为SA_SIGINFO时,信号处理函数为sa_sigaction,否则信号处理函数为(此时退化为和signal函数一样的功能)sa_handler。
信号集sigset_t定义如下:
#define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))
typedef struct
{
unsigned long int __val[_SIGSET_NWORDS];
} __sigset_t;typedef __sigset_t sigset_t;
信号集sigset_t处理是位图操作,需要通过其api进行操作,常用api如下:
sigemptyset:清空信号集合
sigaddset:信号集中增加某个信号
sigprocmask:用于改变进程的当前阻塞信号集,也可以用来检测当前进程的信号掩码。
每个进程都有一个用来描述哪些信号递送到进程时将被阻塞的信号集,该信号集中的所有信号在递送到进程后都将被阻塞。
备注:每个进程都有一个用来描述哪些信号递送到进程时将被阻塞的信号集,该信号集中的所有信号在递送到进程后都将被阻塞。
int sigprocmask(int how,const sigset_t *set,sigset_t * oldset);
sigprocmask可以用来改变目前的信号掩码,其操作依参数how来决定:
SIG_BLOCK 新的信号掩码由目前的信号掩码和参数set 指定的信号掩码作并集,也就是将set中的信号掩码添加到现有的信号掩码集合中
SIG_UNBLOCK 将目前的信号=掩码)=删除掉参数set指定的信号掩码,也就是在现有的信号掩码集合中删去set制定的信号掩码
SIG_SETMASK 将目前的信号掩码设成参数set指定的信号掩码。
(SIG_BLOCK是添加,SIG_UNBLOCK是删除,SIG_SETMASK是直接赋值)
如果参数oldset不是NULL指针,oldset指向目前的信号掩码
注意事项:sigprocmask()函数只为单线程的进程定义的,在多线程中要使用pthread_sigmask变量,在使用之前需要声明和初始化。
sigsuspend: 信号挂起,等待有信号来,类似于条件变量的功能
备注:sigsuspend是一个原子操作,包含4个步骤:
(1) 设置新的mask阻塞当前进程;
(2) 收到信号,恢复原先mask;
(3) 调用该进程设置的信号处理函数;
(4) 待信号处理函数返回后,sigsuspend返回。
sigaction:设置信号处理函数
sigfillset:填充信号集
sigdelset:信号集中删除信号
sigismember:信号在信号集中
sigpending: 检查悬而未决的信号集
#include
#include
#include
#include
void sig_handler (int num) {
printf("signal %d\n", num);
}
//使用sigprocmask
void test1() {
sigset_t set;
signal(SIGINT, sig_handler);
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigprocmask(SIG_SETMASK, &set, NULL);
//sigprocmask(SIG_BLOCK, &set, NULL);
while(1) {
printf("master process running ...\n");
sleep(10);
}
}
//使用sigprocmask + sigsuspend
void test2() {
sigset_t set;
signal(SIGINT, sig_handler);
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigprocmask(SIG_SETMASK, &set, NULL);
//sigprocmask(SIG_BLOCK, &set, NULL);
sigemptyset(&set);
while(1) {
printf("master process running ...\n");
sigsuspend(&set);
sleep(10);
}
}
int main()
{
#ifdef TEST1
test1();
#else
test2();
#endif
return 0;
}
(1)测试sigprocmask
g++ test.cpp -DTEST1
./a.out
运行结果如下:
test1()中
sigaddset(&set, SIGINT);
sigprocmask(SIG_SETMASK, &set, NULL);
屏蔽了信号SIGINT,所以多次发送ctrl + c程序没有相应。
(2)测试sigprocmask + sigsuspend
g++ test.cpp
./a.out
运行结果如下:
运行结果分析:
test2()中虽然调用
sigaddset(&set, SIGINT);
sigprocmask(SIG_SETMASK, &set, NULL);
屏蔽了信号SIGINT,但是调用了sigsuspend(&set);
设置新的mask(程序中为空信号集)阻塞当前进程,收到信号,恢复原先mask。执行信号处理函数,sigsuspend是一个原子操作,其包含如下步骤:
(1) 设置新的mask阻塞当前进程;
(2) 收到信号,恢复原先mask;
(3) 调用该进程设置的信号处理函数;
(4) 待信号处理函数返回后,sigsuspend返回。
#include
#include
#include
#include
//SIGQUIT -> ctrl + \产生
static void sig_quit(int signo) {
printf("caught SIGQUIT : %d\n", signo);
//将SIGQUIT的动作设为缺省值
signal(SIGQUIT, SIG_DFL);
}
int main() {
sigset_t newmask;
sigset_t oldmask;
sigset_t pendmask;
//信号量捕捉函数,捕捉到SIGQUIT,跳转到函数指针sig_quit处执行
signal(SIGQUIT, sig_quit);
//初始化信号量集
sigemptyset(&newmask);
//将SIGQUIT添加到信号量集中
sigaddset(&newmask, SIGQUIT);
//将newmask中的SIGQUIT阻塞掉,并保存当前信号屏蔽字
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
sleep (5);
//检查信号是悬而未决的
sigpending(&pendmask);
//SIGQUIT是悬而未决的。所谓悬而未决,是指SIGQUIT被阻塞还没有被处理
if (sigismember(&pendmask, SIGQUIT)) {
printf("SIGQUIT pending\n");
}
//恢复被屏蔽的信号SIGQUIT
sigprocmask(SIG_SETMASK, &oldmask, NULL);
printf("SIGQUIT unblocked\n");
sleep(5);
printf("end end\n");
return 0;
}
运行结果如下:
运行结果分析:
sigprocmask(SIG_BLOCK, &newmask, &oldmask);屏蔽信号SIGQUIT,给进程发送SIGQUIT,SIGQUIT为未决信号,5秒后 sigpending(&pendmask);获取未决信号集,sigismember(&pendmask, SIGQUIT)调用后发现SIGQUIT位于未决信号集中,输出SIGQUIT pending,恢复最原始信号屏蔽集合,输出SIGQUIT unblocked,此时发送SIGQUIT,执行信号捕捉函数,恢复其默认执行,程序立刻退出。
#include
#include
#include
#include
/*
SIGQUIT:Ctrl+\ 产生 默认行为: Quit (core dumped),程序退出 忽略:signal(SIGINT, SIG_IGN);
SIGINT:Ctrl+c 产生 默认行为:程序退出 忽略:signal(SIGQUIT, SIG_IGN);
无法捕获的信息号:
SIGKILL
*/
void catch_sig_handler(int num) {
printf("begin call,catch %d sig\n", num);
sleep(5);
printf("end call,catch %d sig\n", num);
}
//发送Ctrl+\发送SIGQUIT消息时,程序立刻 Quit (core dumped)
void test1() {
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = catch_sig_handler;
//清空屏蔽信号集
sigemptyset(&act.sa_mask);
//等待按键Ctrl+c,捕捉2号信号
sigaction(SIGINT, &act, NULL);
signal(SIGINT, catch_sig_handler);
}
//发送Ctrl+\发送SIGQUIT消息时,SIGINT信号函数处理完毕程序才会Quit (core dumped)
void test2() {
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = catch_sig_handler;
//清空屏蔽信号集
sigemptyset(&act.sa_mask);
//临时屏蔽Ctrl+\信号 执行SIGINT信号期间,先不处理SIGQUIT信号
sigaddset(&act.sa_mask, SIGQUIT);
//等待按键Ctrl+c,捕捉2号信号
sigaction(SIGINT, &act, NULL);
}
int main(int argc, char *argv[])
{
#ifdef TEST1
test1();
#else
test2();
#endif
while(1) {
printf("I am running ......\n");
sleep(1);
}
return 0;
}
g++ test.cpp -DTEST1
./a.out
运行效果:
由于没有屏蔽信号SIGQUIT(Ctrl+\),所以收到SIGQUIT后程序直接退出
g++ test.cpp
./a.out
运行效果:
由于调用sigaddset(&act.sa_mask, SIGQUIT);屏蔽信号SIGQUIT,所以在执行SIGINT信号处理函数过程中是不会被SIGQUIT所干扰的,只有信号处理函数执行完毕后才执行SIGQUIT的动作(默认是退出程序)。
nginx中信号处理在函数ngx_init_signals和ngx_master_process_cycle中,代码调用大致逻辑如下:
int ngx_cdecl
main(int argc, char *const *argv)
{
...
//设置信号处理函数,初始化需要捕获的信号
if (ngx_init_signals(cycle->log) != NGX_OK) {
return 1;
}
...
主线程捕获函数
ngx_master_process_cycle(cycle);
...
return 0;
}
ngx_init_signals定义如下:
通过 sigprocmask + sigsuspend配合在保证master主进程接收外部信号输入并及时处理。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define GROUP_NUM 2
typedef struct {
int signo;
char *signame;
char *name;
void (*handler)(int signo, siginfo_t *siginfo, void *ucontext);
} ngx_signal_t;
#define ngx_signal_helper(n) SIG##n
#define ngx_signal_value(n) ngx_signal_helper(n)
#define ngx_value_helper(n) #n
#define ngx_value(n) ngx_value_helper(n)
#define NGX_SHUTDOWN_SIGNAL QUIT
#define NGX_TERMINATE_SIGNAL TERM
#define NGX_NOACCEPT_SIGNAL WINCH
#define NGX_RECONFIGURE_SIGNAL HUP
#if (NGX_LINUXTHREADS)
#define NGX_REOPEN_SIGNAL INFO
#define NGX_CHANGEBIN_SIGNAL XCPU
#else
#define NGX_REOPEN_SIGNAL USR1
#define NGX_CHANGEBIN_SIGNAL USR2
#endif
#define ngx_errno errno
typedef intptr_t ngx_int_t;
typedef int ngx_err_t;
typedef uintptr_t ngx_uint_t;
typedef pid_t ngx_pid_t;
typedef ngx_uint_t ngx_rbtree_key_t;
typedef ngx_int_t ngx_rbtree_key_int_t;
typedef ngx_rbtree_key_t ngx_msec_t;
typedef ngx_rbtree_key_int_t ngx_msec_int_t;
#define ngx_getpid getpid
#define ngx_getppid getppid
#define ngx_gettimeofday(tp) (void) gettimeofday(tp, NULL);
#define ngx_msleep(ms) (void) usleep(ms * 1000)
#define ngx_sleep(s) (void) sleep(s)
#define NGX_PROCESS_SINGLE 0
#define NGX_PROCESS_MASTER 1
#define NGX_PROCESS_SIGNALLER 2
#define NGX_PROCESS_WORKER 3
#define NGX_PROCESS_HELPER 4
#define NGX_OK 0
#define NGX_ERROR -1
#define NGX_AGAIN -2
#define NGX_BUSY -3
#define NGX_DONE -4
#define NGX_DECLINED -5
#define NGX_ABORT -6
ngx_uint_t ngx_process;
ngx_uint_t ngx_worker;
ngx_pid_t ngx_pid;
ngx_pid_t ngx_parent;
sig_atomic_t ngx_reap;
sig_atomic_t ngx_sigio;
sig_atomic_t ngx_sigalrm;
sig_atomic_t ngx_terminate;
sig_atomic_t ngx_quit;
sig_atomic_t ngx_debug_quit;
ngx_uint_t ngx_exiting;
sig_atomic_t ngx_reconfigure;
sig_atomic_t ngx_reopen;
sig_atomic_t ngx_change_binary;
ngx_pid_t ngx_new_binary;
ngx_uint_t ngx_inherited;
ngx_uint_t ngx_daemonized;
sig_atomic_t ngx_noaccept;
ngx_uint_t ngx_noaccepting;
ngx_uint_t ngx_restart;
void
ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext);
ngx_signal_t signals[] = {
{ ngx_signal_value(NGX_RECONFIGURE_SIGNAL),
"SIG" ngx_value(NGX_RECONFIGURE_SIGNAL),
"reload",
ngx_signal_handler },
{ ngx_signal_value(NGX_REOPEN_SIGNAL),
"SIG" ngx_value(NGX_REOPEN_SIGNAL),
"reopen",
ngx_signal_handler },
{ ngx_signal_value(NGX_NOACCEPT_SIGNAL),
"SIG" ngx_value(NGX_NOACCEPT_SIGNAL),
"",
ngx_signal_handler },
{ ngx_signal_value(NGX_TERMINATE_SIGNAL),
"SIG" ngx_value(NGX_TERMINATE_SIGNAL),
"stop",
ngx_signal_handler },
{ ngx_signal_value(NGX_SHUTDOWN_SIGNAL),
"SIG" ngx_value(NGX_SHUTDOWN_SIGNAL),
"quit",
ngx_signal_handler },
{ ngx_signal_value(NGX_CHANGEBIN_SIGNAL),
"SIG" ngx_value(NGX_CHANGEBIN_SIGNAL),
"",
ngx_signal_handler },
{ SIGALRM, "SIGALRM", "", ngx_signal_handler },
{ SIGINT, "SIGINT", "", ngx_signal_handler },
{ SIGIO, "SIGIO", "", ngx_signal_handler },
{ SIGCHLD, "SIGCHLD", "", ngx_signal_handler },
{ SIGSYS, "SIGSYS, SIG_IGN", "", NULL },
{ SIGPIPE, "SIGPIPE, SIG_IGN", "", NULL },
{ 0, NULL, "", NULL }
};
void
ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext)
{
char *action;
ngx_int_t ignore;
ngx_err_t err;
ngx_signal_t *sig;
ignore = 0;
err = ngx_errno;
printf("signo = [%d]\n", signo);
for (sig = signals; sig->signo != 0; sig++) {
if (sig->signo == signo) {
break;
}
}
action = "";
switch (ngx_process) {
case NGX_PROCESS_MASTER:
case NGX_PROCESS_SINGLE:
switch (signo) {
case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
ngx_quit = 1;
action = ", shutting down";
printf("recv signal SIGQUIT\n");
sleep(5);
break;
case ngx_signal_value(NGX_TERMINATE_SIGNAL):
case SIGINT:
ngx_terminate = 1;
action = ", exiting";
printf("recv signal SIGINT\n");
sleep(5);
break;
case ngx_signal_value(NGX_NOACCEPT_SIGNAL):
if (ngx_daemonized) {
ngx_noaccept = 1;
action = ", stop accepting connections";
}
break;
case ngx_signal_value(NGX_RECONFIGURE_SIGNAL):
ngx_reconfigure = 1;
action = ", reconfiguring";
break;
case ngx_signal_value(NGX_REOPEN_SIGNAL):
ngx_reopen = 1;
action = ", reopening logs";
break;
case ngx_signal_value(NGX_CHANGEBIN_SIGNAL):
if (ngx_getppid() == ngx_parent || ngx_new_binary > 0) {
action = ", ignoring";
ignore = 1;
break;
}
ngx_change_binary = 1;
action = ", changing binary";
break;
case SIGALRM:
ngx_sigalrm = 1;
break;
case SIGIO:
ngx_sigio = 1;
break;
case SIGCHLD:
ngx_reap = 1;
break;
}
break;
case NGX_PROCESS_WORKER:
case NGX_PROCESS_HELPER:
switch (signo) {
case ngx_signal_value(NGX_NOACCEPT_SIGNAL):
if (!ngx_daemonized) {
break;
}
ngx_debug_quit = 1;
/* fall through */
case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
ngx_quit = 1;
action = ", shutting down";
break;
case ngx_signal_value(NGX_TERMINATE_SIGNAL):
case SIGINT:
ngx_terminate = 1;
action = ", exiting";
break;
case ngx_signal_value(NGX_REOPEN_SIGNAL):
ngx_reopen = 1;
action = ", reopening logs";
break;
case ngx_signal_value(NGX_RECONFIGURE_SIGNAL):
case ngx_signal_value(NGX_CHANGEBIN_SIGNAL):
case SIGIO:
action = ", ignoring";
break;
}
break;
}
if (siginfo && siginfo->si_pid) {
// printf("signal %d (%s) received from %P%s",
// signo, sig->signame, siginfo->si_pid, action);
printf("signal %d (%s)\n", signo, sig->signame);
} else {
/*
printf("signal %d (%s) received%s",
signo, sig->signame, action);
*/
printf("signal %d (%s)\n", signo, sig->signame);
}
if (ignore) {
printf("the changing binary signal is ignored: "
"you should shutdown or terminate "
"before either old or new binary's process");
}
//printf("action = [%s]\n", action);
}
ngx_int_t
ngx_init_signals()
{
ngx_signal_t *sig;
struct sigaction sa;
for (sig = signals; sig->signo != 0; sig++) {
memset(&sa, 0x00, sizeof(struct sigaction));
if (sig->handler) {
sa.sa_sigaction = sig->handler;
sa.sa_flags = SA_SIGINFO;
} else {
sa.sa_handler = SIG_IGN;
}
sigemptyset(&sa.sa_mask);
if (sigaction(sig->signo, &sa, NULL) == -1) {
#if (NGX_VALGRIND)
printf("sigaction(%s) failed, ignored", sig->signame);
#else
printf("sigaction(%s) failed", sig->signame);
return NGX_ERROR;
#endif
}
}
return NGX_OK;
}
void
ngx_master_process_cycle()
{
char *title;
u_char *p;
size_t size;
ngx_int_t i;
ngx_uint_t sigio;
sigset_t set;
struct itimerval itv;
ngx_uint_t live;
ngx_msec_t delay;
sigemptyset(&set);
sigaddset(&set, SIGCHLD);
sigaddset(&set, SIGALRM);
sigaddset(&set, SIGIO);
sigaddset(&set, SIGINT);
sigaddset(&set, ngx_signal_value(NGX_RECONFIGURE_SIGNAL));
sigaddset(&set, ngx_signal_value(NGX_REOPEN_SIGNAL));
sigaddset(&set, ngx_signal_value(NGX_NOACCEPT_SIGNAL));
sigaddset(&set, ngx_signal_value(NGX_TERMINATE_SIGNAL));
sigaddset(&set, ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
sigaddset(&set, ngx_signal_value(NGX_CHANGEBIN_SIGNAL));
//如下代码的含义: 系统很忙的,没有时间及时相应信号,进程可以先把信号阻塞掉,等系统有空闲时间再去相应,这也保证了信号的可靠性。
if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {
printf("sigprocmask() failed, ngx_errno:%d\n", ngx_errno);
}
sigemptyset(&set);
ngx_new_binary = 0;
delay = 0;
sigio = 0;
live = 1;
for ( ;; ) {
printf("master process running ......\n");
if (delay) {
/*
if (ngx_sigalrm) {
sigio = 0;
delay *= 2;
ngx_sigalrm = 0;
}
printf("termination cycle: %M\n", delay);
itv.it_interval.tv_sec = 0;
itv.it_interval.tv_usec = 0;
itv.it_value.tv_sec = delay / 1000;
itv.it_value.tv_usec = (delay % 1000 ) * 1000;
if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
printf("setitimer() failed, ngx_errno:%d\n", ngx_errno);
}
*/
}
sigsuspend(&set);
if (ngx_reap) {
ngx_reap = 0;
live = 1;
}
if (!live && (ngx_terminate || ngx_quit)) {
printf("master process exit\n");
exit(0);
}
if (ngx_terminate) {
if (delay == 0) {
delay = 50;
}
if (sigio) {
sigio--;
continue;
}
continue;
}
if (ngx_quit) {
continue;
}
if (ngx_reconfigure) {
ngx_reconfigure = 0;
if (ngx_new_binary) {
ngx_noaccepting = 0;
continue;
}
printf("reconfiguring \n");
/* allow new processes to start */
ngx_msleep(100);
live = 1;
}
if (ngx_restart) {
ngx_restart = 0;
live = 1;
}
if (ngx_reopen) {
ngx_reopen = 0;
}
if (ngx_change_binary) {
ngx_change_binary = 0;
}
if (ngx_noaccept) {
ngx_noaccept = 0;
ngx_noaccepting = 1;
}
}
}
int main()
{
//信号初始化
ngx_init_signals();
//创建子进程
for (int i = 0; i < GROUP_NUM; i++) {
int pid = fork();
switch (pid) {
case -1: // error
return -1;
case 0: // child
sleep(2);
printf("child exit \n");
exit(0);
default: // parent
break;
}
}
//主进程执行
ngx_master_process_cycle();
return 0;
}
编译运行:
通过运行可以看出程序很快速的交替响应信号SIGINT和SIGQUIT。