参考博客:http://blog.csdn.net/alex_my/article/details/39494129
1. 信号概念
何为信号?
信号是一种软中断,可以由以下情形触发:
-1: 用户按下某些终断键,例如ctrl + C ,这可以停止一个失去控制的程序。(ctrl + D并不是发送信号,而是抛出一个
EOF 结束符,使程序退出。)
-2: 硬件异常,例如除数为0,无效的内存引用
-3:kill(2), kill(1) (接收信号的进程必须与发送信号进程的所有者相同,或者发送信号进程的uid为root)
-4: 当软件条件达成,且有进程需要得到此通知
注:信号是异步事件的经典实例。
当信号发生时,可以告诉内核进行以下处理:
-1:忽略信号(SIG_IGN): 有两个信号不能被忽略,SIGKILL(9), SIGSTOP(19), 不能被忽略的原因是这两个信号为内核和超级用户提供了一条可靠的方法去杀死和暂停进程。当忽略一些来自硬件的异常信号,产生的结果是未定义的,不如引用了无效的内存,除数为0。
-2:抓取信号: 在抓取信号之前,首先要定义抓取信号之后要如何处理,SIGKILL和SIGSTOP信号不能被捕捉。
-3:默认处理信号(SIG_DEF): 每一个信号都有一个默认的处理,大部分处理方式都是终止进程。如果想看各个的处理方式,可以在终端上输入man 7 signal,以下列出备查。红色标注为常用信号。
Signal Value Action Comment
------------------------------------------------------------------------------------------------------------------------------------------------
SIGHUP 1 Term Hangup detected on controlling terminal
or death of controlling process
SIGINT 2 Term Interrupt from keyboard
SIGQUIT 3 Core Quit from keyboard
SIGILL 4 Core Illegal Instruction
SIGABRT 6 Core Abort signal from abort(3)
SIGFPE 8 Core Floating point exception
SIGKILL 9 Term Kill signal
SIGSEGV 11 Core Invalid memory reference
SIGPIPE 13 Term Broken pipe: write to pipe with no
readers
SIGALRM 14 Term Timer signal from alarm(2)
SIGTERM 15 Term Termination signal
SIGUSR1 30,10,16 Term User-defined signal 1
SIGUSR2 31,12,17 Term User-defined signal 2
SIGCHLD 20,17,18 Ign Child stopped or terminated
SIGCONT 19,18,25 Cont Continue if stopped
SIGSTOP 17,19,23 Stop Stop process
SIGTSTP 18,20,24 Stop Stop typed at terminal
SIGTTIN 21,21,26 Stop Terminal input for background process
SIGTTOU 22,22,27 Stop Terminal output for background process
Next the signals not in the POSIX.1-1990 standard but described in SUSv2 and POSIX.1-2001.
SIGBUS 10,7,10 Core Bus error (bad memory access)
SIGPOLL Term Pollable event (Sys V).
Synonym for SIGIO
SIGPROF 27,27,29 Term Profiling timer expired
SIGSYS 12,31,12 Core Bad argument to routine (SVr4)
SIGTRAP 5 Core Trace/breakpoint trap
SIGURG 16,23,21 Ign Urgent condition on socket (4.2BSD)
SIGVTALRM 26,26,28 Term Virtual alarm clock (4.2BSD)
SIGXCPU 24,24,30 Core CPU time limit exceeded (4.2BSD)
SIGXFSZ 25,25,31 Core File size limit exceeded (4.2BSD)
Up to and including Linux 2.2, the default behavior for SIGSYS, SIGXCPU, SIGXFSZ, and (on architec-
tures other than SPARC and MIPS) SIGBUS was to terminate the process (without a core dump). (On some
other Unix systems the default action for SIGXCPU and SIGXFSZ is to terminate the process without a
core dump.) Linux 2.4 conforms to the POSIX.1-2001 requirements for these signals, terminating the
process with a core dump.
Next various other signals.
SIGIOT 6 Core IOT trap. A synonym for SIGABRT
SIGEMT 7,-,7 Term
SIGSTKFLT -,16,- Term Stack fault on coprocessor (unused)
SIGIO 23,29,22 Term I/O now possible (4.2BSD)
SIGCLD -,-,18 Ign A synonym for SIGCHLD
SIGPWR 29,30,19 Term Power failure (System V)
SIGINFO 29,-,- A synonym for SIGPWR
SIGLOST -,-,- Term File lock lost (unused)
SIGWINCH 28,28,20 Ign Window resize signal (4.3BSD, Sun)
SIGUNUSED -,31,- Core Synonymous with SIGSYS
2. signal 函数
#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int);
若出错,返回SIG_ERR
这个signal函数接受两个参数
参数1: 信号名,如SIGCHLD
参数2: 接受信号处理函数指针,函数原型void func(int),也可以是常量SIG_IGN或SIG_DEF
另外,还有三个定义:
#define SIG_ERR (void(*)())-1 // 返回错误值
#define SIG_DFL (void(*)())0 //恢复为参数1所指信号的处理方法为默认方法
#define SIG_IGN (void(*)())1 // 忽略参数1所指信号
可以作为参数2或者signal的返回值
程序用例:
<span style="font-size:14px;">#include "apue.h" static void sig_func(int signo) { printf(" signo: %d ", signo); exit(EXIT_SUCCESS); } int main(int argc, char** argv) { if(signal(SIGINT, sig_func) == SIG_ERR) { printf("signal failed. errno[%d] %s ", errno, strerror(errno)); exit(EXIT_FAILURE); } pause(); exit(EXIT_SUCCESS); }</span>
输出:
在键盘上按下ctrl + c,产生中断信号。
^C
signo: 2
注意点:
-1:当执行一个程序时,所有信号的状态都是系统默认或者忽略,且信号响应后执行默认动作,直到在程序中做出改变。如同上一程序用例当中,按下ctrl + c后,默认是终止进程,当在程序中做出改变之后,变成执行函数sig_func了。
-2:当使用fork后,子进程继承父进程的信号处理方式。
-3:当fork后又使用exec后,新的程序中的信号又恢复了默认,因为替换旧的数据空间,堆栈等,使得原来处理函数对于新的程序来说,已经失效
-4:对于一个正在执行任务的进程,如果异步捕获了一个信号,并且进入相应的信号处理函数,当它处理完这个函数并想要回到进程中时,可能会使进程“忘记”刚才做到哪里了,这就引入了可重入函数的概念。书中列出了异步信号安全的函数。
一个具体的例子:
假如主线程用malloc申请一个动态空间,并向其中写数据,这是异步获得一个SIG,并跳进SIG处理函数,这个处理函数可能也会向此动态空间写入数据,从而导致破坏了malloc的数据结构。
因此,要避免在信号处理程序中调用非可重入函数。
-5:不可靠的信号是指不具备阻塞信号能力的信号(早起Unix版本中存在的问题)
3.信号集:signal sets
#include <signal.h>
int sigemptyset(sigset_t* set);
int sigfillset(sigset_t* set);
int sigaddset(sigset_t* set, int signum);
int sigdelset(sigset_t* set, int signum);
return 0 on success and -1 on error
int sigismember(const sigset_t* set, int signum);
return 1 if is signum is a member of set, return 0 if not, -1 on error
以上函数可以从字面意思理解:
sigemptyset: (初始化)将set信号集清空
sigfillset : (初始化)将所有的信号填充到set中
sigaddset : 添加指定信号signum到set中
sigdelset : 从set中移除指定信号signum
sigismember: 特使signum是否是set中的成员
4. sigprocmask
#include <signal.h>
int pthread_sigmask(int how, const sigset_t* restrict set, sigset_t* restrict oset);
int sigprocmask(int how, const sigset_t* restrict set, sigset_t* restrict oset);
sigprocmask: 用于设定信号屏蔽集内信号的处理方式
参数how可选值如下:
SIG_BLOCK : set中包含了我们所希望添加的阻塞信号
SIG_UNBLOCK: set中包含了我们所希望解除阻塞的信号
SIG_SETMASK: set中为所设置屏蔽的信号
程序用例:
<span style="font-size:14px;">#include "apue.h" int main(int argc, char** argv) { sigset_t set; sigemptyset(&set); sigaddset(&set, SIGINT); if(sigismember(&set, SIGINT) == 1) printf("SIGINT is a member now "); else printf("SIGINT is not a member or error occur "); sigdelset(&set, SIGINT); if(sigismember(&set, SIGINT) == 0) printf("SIGINT is not a member now "); else printf("SIGINT is still a member or error occur "); sigaddset(&set, SIGINT); sigprocmask(SIG_SETMASK, &set, NULL); getchar(); // try ctrl + c return 0; }</span>
编译运行之后,可以ctrl + c,可以发现无效。
如果将sigprocmask(SIG_SETMASK, &set, NULL)注释,则可以了。
5. sigpending
#include <signal.h>
int sigpending(sigset_t* set);
return 0 if success and -1 on error.
sigpending: 通过set返回已经通知进程,但被阻塞而挂起的信号(未决信号)。比如阻塞了SIGINT信号一段时间,而在这段时间中产生了这个信号,通知进程,这个信号称之为pending信号。如果产生多个SIGINT信号,在Linux实现中,也只会处理一次。可以通过程序用例观察到。
程序用例:
<span style="font-size:14px;">#include "apue.h" static void sigfunc(int signo) { printf("signo: %d ", signo); } int main(int argc, char** argv) { sigset_t set, oset, pset; if(signal(SIGINT, sigfunc) == SIG_ERR) { printf("signal failed, error[%d]: %s ", errno, strerror(errno)); exit(EXIT_FAILURE); } sigemptyset(&set); sigaddset(&set, SIGINT); if(sigprocmask(SIG_BLOCK, &set, &oset) == -1) { printf("sigprocmask failed, error[%d]: %s ", errno, strerror(errno)); exit(EXIT_FAILURE); } //此时,阻塞掉信号集内的信号 // 在此期ctrl + c,可以多次输入,看会处理几次 sleep(5); //处理未决信号,把未决信号SIGINT放入pset信号集 if(sigpending(&pset) == -1) { printf("sigpending failed, error[%d]: %s ", errno, strerror(errno)); exit(EXIT_FAILURE); } if(sigismember(&pset, SIGINT) == 1) printf("SIGINT is pending "); else printf("SIGINT is not pending "); // 不再屏蔽,也可以用SIG_UNBLOCK if(sigprocmask(SIG_SETMASK, &oset, NULL) == -1) { printf("sigprocmask failed2, error[%d]: %s ", errno, strerror(errno)); exit(EXIT_FAILURE); } printf("sleep again "); // ctrl + c, 可以多次输入,看会处理几次 sleep(5); exit(EXIT_SUCCESS); }</span>
输出:
第一次sleep时,可以多次输入ctrl + c,5秒结束之后,会显示"SIGINT is pending",且函数sigfunc仅会被调用一次,说明多次输入,在当前的Linux实现中只会被调用一次。
第二次sleep期间,由于SIGINT不再被阻塞,因此输入ctrl + c后,会立即响应。
6. sigaction
#include <signal.h>
int sigaction(int sig, const struct sigaction* restrict act, struct sigaction* restrict oact);
sigaction: 指定或者修改与指定信号相关联的处理动作。可用sigaction实现signal
struct sigaction:{
void (*sa_handler )(int); /*addr of signal handler,
or SIG_IGN or SIG_DFL.*/
sigset_t sa_mask /*additional signals to block */
int sa_flags /* Special flags to affect behavior of signal. */
void(*sa_sigaction )(int, siginfo_t *, void *) Pointer to a signal-catching function.
sa_handler: 信号处理函数,参数为int,如同signal函数的参数意愿。
sa_sigaction: 信号处理函数,参数有3个。
至于选择sa_handler指向的函数还是sa_sigaction指向的函数,需要依靠sa_flags判断。
sa_flags: 指定信号处理行为,以下值可以使用或组合
SA_NOCLDSTOP: 父进程在子进程暂停或者继续运行时不会收到SIGCHLD信号
SA_ONSTACK : 如果设置此标志,且使用了sigaltstack设置了备用信号堆栈,则信号会传递给该堆栈中的进程,否则,在当前的进程中
SA_RESETHAND: 信号处理之后,重新设置为默认的处理方式
SA_RESTART : 被信号打断的系统调用自动重新发起
SA_SIGINFO : 使用sa_sigaction做为处理函数,而不是sa_handler
SA_NOCLDWAIT: 父进程在子进程中退出不会收到SIGCHLD信号,且子进程不会变成僵尸进程
SA_NODEFER : 使sa_mask设置的屏蔽无效
sa_mask: 在信号处理函数执行过程中,屏蔽哪些信号。注意的是,这个屏蔽仅在信号处理函数执行过程中有效,而不是整个进程。
程序用例:
#include "apue.h" static void sigfunc(int signo) { printf("signo: %d ", signo); sleep(10); printf("sleep over "); } static void sigfunc_int(int signo) { printf("SIGINT occur "); } int main(int argc, char** argv) { struct sigaction act; act.sa_flags = SA_NODEFER; sigemptyset(&act.sa_mask); act.sa_handler = sigfunc_int; sigaction(SIGINT, &act, NULL); act.sa_handler = sigfunc; sigaddset(&act.sa_mask, SIGINT); /*阻塞SIGINT信号,在输出时,如果我们先用ctrl+c产生SIGINT信号, 则立即处理该信号退出程序,但是如果程序先捕获到的是SIGQUIT信号, 则sigaddset处理会将SIGINT阻塞,先执行sigfunc()函数,sleep10秒后, 执行sigfunc_int函数,完成后退出。 */ if(sigaction(SIGQUIT, &act, NULL) == -1) { printf("sigaction failed"); exit(EXIT_FAILURE); } sleep(20); printf(" "); return 0; }