信号是软件中断,很多比较重要的应用程序都需要处理信号。并且信号提供了一种处理异步事件的方法。如终端用户键入中断键,会通过信号机制停止一个程序,或及早终止管道中的下一个程序
很多条件都可以产生信号,比如用户键入某些终端键,CTRL+C或者delete,进程调用kill(2)函数可将任意信号发送给另一个进程或进程组, 硬件检测到异常产生的信号,软件某种条件导致的异常
使用kill -l就可以看有多少种信号类型
kill -L
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
从上面的结果会发现一个规律,前32种信号会有各种不同的名称,后32种会以“SIGRTMIN”或者“SIGRTMAX”开头,前者是从unix继承下来的信号,称为不可靠信号(也称为非实时信号),后者为了解决“不可靠信号”的问题进行了更改和扩充的信号形成了可靠信号(也称为实时信号)
如果想要了解可靠与不可靠信号,需要了解信号的生命周期:
一个完整的信号周期可以分为三个重要阶段,三个重要阶段有四个重要事件刻画的:信号产生,信号在进程中注册,信号在进程中注销,执行信号处理函数。
相邻的两个事件的时间间隔构成了生命周期的一个阶段,这里的信号处理有多种方式,一般由内核完成,也可以由用户进程完成
可靠信号与不可靠信号的区别:
不可靠信号如果发现信号已经在进程中注册,就会忽略该信号,因此若前一个信号还没有注销又产生了新的信号就是导致信号丢失
可靠信号发送给一个进程时,不管该信号是否已经在进程中注册,都会被再注册一次,因此信号不会丢失,所有可靠信号都支持排队,所有不可靠信号都不支持排队。
这里信号的产生,注册,注销等是指信号的内部的实现机制,而不是调用信号的函数实现,所以信号注册与否,与后面讲到的发送信号函数(kill等)以及信号安装函数(signal()等)无关只与信号值有关
下表是这些信号的说明:
Signal |
Description |
SIGABRT |
由调用abort函数产生,进程非正常退出 |
SIGALRM |
用alarm函数设置的timer超时或setitimer函数设置的interval timer超时 |
SIGBUS |
某种特定的硬件异常,通常由内存访问引起 |
SIGCANCEL |
由Solaris Thread Library内部使用,通常不会使用 |
SIGCHLD |
进程Terminate或Stop的时候,SIGCHLD会发送给它的父进程。缺省情况下该Signal会被忽略 |
SIGCONT |
当被stop的进程恢复运行的时候,自动发送 |
SIGEMT |
和实现相关的硬件异常 |
SIGFPE |
数学相关的异常,如被0除,浮点溢出,等等 |
SIGFREEZE |
Solaris专用,Hiberate或者Suspended时候发送 |
SIGHUP |
发送给具有Terminal的Controlling Process,当terminal被disconnect时候发送 |
SIGILL |
非法指令异常 |
SIGINFO |
BSD signal。由Status Key产生,通常是CTRL+T。发送给所有Foreground Group的进程 |
SIGINT |
由Interrupt Key产生,通常是CTRL+C或者DELETE。发送给所有ForeGround Group的进程 |
SIGIO |
异步IO事件 |
SIGIOT |
实现相关的硬件异常,一般对应SIGABRT |
SIGKILL |
无法处理和忽略。中止某个进程 |
SIGLWP |
由Solaris Thread Libray内部使用 |
SIGPIPE |
在reader中止之后写Pipe的时候发送 |
SIGPOLL |
当某个事件发送给Pollable Device的时候发送 |
SIGPROF |
Setitimer指定的Profiling Interval Timer所产生 |
SIGPWR |
和系统相关。和UPS相关。 |
SIGQUIT |
输入Quit Key的时候(CTRL+)发送给所有Foreground Group的进程 |
SIGSEGV |
非法内存访问 |
SIGSTKFLT |
Linux专用,数学协处理器的栈异常 |
SIGSTOP |
中止进程。无法处理和忽略。 |
SIGSYS |
非法系统调用 |
SIGTERM |
请求中止进程,kill命令缺省发送 |
SIGTHAW |
Solaris专用,从Suspend恢复时候发送 |
SIGTRAP |
实现相关的硬件异常。一般是调试异常 |
SIGTSTP |
Suspend Key,一般是Ctrl+Z。发送给所有Foreground Group的进程 |
SIGTTIN |
当Background Group的进程尝试读取Terminal的时候发送 |
SIGTTOU |
当Background Group的进程尝试写Terminal的时候发送 |
SIGURG |
当out-of-band data接收的时候可能发送 |
SIGUSR1 |
用户自定义signal 1 |
SIGUSR2 |
用户自定义signal 2 |
SIGVTALRM |
setitimer函数设置的Virtual Interval Timer超时的时候 |
SIGWAITING |
Solaris Thread Library内部实现专用 |
SIGWINCH |
当Terminal的窗口大小改变的时候,发送给Foreground Group的所有进程 |
SIGXCPU |
当CPU时间限制超时的时候 |
SIGXFSZ |
进程超过文件大小限制 |
SIGXRES |
Solaris专用,进程超过资源限制的时候发送 |
在某个信号出现时,可以告诉内核按下列3种方式进行处理
1 忽略信号:除了SIGKILL和SIGSTOP。其他信号都可以忽略。这两种信号不能被忽略的原因是它们向内核和超级用户提供了使进程终止或停止的可靠方法。
2 捕捉信号:通知内核在某种信号发生的时候,调用一个用户函数。比如前面的进程函数中,如果捕捉到SIGCHLD信号,则表示一个子进程已经终止,所以此信号的捕捉函数可以调用waitpid以取得该子进程的进程ID以及它的终止状态
3 执行系统默认动作。对于大多数信号的系统默认动作就是终止该进程。
下面来看下信号的具体用法,一般有三种方式进行操作:
1 signal(SIGINT,SIG_ING); SIGINT是信号,SIG_ING代表忽略SIGINT信号
下面是一个死循环的函数,按下CTRL+C是没有反应的,只有按下CTRL+来结束
void signal_func(){
signal(SIGINT,SIG_IGN);
for(;;);
}
2 signal(SIGINT,SIG_DFL) SIG_DFL表示接到此信号后的动作是系统默认动作
void signal_func(){
signal(SIGINT,SIG_DFL);
for(;;);
}
3 void ( *signal( int sig, void (* handler)( int )))( int );
这是一个函数指针, handler所指向的函数是一个不带任何参数, 并且返回值为int的一个函数.
signal是一个函数, 它返回一个函数指针, 后者所指向的函数接受一个整型参数 且没有返回值, 仔细看, 是不是siganal( int signo, void (*handler)(int) )的第2个参数了,对了,其实他所返回的就是 signal的第2个信号处理函数,指向信号处理函数,就可以执行函数了( signal内部时, signal把信号做为参数传递给handler信号处理函数,接着 signal函数返回指针, 并且又指向信号处理函数, 就开始执行它)
使用方法如下:
typedef void (*signal_handler)(int);
void signal_handler_fun(int signal_num){
printf("Catch the signal %d ",signal_num);
}
void signal_func(){
signal_handler p_signal=signal_handler_fun;
signal(SIGINT,p_signal);
for(;;);
}
当检测到CTRL+C的时候,则会跳转去执行signal_handler_fun函数。
发送信号的函数主要有kill(),raise(),alarm(),pause()
1)kill()和raise()
kill()函数和熟知的kill系统命令一样,可以发送信号给信号和进程组(实际上kill系统命令只是kill函数的一个用户接口),需要注意的是他不仅可以终止进程(发送SIGKILL信号),也可以向进程发送其他信号。
与kill函数不同的是raise()函数允许进程向自身发送信号。
#include<signal.h>
#include<sys/types.h>
int kill(pid_t pid,int sig)
pid >0 将该信号发送给进程ID为PID的进程
pid == 0 将该信号发送给与发送进程属于同一进程组的所有进程。
pid < 0 信号发送给进程组号为-pid的进程
pid == -1 信号发给所有的进程表中的进程
raise函数原型:
#include<signal.h>
#include<sys/types.h>
int raise(int sig)
下面的例子使子进程不在父进程调用kill之前不退出,然后父进程调用kill使子进程退出:
void signal_func(){
pid_t pid;
int ret;
pid=fork();
if(pid == 0){
printf("child(pid:%d) is waiting for any signal ",getpid());
raise(SIGSTOP);
exit(0);
}
else{
sleep(2);
if((waitpid(pid,NULL,WNOHANG)) == 0){
if((ret=kill(pid,SIGKILL)) == 0){
printf("parent kill %d ",pid);
}
}
// waitpid(pid,NULL,0);
exit(0);
}
}
运行结果: