信号是内核和进程之间通信的一种方式,信号是由内核产生,并发送给一个或一组进程的短消息,用不同特定的数字表示不同的信号,信号的作用是表示某种事件的发生。
信号简介
分类
- 非实时不可靠信号,值为1-31
- 实时的可靠信号,值为32-63
信号由内核生成,信号生成和事件的发生密切相关,可将事件发生源分为以下三类:
信号事件发生源:
- 用户,如键入CTRL+C,终端驱动程序将通知内核产生信号发送到相应的进程
- 内核,内核执行过程中,遇到非法指令和浮点数溢出等情况
- 进程,一个进程调用kill函数向另一个进程发送信号,进行进程间通信
通常,LInux为每个信号定义了缺省的处理方式,但是用户可根据需要,对信号的处理方式进行重新定义。
信号的缺省处理方式包括
- A 结束进程
- B 忽略信号
- C 结束进程并写入内核文件
- D 停止进程
- E 信号不能被捕获
- F 信号不能被忽略
- G 非POSIX信号
###自定义信号处理函数
必须重新建立信号值和处理方式之间的对应关系,才能重新定义信号的处理方式。LInux提供signal和sigaction函数来实现信号的设置。signal和sigaction的区别在于signal不支持项信号处理函数传递数据。
注:SIGKILL和SIGSTOP不能被重定义或忽略。
signal函数原型:
// __sig为需设置的信号,__handler为新信号处理函数;失败返回SIG_ERR,否则成功。
//__handler为SIG_IGN忽略信号,SIG_DEL默认信号处理
extern __sighandler_t signal (int __sig, __sighandler_t __handler)
__THROW;
案例:使用signal重定义SIGINT处理函数
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main()
{
void f(int);
int i;
signal(SIGINT,f);
for(i=0;i<5;i++)
{
printf("hello\n");
sleep(1);
}
return 0;
}
void f(int signum)
{
printf("hello Linux\n");
}
sigaction函数原型
/*
signo需要处理的信号
act指向描述信号操作的结构
oact指向被替换操作的结构
成功返回0,否则返回-1
*/
int sigaction(int signo,const struct sigaction *act,struct sigaction *oact);
/*
sa_mask指定在信号处理过程中,何种信号被阻塞。缺省情况是当前信号被阻塞,以免信号处理函数被递归调用。
*/
struct sigaction
{
void(*sa_handler)(int); //信号处理函数
void(*sa_sigaction)(int,siginfo_t*,void *); //带参数的信号处理函数
sigset_t sa_mask; //信号掩码
int sa_flags; //设定信号处理相关行为
}
案例:sigaction定义信号SIGINT处理函数,并屏蔽其他信号
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int num=0;
void int_handle(int signum)
{
printf("SIGINT:%d]n",signum);
printf("int_handle called %d times\n",++num);
}
int main(void)
{
static struct sigaction act;
void int_handle(int);
act.sa_handler = int_handle;
sigfillset(&(act.sa_mask));
sigaction(SIGINT,&act,NULL);
while(1)
{
printf("i am sleepy..\n");
sleep(1);
if(num >=3)
{
return 0;
}
}
}
###信号集、信号屏蔽与阻塞
信号屏蔽就是临时阻塞信号被发送到某个进程,它包含一个被阻塞的信号集。当进程屏蔽某个信号时,内核将不发送该信号至屏蔽它的进程,直至该信号的屏蔽被解除。 信号集用于描述所有信号的集合。对于sigaction中的sa_mask字段,每一位对应一个信号,若某一位被设置为1,表示该位对应信号被屏蔽。
信号集定义及其操作函数
typedef struct
{
unsigned long sig[2];
}sigset_t
int sigemptyset(sigset_t *set); //清空信号集中所有信号
int sigfillset(sigset_t *set); //在set信号集中加入linux支持的所有信号
int sigaddset(sigset_t *set,int signum); //向信号集中加入signum信号
int sigdelset(sigset_t *set,int signum); //从信号集中删除signum信号
int sigismember(const sigset_t *set, int signum) //判断signum信号是否在信号集set中
每个进程定义一个信号掩码,该掩码对应一个信号集,该信号集中的所有信号在发送至进程后都将被阻塞。通过更改进程的信号掩码来阻塞或解除阻塞所选择的信号。以此来保护不希望由信号中断的临界代码。
信号阻塞函数sigprocmask
/*
how 如何修改信号掩码
SIG_BLOCK 添加信号到进程屏蔽
SIG_UNBLOCK将信号从进程屏蔽中删除
SIG_SETMASK将set的值设定为新的信号掩码
set 指向设置信号列表
oldset指向之前的信号掩码列表
*/
int sigprocmask(int how,const sigset_t *set,sigset_t *oldset);
案例:阻塞SIGINT信号3秒后恢复
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
int main(void)
{
sigset_t set;
int count = 3;
sigemptyset(&set);
sigaddset(&set,SIGINT);
sigprocmask(SIG_BLOCK,&set,NULL);
while(count)
{
printf("don't disturb me (%d)\n",count--);
sleep(1);
}
sigprocmask(SIG_UNBLOCK,&set,NULL);
printf("you did not disturb me!!\n");
return 0;
}