信号的基本概念
信号是系统响应某个条件而产生的事件,进程接受到信号会执行相应的操作。(软中断信号,用来通知进程发生了异步事件)
信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达。
系统预先定义好的某些特定事件,信号可以被发送,也可以被接受,发送和接受的主题都是进程。
可靠信号以及不可靠信号
- 不可靠信号:信号值小于SIGRTMIN(32)的信号,主要存在的问题是:进程每次处理信号后,就将对信号的响应设置为默认动作,在某些情况下,将导致信号的错误处理,用户如果不希望这样操作,就将在信号处理函数的结尾再一次调用signal()重新安装该信号,(总的俩说就是进程可能对信号作出错误的反应,以及信号可能丢失)
- 可靠信号:在原有信号的机制上进行改进和扩充,这些信号支持派对,不会丢失,信号的发送和安装也出现了新函数(sigqueue和sigaction),早期的kill和signal依然被支持,信号值位于SIGRTMIN(32)和SIGRTMAX(63)之间的都称为可靠信号。
注意: 信号的可靠与不可靠只与信号值有关,与信号的发送和安装函数无关
signal和sigaction函数的主要区别就是:经过sigaction函数安装的信号都能传递信息给信号处理函数(对所有信号都成立),而经过signal函数安装的信号却不能向信号处理函数传递信息,对于信号发送函数也是一样的。
与信号有关的系统调用在“signal.h
”头文件中有声明
常见信号的值,及对应的功能说明:
在Linux系统下定义了一些信号:
存储位置为:
/usr/include/bits/signum.h
理解信号:通知进程产生了某事件
忽略:有两个信号不可忽略:SIGKILL和SIGSTOP,
默认:linux对每一个信号对规定了默认操作,对实时信号(后32)的默认反应是进程中止。
自定义:定义信号处理函数,信号发生时,执行响应的信号处理函数。
信号的使用
信号发送
1、kill函数
将参数sig指定的信号传递给参数pid指定的进程,pid有如下几种情况:
①pid > 0:将信号传给进程识别码为pid的进程;
②pid = 0:将信号传给和目前进程相同进程组的所有进程;
③pid = -1:将信号广播给系统内的所有进程;
④pid < 0:将信号传给进程组识别码为pid绝对值的所有进程;
#include<sys/types.h>
#include<signal.h>
int kill(pid_t pid,int sig)
执行成功返回0,执行失败返回-1;
2、alarm函数
用于设置信号传送闹钟,用来设置信号SIGALRM,在经过参数seconds指定的秒数后传送给目前的进程,若参数0,则之前设置的闹钟会被取消,并将剩下的时间返回。
#include<unistd.h>
unsigned int alarm(unsigned int seconds);
示例
int main()
{
int i;
signal(SIGALRM,handler);
alarm(5);
for(i = 1;i < 7;++i)
{
printf("sleep %d...\n",i);
sleep(1);
}
}
自定义信号处理方式
如果信号要处理某一个信号,首先就要在进程中安装该信号。安装信号主要用来确定要处理哪个信号以及当信号被传递给进程时,将执行何种操作。
①signal()
—— 不支持信号传递信息,主要用于前32中非实时信号的安装(有两个参数)
- 默认
SIG_DFL
- 忽略
SIG_IGN
- 自定义
void fun_sig(int sig)
我们通过帮助手册了解一下
- 第一个参数是一个信号代号
- 第二个参数是响应方式,是一个函数指针,返回值为void,参数为int
对于上面的程序,只有在按下ctrl+c后才会结束,否则将会一直执行下去,原因是给主程序发送了一个2号信号SIGINT
,即终端终端信号,程序收到该信号后自动退出。
接下来,我们改变以下对于该信号的响应方式,我们收到该信号后打印一下这个信号的代号:
代码如下:
注意
:这里的signal并没有执行,只是一个声明,只有当收到信号时才会调用
int sig就是信号的代号
此时我们按下Ctrl+c后发现是这样的:主程序并没有被打断,原因是:主程序是死循环是不会退出的,如果收到信号,当前程序被打断,内核帮助我们调用fun方法,再恢复执行while的函数体内容,因为我们已经改变了信号的响应方式,之前是按照默认方式响应的。
此时我们使用**Ctrl+\ **强制退出结束
下面展示对于该信号的三种操作方式的写法:
//默认
signal(SIGINT,SIG_DFL);
//忽略
signal(SIGINT,SIG_IGN);
//自定义
signal(SIGINT,sig_fun);
思考一下,如何使得第一次产生信号时是自定义,第二次又调用默认呢?
修改一下自定义函数即可:
于是:
②siigaction()
—— 支持信号传递信息,主要用于前32中非实时信号的安装(有三个参数)
函数原型
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
根据参数signum指定的信号编号来设置该信号的处理函数,如果参数oldact不是NULL,则原来的信号处理方式将通过结构sigaction返回。
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
}
- sa_handler是一个函数指针此参数和signal()的参数handler相同,代表新的信号处理函数
- sa_mask 用来设置在处理该信号时暂时将sa_mask 指定的信号集搁置
- sa_flags 用来设置信号处理的其他相关操作,下列的数值可用。
- SA_RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值SIG_DFL
- SA_RESTART:如果信号中断了进程的某个系统调用,则系统自动启动该系统调用
- SA_NODEFER :一般情况下, 当信号处理函数运行时,内核将阻塞该给定信号。但是如果设置了 SA_NODEFER标记, 那么在该信号处理函数运行时,内核将不会阻塞该信号
sa_sigaction 则是另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息。当 sa_flags 成员的值
包含了 SA_SIGINFO 标志时,系统将使用 sa_sigaction 函数作为信号处理函数,否则使用 sa_handler 作为信号处理
函数。在某些系统中,成员 sa_handler 与 sa_sigaction 被放在联合体中,因此使用时不要同时设置。
sa_mask 成员用来指定在信号处理函数执行期间需要被屏蔽的信号,特别是当某个信号被处理时,它自身会被
自动放入进程的信号掩码,因此在信号处理函数执行期间这个信号不会再度发生。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
int main()
{
struct sigaction newact,oldact;
/* 设置信号忽略 */
newact.sa_handler = SIG_IGN; //这个地方也可以是函数
sigemptyset(&newact.sa_mask);
newact.sa_flags = 0;
int count = 0;
pid_t pid = 0;
sigaction(SIGINT,&newact,&oldact);//原来的备份到oldact里面
pid = fork();
if(pid == 0)
{
while(1)
{
printf("I'm child gaga.......\n");
sleep(1);
}
return 0;
}
while(1)
{
if(count++ > 3)
{
sigaction(SIGINT,&oldact,NULL); //备份回来
printf("pid = %d\n",pid);
kill(pid,SIGKILL); //父进程发信号,来杀死子进程
}
printf("I am father .......... hahaha\n");
sleep(1);
}
return 0;
}
信号集操作
信号集被定义成一种数据类型:
typedef struct
{
unsigned long sig[_NSIG_WORDS];
}sigset_t
x信号集用来描述信号的集合,Linux所支持的所有信号可以全部或部分地出现在信号集中,主要与信号阻塞相关的函数配合使用,以下是一些信号集操作定义的函数:
①sigemptyset函数
用于初始化信号集
int sigemptyset(sigset_t *set); //将set所指信号集清0 成功:0;失败:-1
②sigfillset函数
用于将参数set初始化,将所有信号加入此信号集
int sigfillset(sigset_t *set);
③sigaddset函数
用于将参数signum代表的信号加入到参数set中
int sigaddset(sigset_t *set, int signum);
④sigdelset函数
用于从set信号集中删除signum信号
int sigdelset(sigset_t *set, int signum);
⑥sigismember函数
用于测试某个信号是否已加入信号集中
int sigismember(const sigset_t *set, int signum);
用信号处理僵尸进程
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <assert.h>
void fun(int sign)
{
wait(NULL);
}
int main()
{
signal(SIGCHILD,fun);//绑定信号与信号处理函数
pid_t t = fork();
assert(t!=-1);
if(t == 0)
{
printf("child start\n");
sleep(2);
printf("child over");
}
else
{
int i=0;
while(i<100)
{
sleep(1);
i++;
}
}
}
例:运行程序,第一次接收到SIGINT信号,进程打印HelloWorld,第二次接受SIGINT信号,进程结束
方法:
- 第一次接受信号:fun
- 第二次接受信号前,将信号的响应方式修改为默认SIG_DFL
- 第二次接收信号,默认退出程序
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void fun(int sign)
{
printf("HelloWorld\n");//第一次接收到信号
signal(SIGINT,SIG_DFL);//将信号响应方式恢复为默认,默认退出程序
}
int main()
{
signal(SIGINT, fun);//程序启动时,绑定
int i=0;
while(i<100)
{
sleep(1);
printf("process running...\n");
i++;
}
}