signals
1 signal concepts
信号是一种软中断,可以由以下情形触发:
-1: 用户按下某些终端键,例如ctrl + D
-2: 硬件异常,例如除数为0,无效的内存引用
-3:kill(2), kill(1)
-4: 当软件条件达成,且有进程需要得到此通知
当信号发生时,可以告诉内核进行以下处理:
-1:忽略信号;有两个信号不能被忽略,SIGKILL, SIGSTOP, 不能被忽略的原因是这两个信号为内核和超级用户提供了一条可靠的方法去杀死和暂停进程。当忽略一些来自硬件的异常信号,产生的结果是未定义的,不如引用了无效的内存,除数为0。
-2:抓取信号;在抓取信号之前,首先要定义抓取信号之后要如何处理,SIGKILL和SIGSTOP信号不能被捕捉。关于抓取信号处理的可以参考以下前面章节第五节--程序用例3,链接如下:
http://blog.csdn.net/alex_my/article/details/39345931
-3:默认处理信号;每一个信号都有一个默认的处理,大部分处理方式都是终止进程。如果想看各个的处理方式,可以在终端上输入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
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)
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 function
#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);
这个signal函数接受两个参数
参数1: 信号编码,也就是第一节中的Signal, 比如SIGCHLD
参数2: 接受信号处理函数指针,函数原型void func(int)
另外,还有三个定义:
#define SIG_ERR (void(*)())-1 // 返回错误值
#define SIG_DFL (void(*)())0 // 忽略参数1所指信号
#define SIG_IGN (void(*)())1 // 恢复为参数1所指信号的处理方法为默认方法
可以作为参数2或者signal的返回值
程序用例:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
static void sig_func(int signo)
{
printf("\nsigno: %d\n", signo);
exit(EXIT_SUCCESS);
}
int main(int argc, char** argv)
{
if(signal(SIGINT, sig_func) == SIG_ERR)
{
printf("signal failed. errno[%d]\t %s\n", errno, strerror(errno));
exit(EXIT_FAILURE);
}
pause();
exit(EXIT_SUCCESS);
}
输出:
在键盘上同时按下ctrl + c,产生中断信号。
^C
signo: 2
3
-1:当执行一个程序时,所有信号的状态都是系统默认或者忽略,且信号相应后执行默认动作,直到在程序中做出改变。如同上一节程序用例当中,按下ctrl + c后,默认是终止进程,当在程序中做出改变之后,变成执行函数sig_func了。
-2:当使用fork后,子进程继承父进程的信号处理方式。
-3:当fork后又使用exec后,新的程序中的信号又恢复了默认,因为替换旧的数据空间,堆栈等,使得原来处理函数对于新的程序来说,已经失效。
4 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中的成员
程序用例在下一节
5 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中为所设置屏蔽的信号
程序用例:
#include <stdio.h>
#include <stdlib.h>
#include <signal.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\n");
else
printf("SIGINT is not a member or error occur\n");
sigdelset(&set, SIGINT);
if(sigismember(&set, SIGINT) == 0)
printf("SIGINT is not a member now\n");
else
printf("SIGINT is still a member or error occur\n");
sigaddset(&set, SIGINT);
sigprocmask(SIG_SETMASK, &set, NULL);
getchar();
// try ctrl + c
return 0;
}
编译运行之后,可以ctrl + c,可以发现无效。
如果将sigprocmask(SIG_SETMASK, &set, NULL)注释,则可以了。
6 sigpending
#include <signal.h>
int sigpending(sigset_t* set);
return 0 if success and -1 on error.
sigpending: 通过set返回已经通知进程,但被阻塞而挂起的信号。比如阻塞了SIGINT信号一段时间,而在这段时间中产生了这个信号,通知进程,这个信号称之为pending信号。如果产生多个SIGINT信号,在Linux实现中,也只会处理一次。可以通过程序用例观察到。
程序用例:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
static void sigfunc(int signo)
{
printf("signo: %d\n", signo);
}
int main(int argc, char** argv)
{
sigset_t set, oset, pset;
if(signal(SIGINT, sigfunc) == SIG_ERR)
{
printf("signal failed, error[%d]: %s\n", errno, strerror(errno));
exit(EXIT_FAILURE);
}
sigemptyset(&set);
sigaddset(&set, SIGINT);
if(sigprocmask(SIG_BLOCK, &set, &oset) == -1)
{
printf("sigprocmask failed, error[%d]: %s\n", errno, strerror(errno));
exit(EXIT_FAILURE);
}
// 在此期ctrl + c,可以多次输入,看会处理几次
sleep(5);
if(sigpending(&pset) == -1)
{
printf("sigpending failed, error[%d]: %s\n", errno, strerror(errno));
exit(EXIT_FAILURE);
}
if(sigismember(&pset, SIGINT) == 1)
printf("SIGINT is pending\n");
else
printf("SIGINT is not pending\n");
// 不再屏蔽
if(sigprocmask(SIG_SETMASK, &oset, NULL) == -1)
{
printf("sigprocmask failed2, error[%d]: %s \n", errno, strerror(errno));
exit(EXIT_FAILURE);
}
printf("sleep again\n");
// ctrl + c, 可以多次输入,看会处理几次
sleep(5);
exit(EXIT_SUCCESS);
}
输出:
第一次sleep时,可以多次输入ctrl + c,5秒结束之后,会显示"SIGINT is pending",且函数sigfunc仅会被调用一次,说明多次输入,在当前的Linux实现中只会被调用一次。
第二次sleep期间,由于SIGINT不再被阻塞,因此输入ctrl + c后,会立即响应。
7 sigaction
int sigaction(int sig, const struct sigaction* restrict act, struct sigaction* restrict oact);
sigaction: 指定或者修改与指定信号相关联的处理动作
struct sigaction:
void(*)(int) sa_handler Pointer to a signal-catching
function or one of the macros
SIG_IGN or SIG_DFL.
sigset_t sa_mask Additional set of signals to
be blocked during execution of
signal-catching function.
int sa_flags Special flags to affect behav‐
ior of signal.
void(*)(int, siginfo_t *, void *) sa_sigaction 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_RESART : 被信号打断的系统调用自动重新发起
SA_SIGINTO : 使用sa_sigaction做为处理函数,而不是sa_handler
SA_NOCLDWAIT: 父进程在子进程中退出不会收到SIGCHLD信号,且子进程不会变成僵尸进程
SA_NODEFER : 使sa_mask设置的屏蔽无效
sa_mask: 在信号处理函数执行过程中,屏蔽哪些信号。注意的是,这个屏蔽仅在信号处理函数执行过程中有效,而不是整个进程。
程序用例:
// test8.9.cc
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
static void sigfunc(int signo)
{
printf("signo: %d\n", signo);
sleep(10);
printf("sleep over\n");
}
static void sigfunc_int(int signo)
{
printf("SIGINT occur\n");
}
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);
if(sigaction(SIGQUIT, &act, NULL) == -1)
{
printf("sigaction failed");
exit(EXIT_FAILURE);
}
sleep(20);
printf("\n");
return 0;
}
关于sa_mask和SA_NODEFER还是有些疑问,留着。