信号函数
signal函数
头文件:#include <signal.h>
定义函数:void (*signal(int signum, void(* handler)(int)))(int);
函数说明:signal()会依参数signum 指定的信号编号来设置该信号的处理函数. 当指定的信号到达时就会跳转到参数handler 指定的函数执行. 如果参数handler 不是函数指针, 则必须是下列两个常数之一:
1、SIG_IGN 忽略参数signum 指定的信号.
2、SIG_DFL 将参数signum 指定的信号重设为核心预设的信号处理方式.
关于信号的编号和说明, 请参考附录D
返回值:返回先前的信号处理函数指针, 如果有错误则返回SIG_ERR(-1).
附加说明:在信号发生跳转到自定的 handler 处理函数执行后, 系统会自动将此处理函数换回原来系统预设的处理方式, 如果要改变此操作请改用sigaction().
sigaction函数
头文件:#include <signal.h>
定义函数:int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
函数说明:sigaction()会依参数signum 指定的信号编号来设置该信号的处理函数. 参数signum 可以指定SIGKILL 和SIGSTOP 以外的所有信号。如参数结构sigaction 定义如下:
1 2 3 4 5 6 7
| struct sigaction { void (*sa_handler) (int); sigset_t sa_mask; int sa_flags; void (*sa_restorer) (void); }
|
1、sa_handler 此参数和signal()的参数handler 相同, 代表新的信号处理函数, 其他意义请参考signal().
2、sa_mask 用来设置在处理该信号时暂时将sa_mask 指定的信号搁置.
3、sa_restorer 此参数没有使用.
4、sa_flags 用来设置信号处理的其他相关操作, 下列的数值可用:
A_NOCLDSTOP: 如果参数signum 为SIGCHLD, 则当子进程暂停时并不会通知父进程
SA_ONESHOT/SA_RESETHAND: 当调用新的信号处理函数前, 将此信号处理方式改为系统预设的方式.
SA_RESTART: 被信号中断的系统调用会自行重启
SA_NOMASK/SA_NODEFER: 在处理此信号未结束前不理会此信号的再次到来. 如果参数oldact 不是NULL 指针, 则原来的信号处理方式会由此结构sigaction 返回.
返回值:执行成功则返回0, 如果有错误则返回-1.
错误代码:
1、EINVAL 参数signum 不合法, 或是企图拦截SIGKILL/SIGSTOPSIGKILL 信号。
2、EFAULT 参数act, oldact 指针地址无法存取。
3、EINTR 此调用被中断。
商业软件中,不用signal(),而要用sigaction();
sigaction结构体
1 2 3 4 5 6 7
| struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t*, void*); sigset_t sa_mask; int sa_flags; };
|
sa_handler
:信号处理器函数的地址,亦或是常量SIG_IGN
、SIG_DFL
之一。仅当sa_handler
是信号处理程序的地址时,亦即sa_handler的取值在SIG_IGN
和SIG_DFL
之外,才会对sa_mask
和sa_flags
字段加以处理。
sa_sigaction
:如果设置了SA_SIGINFO
标志位,则会使用sa_sigaction
处理函数,否则使用sa_handler
处理函数。
sa_mask
:定义一组信号,在调用由sa_handler
所定义的处理器程序时将阻塞该组信号,不允许它们中断此处理器程序的执行。
sa_flags
:位掩码,指定用于控制信号处理过程的各种选项。
SA_NODEFER
:捕获该信号时,不会在执行处理器程序时将该信号自动添加到进程掩码中。
SA_ONSTACK
:针对此信号调用处理器函数时,使用了由sigaltstack()
安装的备选栈。
SA_RESETHAND
:当捕获该信号时,会在调用处理器函数之前将信号处置重置为默认值(即SIG_IGN
)。
SA_SIGINFO
:调用信号处理器程序时携带了额外参数,其中提供了关于信号的深入信息
初始化信号的函数
信号相关结构体,这里设置一个数组,方便后续根据信号名称调用对应的信号处理函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| typedef struct { int signo; const char *signame;
void (*handler)(int signo, siginfo_t *siginfo, void *ucontext); } ngx_signal_t;
ngx_signal_t signals[] = { { SIGHUP, "SIGHUP", ngx_signal_handler }, { SIGINT, "SIGINT", ngx_signal_handler }, { SIGTERM, "SIGTERM", ngx_signal_handler }, { SIGCHLD, "SIGCHLD", ngx_signal_handler }, { SIGQUIT, "SIGQUIT", ngx_signal_handler }, { SIGIO, "SIGIO", ngx_signal_handler }, { SIGSYS, "SIGSYS, SIG_IGN", NULL }, { 0, NULL, NULL } };
|
这种信号处理的写法十分的固定,代码简单,但是注意一些小细节:
- 临时定义结构体sigaction sa;利用sa临时设置信号选项和信号的处理函数或者忽略该信号
- 用sa.sa_sigaction指定信号处理函数,sa.sa_flags指定信号选项
- 然后调用
sigaction(sig->signo, &sa, NULL)
信号处理动作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
|
int ngx_init_signals() { ngx_signal_t *sig; struct sigaction sa;
for (sig = signals; sig->signo != 0; sig++) { memset(&sa,0,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) { ngx_log_error_core(NGX_LOG_EMERG,errno,"sigaction(%s) failed",sig->signame); return -1; } else { } } return 0; }
static void ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext) { printf("来信号了\n"); }
|
注意void ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext);
信号处理函数是我们自己声明的,但是它的参数和返回值是固定的【操作系统就这样要求】,写的时候就先这么写,也不用思考这么多;
ngx_signal_t signals[]
定义本系统处理的各种信号,我们取一小部分nginx中的信号,并没有全部搬移到这里,日后若有需要根据具体情况再增加,在实际商业代码中,你能想到的要处理的信号,都弄进来。
而int ngx_init_signals()
函数中设置信号为对应的处理函数或者忽略该信号。
1 2 3 4 5 6
| if (sig->handler){ sa.sa_sigaction = sig->handler; sa.sa_flags = SA_SIGINFO; }else{ sa.sa_handler = SIG_IGN; }
|
信号处理函数
这个信号处理函数根据ngx_process判断父子进程而对相同的信号执行不同的操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| static void ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext) { ngx_signal_t *sig; char *action; for (sig = signals; sig->signo != 0; sig++) { if (sig->signo == signo) { break; } }
action = (char *)"";
if(ngx_process == NGX_PROCESS_MASTER) { switch (signo) { case SIGCHLD: ngx_reap = 1; break;
default: break; } } else if(ngx_process == NGX_PROCESS_WORKER) { } else { }
if(siginfo && siginfo->si_pid) { ngx_log_error_core(NGX_LOG_NOTICE,0,"signal %d (%s) received from %P%s", signo, sig->signame, siginfo->si_pid, action); } else { ngx_log_error_core(NGX_LOG_NOTICE,0,"signal %d (%s) received %s",signo, sig->signame, action); }
if (signo == SIGCHLD) { ngx_process_get_status(); }
return; }
|
获取信号状态函数
避免子进程被杀掉时变成僵尸进程
父进程要处理SIGCHILD信号并在信号处理函数中调用waitpid()来解决僵尸进程的问题;
信号处理函数中的代码,要坚持一些书写原则:
a)代码尽可能简单,尽可能快速的执行完毕返回;
b)用一些全局量做一些标记;尽可能不调用函数;
c)不要在信号处理函数中执行太复杂的代码以免阻塞其他信号的到来,甚至阻塞整个程序执行流程;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| static void ngx_process_get_status(void) { pid_t pid; int status; int err; int one=0;
for ( ;; ) { pid = waitpid(-1, &status, WNOHANG);
if(pid == 0) { return; } if(pid == -1) { err = errno; if(err == EINTR) { continue; }
if(err == ECHILD && one) { return; }
if (err == ECHILD) { ngx_log_error_core(NGX_LOG_INFO,err,"waitpid() failed!"); return; } ngx_log_error_core(NGX_LOG_ALERT,err,"waitpid() failed!"); return; } one = 1; if(WTERMSIG(status)) { ngx_log_error_core(NGX_LOG_ALERT,0,"pid = %P exited on signal %d!",pid,WTERMSIG(status)); } else { ngx_log_error_core(NGX_LOG_NOTICE,0,"pid = %P exited with code %d!",pid,WEXITSTATUS(status)); } } return; }
|
waitpid的返回值一共有3种情况:
- 当正常返回的时候,waitpid返回收集到的子进程的进程ID;
- 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
- 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid就会出错返回,这时errno被设置为ECHILD;
注意在for循环当中
第一次waitpid返回一个> 0值,表示成功,后边显示 2019/01/14 21:43:38 [alert] 3375: pid = 3377 exited on signal 9【SIGKILL】
第二次再循环回来,再次调用waitpid会返回一个0,表示没有已退出的子进程且子进程还没结束,然后这里有return来退出;
ngx_master_process_cycle父进程死循环
- 先调用sigaddset防止10个信号的干扰,当master该做的事情做完了进入一个死循环for
- for里面调用
sigsuspend(&set);
进程是挂起的,不占用cpu时间,只有收到信号才会被唤醒
sigsuspend是一个原子操作,包含4个步骤:
- 根据给定的参数设置新的mask 并 阻塞当前进程【因为是个空集,所以不阻塞任何信号】
- 此时,一旦收到信号,便恢复原先的信号屏蔽【我们原来调用sigprocmask()的mask在上边设置的,阻塞了多达10个信号,从而保证我下边的执行流程不会再次被其他信号截断】
- 调用该信号对应的信号处理函数
- 信号处理函数返回后,sigsuspend返回,使程序流程继续往下走
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| void ngx_master_process_cycle() { sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGCHLD); sigaddset(&set, SIGALRM); sigaddset(&set, SIGIO); sigaddset(&set, SIGINT); sigaddset(&set, SIGHUP); sigaddset(&set, SIGUSR1); sigaddset(&set, SIGUSR2); sigaddset(&set, SIGWINCH); sigaddset(&set, SIGTERM); sigaddset(&set, SIGQUIT); if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) { ngx_log_error_core(NGX_LOG_ALERT,errno,"ngx_master_process_cycle()中sigprocmask()失败!"); }
size_t size; int i; size = sizeof(master_process); size += g_argvneedmem; if(size < 1000) { char title[1000] = {0}; strcpy(title,(const char *)master_process); strcat(title," "); for (i = 0; i < g_os_argc; i++) { strcat(title,g_os_argv[i]); } ngx_setproctitle(title); ngx_log_error_core(NGX_LOG_NOTICE,0,"%s %P 【master进程】启动并开始运行......!",title,ngx_pid); } CConfig *p_config = CConfig::GetInstance(); int workprocess = p_config->GetIntDefault("WorkerProcesses",1); ngx_start_worker_processes(workprocess);
sigemptyset(&set); for ( ;; ) {
sigsuspend(&set);
sleep(1);
} return; }
|
ngx_worker_process_init
原先屏蔽的十个信号一定要释放开,否则子进程将无法接收信号。
注意worker进程中不该使用sigsuspend来阻塞住,因为worker进程是用来干活的,阻塞住了程序无法正常运行了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| static void ngx_worker_process_init(int inum) { sigset_t set;
sigemptyset(&set); if (sigprocmask(SIG_SETMASK, &set, NULL) == -1) { ngx_log_error_core(NGX_LOG_ALERT,errno,"ngx_worker_process_init()中sigprocmask()失败!"); }
CConfig *p_config = CConfig::GetInstance(); int tmpthreadnums = p_config->GetIntDefault("ProcMsgRecvWorkThreadCount",5); if(g_threadpool.Create(tmpthreadnums) == false) { exit(-2); } sleep(1);
if(g_socket.Initialize_subproc() == false) { exit(-2); } g_socket.ngx_epoll_init(); return; }
|