前言:
signal 是一种通信机制,可以跨进程发送,可以同进程跨线程发送,可以不同进程向指定线程发送。
信号的创建有两套api,一个是signal,一个是sigaction,signal缺陷很多,比如没有提供触发后自动再次设置处理信号处理策略,这会导致连续触发的两个信号,一个进入了期待的信号处理流程,另外一个则进入了默认的信号处理流程。
信号的递送和接受处理是异步的,即信号发送者不会因为信号接收者使用了阻塞信号处理函数而被阻塞住。但是信号的递送可能会出现阻塞,这个阻塞发生在信号发送者把信号送入内核的信号队列中(fixme,需要从代码层面验证)。
信号处理方式:
信号的处理有三种方式:默认,忽略,信号处理函数,可以在使用 sigaction 创建信号处理策略时指定。
默认分为如下几种:
Term Default action is to terminate the process.
Ign Default action is to ignore the signal.
Core Default action is to terminate the process and dump core (see core(5)).
Stop Default action is to stop the process.
Cont Default action is to continue the process if it is currently stopped.
不同信号的默认行为如下:
Signal Standard Action Comment ────────────────────────────────────────────────────────────────────────
SIGABRT P1990 Core Abort signal from abort(3)
SIGALRM P1990 Term Timer signal from alarm(2)
SIGBUS P2001 Core Bus error (bad memory access)
SIGCHLD P1990 Ign Child stopped or terminated
SIGCLD - Ign A synonym for SIGCHLD
SIGCONT P1990 Cont Continue if stopped
SIGEMT - Term Emulator trap
SIGFPE P1990 Core Floating-point exception
SIGHUP P1990 Term Hangup detected on controlling terminal or death of controlling process
SIGILL P1990 Core Illegal Instruction
SIGINFO - A synonym for SIGPWR
SIGINT P1990 Term Interrupt from keyboard
SIGIO - Term I/O now possible (4.2BSD)
SIGIOT - Core IOT trap. A synonym for SIGABRT
SIGKILL P1990 Term Kill signal
SIGLOST - Term File lock lost (unused)
SIGPIPE P1990 Term Broken pipe: write to pipe with no readers; see pipe(7)
SIGPOLL P2001 Term Pollable event (Sys V). Synonym for SIGIO
SIGPROF P2001 Term Profiling timer expired
SIGPWR - Term Power failure (System V)
SIGQUIT P1990 Core Quit from keyboard
SIGSEGV P1990 Core Invalid memory reference
SIGSTKFLT - Term Stack fault on coprocessor (unused)
SIGSTOP P1990 Stop Stop process
SIGTSTP P1990 Stop Stop typed at terminal
SIGSYS P2001 Core Bad system call (SVr4); see also seccomp(2)
SIGTERM P1990 Term Termination signal
SIGTRAP P2001 Core Trace/breakpoint trap
SIGTTIN P1990 Stop Terminal input for background process
SIGTTOU P1990 Stop Terminal output for background process
SIGUNUSED - Core Synonymous with SIGSYS
SIGURG P2001 Ign Urgent condition on socket (4.2BSD)
SIGUSR1 P1990 Term User-defined signal 1
SIGUSR2 P1990 Term User-defined signal 2
SIGVTALRM P2001 Term Virtual alarm clock (4.2BSD)
SIGXCPU P2001 Core CPU time limit exceeded (4.2BSD); see setrlimit(2)
SIGXFSZ P2001 Core File size limit exceeded (4.2BSD); see setrlimit(2)
SIGWINCH - Ign Window resize signal (4.3BSD, Sun)
通过pthread_kill进行线程间信号传递
信号可以时线程级别的,可以通过 pthread_kill 给同进程的其他线程发信号,可以通过 tgkill 给其他进程的指定线程发信号,通过 raise 可以给当前线程发信号。
Demo:
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
pthread_t newtid;
void usrHandler(int signum,siginfo_t *info,void *ucontext)
{
printf("[SIGUSR1 handler thread] tid -> %d , ready to send SIGUSR2 to child thread \n",gettid());
pthread_kill(newtid,SIGUSR2);
printf("[SIGUSR1 handler thread] tid -> %d , SIGUSR2 sent out \n",gettid());
}
void usrHandler2(int signum,siginfo_t *info,void *ucontext)
{
printf("[SIGUSR2 handler thread] tid -> %d , %d , SIGUSR2 received \n",gettid(),signum);
}
void* threadRoutine(void* arg)
{
pthread_t tid = *(pthread_t*)arg;
printf("[new thread] tid -> %d , arg -> %d \n",gettid(),tid);
// set SIGSUR2
sigset_t mask2;
sigemptyset(&mask2);
struct sigaction act2;
memset(&act2,0x0,sizeof(struct sigaction));
struct sigaction oldact2;
memset(&oldact2,0x0,sizeof(struct sigaction));
act2.sa_sigaction = usrHandler2;
act2.sa_mask = mask2;
act2.sa_flags = 0; //no flag is set
sigaction(SIGUSR2, &act2, &oldact2);
while(1) {;}
}
int main(int argc,char** argv)
{
printf("[main thread] tid -> %d \n",gettid());
// set SIGUSR1
sigset_t mask;
sigemptyset(&mask);
struct sigaction act;
memset(&act,0x0,sizeof(struct sigaction));
struct sigaction oldact;
memset(&oldact,0x0,sizeof(struct sigaction));
act.sa_sigaction = usrHandler;
act.sa_mask = mask;
act.sa_flags = 0; //no flag is set
sigaction(SIGUSR1, &act, &oldact);
pthread_t maintid = gettid();
pthread_create(&newtid,NULL,&threadRoutine,(void*)&maintid);
sleep(2);
while(1) {
raise(SIGUSR1);
sleep(2);
}
}
上面的例子中,主线程会循环给自己发信号 SIGUSR1 ,在信号处理函数中会给子线程发送 SIGUSR2。
当子线程通过pthread_kill给主线程发送信号时,会产生 SIGSEGV, 具体原因不明,如果有类似情况,可以参考如下:
ps:可以通过 pause 挂起当前线程,直到等到一个信号为止;可以通过 sigsuspend 挂起当前线程,直到等到某些信号为止。
通过mask来管理哪个线程会被信号中断,以用作信号处理函数的载体
每个线程都有自己的mask,可以通过pthread_sigmask来管理。
默认情况下,信号处理函数会随机打断一个线程的当前执行流程,转而执行信号处理函数,如果我们不希望某些重要的线程被打断,则需要通过 pthread_sigmask 在当前线程中屏蔽所有信号,那么这个线程就不会被打断了。
一个做法是,在进程的主线程一开始的时候就屏蔽所有线程,然后单独创建一个信号处理线程,在进入新型号处理线程的线程函数后,立刻通过 sigfillset 接收所有类型的信号,然后,通过 sig_wait 阻塞自己以等待信号的到来。至此,所有通过主线程创建的线程都将继承主线程的mask(屏蔽所有信号),且独立的信号处理函数将依次处理传递给当前进程的信号。
大部分情况下,我们会选择在重要的线程中屏蔽所有信号,而其他非重要线程可以用来作为信号处理函数的候选执行载体。
使用sigaction的mask参数来防止信号处理函数被信号中断
信号处理函数执行的过程中是否会被新的信号中断呢?确实是有可能的。sigaction就提供了避免这种情况发生的方法。与之相反,signal函数就不具备这个能力。
使用sigaction创建信号处理策略时指定mask。被列入mask集合中的signal会被阻塞,直到阻塞信号的动作结束,这些信号会被继续投递到信号处理逻辑中。
比如通过sigaction 指定 “当发生SIGUSR1的时候,阻塞所有SIGUSR2”,那么如果 SIGUSR1 的信号处理函数耗时较长,那么 SIGUSR2 会一直等到 SIGUSR1 的处理函数走完才会被递送给相应的进程/线程 以触发 默认动作/忽略动作/信号处理函数。
Demo:
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define USRSIG SIGUSR1
#define RTSIG SIGRTMIN+8
void usrHandler(int signum,siginfo_t *info,void *ucontext)
{
printf("%d , SIGUSR1 reveived \n",signum);
printf("Sender pid[%d] , User cost time [%ld] , System cost time [%ld] , si_code [%d] \n ",
info->si_pid,info->si_utime,info->si_stime,info->si_code);
sleep(15);
printf("exit usrHandler\n");
}
void usrHandler2(int signum,siginfo_t *info,void *ucontext)
{
printf("%d , SIGUSR2 received \n",signum);
}
int main(int argc,char** argv)
{
// set SIGUSR1
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask,SIGUSR2); // SIGUSR2 will be blocked when usrHandler is executing. After return from usrHandler, SIGUSR2 will
// be received adn surHandler2 will be executed.
struct sigaction act;
memset(&act,0x0,sizeof(struct sigaction));
struct sigaction oldact;
memset(&oldact,0x0,sizeof(struct sigaction));
act.sa_sigaction = usrHandler;
act.sa_mask = mask;
act.sa_flags = 0; //no flag is set
sigaction(SIGUSR1, &act, &oldact);
// set SIGSUR2
sigset_t mask2;
sigemptyset(&mask2);
struct sigaction act2;
memset(&act2,0x0,sizeof(struct sigaction));
struct sigaction oldact2;
memset(&oldact2,0x0,sizeof(struct sigaction));
act2.sa_sigaction = usrHandler2;
act2.sa_mask = mask2;
act2.sa_flags = 0; //no flag is set
sigaction(SIGUSR2, &act2, &oldact2);
while(1) {;}
}
当通过 kill 连续发送 SIGUSR1 和 SIGUSR2 给上面的例子时,会发现执行流程会卡在 SIGUSR1 处理函数的 sleep ,这是因为针对 SIGUSR1 设置了block SIGUSR2,这会导致 SIGUSR2 无法中断 SIGUSR1。
信号的同步
信号也提供同步机制,可以用来进行线程间/进程间同步,使用函数 sigwait / sigwaitinfo / sigtimedwait 可以让当前线程/进程一直等待到收到某个信号为止,pause 和 sigsuspend 也具有同样的功能。但是 sigsuspend 不是线程安全的,sigwait 是线程安全的。sigsuspend 会影响整个进程的 sigmask ,sigwait 不会影响。如果是线程级别的信号同步,建议使用 sigwait,如果是进程级别的线程同步,二者皆可。
线程级别同步Demo:
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <errno.h>
pthread_t newtid;
void usrHandler(int signum,siginfo_t *info,void *ucontext)
{
printf("[SIGUSR1 handler thread] tid -> %d , ready to send SIGUSR2 to child thread \n",gettid());
pthread_kill(newtid,SIGUSR2);
printf("[SIGUSR1 handler thread] tid -> %d , SIGUSR2 sent out \n",gettid());
}
void usrHandler2(int signum,siginfo_t *info,void *ucontext)
{
printf("[SIGUSR2 handler thread] tid -> %d , %d , SIGUSR2 received \n",gettid(),signum);
}
void* threadRoutine(void* arg)
{
pthread_t tid = *(pthread_t*)arg;
printf("[new thread] tid -> %d , arg -> %d \n",gettid(),tid);
// set SIGSUR2
sigset_t mask2;
sigemptyset(&mask2);
struct sigaction act2;
memset(&act2,0x0,sizeof(struct sigaction));
struct sigaction oldact2;
memset(&oldact2,0x0,sizeof(struct sigaction));
act2.sa_sigaction = usrHandler2;
act2.sa_mask = mask2;
act2.sa_flags = 0; //no flag is set
sigaction(SIGUSR2, &act2, &oldact2);
int retsig;
sigset_t waitmask;
sigemptyset(&waitmask);
sigaddset(&waitmask,SIGUSR2);
printf("[new thread] tid -> %d , start to wait for signal SIGUSR2 \n",gettid());
int ret = sigwait(&waitmask,&retsig);
printf("[new thread] tid -> %d , exit wait for SIGUSR2 %d | %d \n",retsig,errno);
}
int main(int argc,char** argv)
{
printf("[main thread] tid -> %d \n",gettid());
// set SIGUSR1
sigset_t mask;
sigemptyset(&mask);
struct sigaction act;
memset(&act,0x0,sizeof(struct sigaction));
struct sigaction oldact;
memset(&oldact,0x0,sizeof(struct sigaction));
act.sa_sigaction = usrHandler;
act.sa_mask = mask;
act.sa_flags = 0; //no flag is set
sigaction(SIGUSR1, &act, &oldact);
pthread_t maintid = gettid();
pthread_create(&newtid,NULL,&threadRoutine,(void*)&maintid);
sleep(2);
raise(SIGUSR1);
while(1) {
;
}
}
进程级别同步Demo:
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <errno.h>
pid_t childProcess;
void usrHandler(int signum,siginfo_t *info,void *ucontext)
{
printf("[SIGUSR1 handler] pid -> %d , ready to send SIGUSR2 to child process \n",getpid());
kill(childProcess,SIGUSR2);
printf("[SIGUSR1 handler] pid -> %d , SIGUSR2 sent out \n",getpid());
}
void usrHandler2(int signum,siginfo_t *info,void *ucontext)
{
printf("[SIGUSR2 handler] pid -> %d , %d , SIGUSR2 received \n",getpid(),signum);
}
void* processRoutine()
{
printf("[new process] pid -> %d \n",getpid());
// set SIGSUR2
sigset_t mask2;
sigemptyset(&mask2);
struct sigaction act2;
memset(&act2,0x0,sizeof(struct sigaction));
struct sigaction oldact2;
memset(&oldact2,0x0,sizeof(struct sigaction));
act2.sa_sigaction = usrHandler2;
act2.sa_mask = mask2;
act2.sa_flags = 0; //no flag is set
sigaction(SIGUSR2, &act2, &oldact2);
int retsig;
sigset_t waitmask;
sigemptyset(&waitmask);
sigaddset(&waitmask,SIGUSR2);
printf("[new preocess] pid -> %d , start to wait for signal SIGUSR2 \n",getpid());
int ret = sigwait(&waitmask,&retsig);
printf("[new preocess] pid -> %d , exit wait for SIGUSR2 %d | %d \n",retsig,errno);
}
int main(int argc,char** argv)
{
printf("[main process] pid -> %d \n",getpid());
// set SIGUSR1
sigset_t mask;
sigemptyset(&mask);
struct sigaction act;
memset(&act,0x0,sizeof(struct sigaction));
struct sigaction oldact;
memset(&oldact,0x0,sizeof(struct sigaction));
act.sa_sigaction = usrHandler;
act.sa_mask = mask;
act.sa_flags = 0; //no flag is set
sigaction(SIGUSR1, &act, &oldact);
int pid = fork();
if(pid==0){ //child
processRoutine();
}else{
childProcess = pid;
sleep(5);
raise(SIGUSR1);
while(1){;}
}
}
TODO : 需要一个demo来展示 sigwait 和 sigsuspend 对于 进程/线程 sigmask 的影响。