信号
Posted 别呀
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了信号相关的知识,希望对你有一定的参考价值。
文章目录
一、信号的概念
信号是UNIX系统响应某些状况而产生的事件,进程在接收到信号时会采取相应的行动。
信号是因为某些错误条件而产生的,比如内存段冲突、浮点处理器错误或者非法指令等。
它们由shell和终端管理器产生以引起中断。
进程可以生成信号、捕捉并响应信号或屏蔽信号。
二、信号名称
信号名称 | 描述 |
---|---|
SIGABORT | 进程停止运行 |
SIGALRM | 警告钟 |
SIGFPE | 浮点运算例外 |
SIGHUP | 系统挂断 |
SIGILL | 非法指令 |
SIGINT | 终端中断 |
SIGKILL | 停止进程(此信号不能被忽略或捕获) |
SIGPIPE | 向没有读者的管道 |
SIGSEGV | 无效内存段访问 |
SIGQUIT | 终端退出ctrl+\\ |
SIGTERM | 正常终止 |
SIGUSR1 | 用户定义信号1 |
SIGUSR2 | 用户定义信号2 |
SIGCHLD | 子进程已经停止或退出 |
SIGCONT | 如果被停止则继续执行 |
SIGSTOP | 停止执行 |
SIGTSTP | 终端停止信号 |
SIGTOUT | 后台进程请求进行写操作 |
SIGTTIN | 后台进程请求进行读操作 |
三、信号种类
可以从两个不同的分类角度对信号进行分类:(1)可靠性方面:可靠信号与不可靠信号;(2)与时间的关系上:实时信号与非实时信号。
3.1、不可靠信号
linux信号机制基本上是从unix系统中继承过来的。早期unix系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题,它的主要问题是:
①进程每次处理信号后,就将对信号的响应设置为默认动作。在某些情况下,将导致对信号的错误处理;因此,用户如果不希望这样的操作,那么就要在信号处理函数结尾再一次调用signal(),重新安装该信号。
②早期unix下的不可靠信号主要指的是进程可能对信号做出错误的反应以及信号可能丢失。
③linux支持不可靠信号,但是对不可靠信号机制做了改进:在调用完信号处理函数后,不必重新调用该信号的安装函数(信号安装函数是在可靠机制上的实现)。因此,linux下的不可靠信号问题主要指的是信号可能丢失。
3.2、可靠信号
随着时间的发展,实践证明了有必要对信号的原始机制加以改进和扩充。所以,后来出现的各种unix版本分别在这方面进行了研究,力图实现"可靠信 号"。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。
同时,信号的发送和安装也出现了新版本:信号发送函数sigqueue()
及信号安装函数sigaction()
。
3.3、实时信号
早期Unix系统只定义了32种信号,Ret hat7.2支持64种信号,编号0-63(SIGRTMIN=31,SIGRTMAX=63),将来可能进一步增加,这需要得到内核的支持。前32种信号已经有了预定义值,每个信号有了确定的用途及含义,并且每种信号都有各自的缺省动作。如按键盘的CTRL ^C时,会产生SIGINT信号,对该信号的默认反应就是进程终止。后32个信号表示实时信号,等同于前面阐述的可靠信号。这保证了发送的多个实时信号都被接收。实时信号是POSIX标准的一部分,可用于应用进程。
非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。
进程对信号的响应
进程可以通过三种方式来响应一个信号:(1) 忽略信号,即对信号不做任何处理,其中,有两个信号不能忽略:SIGKILL
及SIGSTOP
;(2)捕捉信号。定义信号处理函数,当信号发生时,执行相应的处理函数;(3)执行缺省操作,Linux对每种信号都规定了默认操作。注意,进程对实时信号的缺省反应都是进程终止。
Linux究竟采用上述三种方式的哪一个来响应信号,取决于传递给响应API函数的参数
四、信号发送
发送信号的主要函数有:kill()
、raise()
、sigqueue()
、alarm()
、setitimer()
以及abort()
4.1、kill函数
进程可以通过调用kill向包括它本身在内的另一个进程发送信号。如果程序没有发送该信号的权限,对kill
的调用就将失败。
#include <signal.h>
#include <sys/types.h>
int kill(pid_t pid, int sig);
参数:
pid: >0进程ID为pid的进程 =0同一个进程组的进程 <0且!=-1进程ID为-pid的所有进程 =-1除发送进程自身外,所有进程ID大于1的进程
sig: 信号值 为0时(即空信号)实际不发送任何信号但照常进行错误检查
kill
函数的作用是把参数sig给定的信号发送给标识号为pid的进程。
要想发送一个信号,发送者进程必须拥有相应的权限。这通常意味着两个进程必须拥有同样的用户ID。
4.2、raise函数
#include <signal.h>
int raise(int signo)
向进程本身发送信号,参数为即将发送的信号值。调用成功返回0;否则,返回-1.
4.3、sigqueue函数
#include <signal.h>
#include <sys/types.h>
int sigqueue(pid_t pid,int sig,const union sigval val)
参数:
pid:接受信号的进程ID
sig:即将发送的信号
val:联合数据结构union sigval,指定信号传递的参数(4个人字节)
union sigval联合数据结构:
typedef union sigval{
int sival_int;
void *sival_ptr;
}
调用成功返回0,;否则,返回-1
sigqueue()
是比较新的发送信号系统调用,主要是针对实时信号提出的,支持信号带有参数,与函数sigaction()
配合使用。
sigqueue()
比kill()
传递了更多的附加信息,但sigqueue()
只能向一个进程发送信号,而不能发送信号给一个进程组。如果signo=0
,将会执行错误检验,但实际上不发送任何信号,0 值信号可用于检查pid
的有效性以及当前进程是否有权权限向目标进程发送信号。
在调用sigqueue
时,sigval _t
指定的信息会拷贝到 3 参数信号处理函数(3参数信号处理函数指的是信号处理函数由sigaction
安装,并设定了sa_ sigaction
指针,稍后将阐述)的siginfo _t
结构中,这样信号处理函数就可以处理这些信息了。由于sigqueue
系统调用支持发送带参数信号,所以比kill系统调用的功能要灵活和强大得多。
4.4、alarm函数
#include <signal.h>
unsigned int alarm(unsigned int seconds)
专门为SIGALRM
信号而设,在指定的时间seconds
秒后,将向进程本身发送SIGALRM
信号,又称为闹钟时间。进程调用alarm
后,任何以前的alarm()
调用都将无效。如果参数seconds
为零。那么进程内将不再包含任何闹钟时间。
返回值,如果调用alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间否则返回 0。
4.5、setitimer函数
#include <signal.h>
int setitimer(int which,const struct itimerval*value.struct itimerval*ovalue)
参数:
which: 指定定时器类型
value: 结构体itimerval的一个实例
ovalue: 可不做处理
itimerval结构体:
struct itimerval{
struct timerval it_interval;/*next value*/
struct timerval it_value;/*current value*/
}
struct timerval{
long tv_sec;/*seconds*/
long tv_usec;/*microseconds*/
}
settimer()
比alarm()
功能强大,支持 3 种类型定时器(参数which的值):
ITIMER_REAL
: 设定绝对时间;经过指定时间后,内核将发送SIGALEM
信号给本进程ITIMER_VIRTUAL
:设定程序执行时间;经过指定时间后,内核将发送SIGVTALRM
信号给本进程。ITIMER_PROF
: 设定进程执行以及内核因本进程而消耗的时间和,经过指定时间后,内核将发送ITIMER_VIRTUAL
信号给本进程。
4.6、abort函数
#include <signal.h>
void abort(void)
向进程发送SIGABORT
信号,默认情况下进程会出现异常退出,当然可定义自己的信号处理函数。即使SIGABORT
被进程设置为阻塞信号,调用abort()后,SIGABORT
仍然被进程接受。该函数无返回值。
五、信号的安装
如果进程要处理某一信号, 那么就要在进程中安装该信号。安装信号主要用来确定信号值及进程针对该信号值的动作之间的映射关系,即进程将要处理哪个信号:该信号被传递给进程时,将执行何种操作。
linux主要有两个函数实现信号的安装: signal()
、 sigaction()
。 其中signal()
在可靠信号系统调用的基础上实现,是库函数。它只有两个参数,不支持信号传递信息,主要是用于前32种非实时信号的安装;而sigaction()
是较新的函数(由两个系统调用实现: sys._signal
以及sys._rt_sigaction
),有三个参数,支持信号传递信息,主要用来与sigqueue()
系统调用配合使用,当然,sigaction()
同样支持非实时信号的安装。sigaction()
优 于signal()
主要体现在支持信号带有参数。
5.1、signal函数
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int sig, sighandler_t handler);
参数:
sig: 指定的信号的值
handler: 针对前面信号值的处理,可以忽略信号(参数设为SIG_IGN);可以采用系统默认方式处理信号(参数设为SIG_DFL);也可以自己实现处理方式(参数指定一个函数地址)
返回值:
成功,返回最后一次为安装信号signum而调用signal()时的handler值;失败,返回SIG_ERR
示例:
(通过Ctrl-C组合键发出中断信号)
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int ctrl_c_count=0;
/* 定义一个函数指针,用来保存signal调用的返回值 */
void(* old_handler)(int);
void ctrl_c(int);
int main()
{
int c;
old_handler=signal(SIGINT,ctrl_c);
while ((c=getchar())!='\\n');
signal(SIGINT,old_handler);
for(;;);
return 0;
}
void ctrl_c(int signum)
{
/* 信号处理完毕,信号的默认处理方式被还原,所以要重新关联 */
signal(SIGINT,ctrl_c);
++ctrl_c_count;
printf("ctrl-c count = %d\\n",ctrl_c_count);
}
5.2、sigaction函数
sigaction函数用于改变进程接受信号后的行为。
#include <signal.h>
int sigaction(int signum,const struct sigaction*act,struct sigaction*oldact)
参数:
signum: 信号的值,可以除SIGKILL及SIGSTOP外的任何一个特定有效的信号(这两个信号定义自己的处理函数,将导致安装错误)
act: 指向结构sigaction的一个实例的指针,在结构sigaction中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理
oldact: 指向的对象用来保存原来对相应信号的处理,可指定oldact为NULL。
如果吧第二、三个参数都设为NULL,那么函数可用于检查信号的有效性
sigaction结构定义:
struct sigaction{
union{
__sighandler_t_sa_handler;
void (*_sa_sigaction)(int,struct siginfo*,void*);
}_u
sigset_t sa_mask;
unsigned long sa_flags;
void (*sa_restorer)(void);
}
其中,sa_ restorer,已过时,POSIX不支持它,不应再被使用。
1、联合数据结构中的两个元素_sa_handler
以及 *_sa_siaction
指定信号关联函数即用户指定的信号处理函数。除了可以是用户自定义的处理函数外,还可以为SIG_DFL
(采用缺省的处理方式),也可以为SIG_IGN
(忽略信号)。
2、由_sa_handler
指定的处理函数只有一一个参数,即信号值,所以信号不能传递除信号值之外的任何信息;由_sa_sigaction
是指定的信号处理函数带有三个参数,是为实时信号而设的(当然同样支持非实时信号),它指定一个 3参数信号处理函数。第一个参数为信号值,第三个参数没有使用( posix没有规范使用该参数的标准),第二个参数是指向siginfo_t
结构的指针, 结构中包含信号携带的数据值。
六、信号集
信号集用来描述信号的集合,linux所支持的所有信号可以全部或部分的出现在信号集中,主要与信号阻塞相关函数配合使用。
6.1、信号集操作函数
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);
6.2、与阻塞相关的函数
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset); //读取或更改进程的信号屏蔽字。
int sigpending(sigset_t *set); //读取当前进程的未决信号集,通过set参数传出。
int sigsuspend(const sigset_t*mask) //用于在接受到某个信号之前,临时用mask替换进程的信号掩码,并暂停进程执行,直到收到信号为止
示例:
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
void printsigset(sigset_t* set)
{
int i;
for (i=1;i<64;i++)
{
if (sigismember(set,i))
putchar('1');
else
putchar('0');
}
puts("");
}
void handler(int sig)
{
if (sig==SIGQUIT)
{
sigset_t s;
sigemptyset(&s);
sigaddset(&s,SIGINT);
sigprocmask(SIG_UNBLOCK,&s,NULL);
}
if (sig==SIGINT)
{
printf("recv a signal %d\\n",sig);
}
}
int main(void)
{
sigset_t s;
sigset_t p;
signal(SIGINT,handler);
signal(SIGQUIT,handler);
sigemptyset(&s);
sigaddset(&s,SIGINT);
sigprocmask(SIG_BLOCK,&s,NULL);
for (;;)
{
sigpending(&p);
printsigset(&p);
sleep(1);
}
}
以上是关于信号的主要内容,如果未能解决你的问题,请参考以下文章
Android MediaPlayer AudioStream AudioFlinger 服务器死机!,致命信号 11