目录
一、信号的概念:
信号的概念信号是Linux进程间通信的最古老的方式。信号是软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式。信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。“中断"在我们生活中经常遇到,譬如,我正在房间里打游戏,突然送快递的来了,把正在玩游戏的我给“中断"了,我去签收快递(处理中断),处理完成后,再继续玩我的游戏。这里我们学习的“信号"就是属于这么一种“中断”。我们在终端上敲“Ctrl+c",就产生一个“中断",相当于产生一个信号,接着就会处理这么一个“中断任务”(默认的处理方式为中断当前进程)。
二、信号的特点
特点、不能携带大量信息,满足某个特设条件才发送。
信号可以直接进行用户空间进程和内核空间
一个完整的信号周期包括三个部分:
信号的产生
信号在进程中的注册
信号在进程中的注销
执行信号处理函数
三、信号的编号
不存在编号为0的信号。其中1-31号信号称之为常规信号(也叫普通信号或标准信号),34-64称之为实时信号,驱动编程与硬件相关。名字上区别不大。而前32个名字各不相同。
四、信号的四要素
1)编号
2)名称
3)事件
4)默认处理动作
man 7 signal
五、信号的发起方式
1)用户按下某些按键
2)硬件异常,如除数为0,无效内存访问。
3)软件异常,如定时器
4)调用系统函数kill raise abort
5)运行Kill
六、未决信号集和信号阻塞集(PCB中)
未决信号集:信号发生,但是未被处理的信号集合
信号阻塞集:加入信号阻塞集的信号,不被处理。
七、信号的API
1、kill函数
头文件:
#include <sys/types.h>
#include <signal.h>
函数原型:
int kill(nidt pid, int sig);
功能:
给指定进程发送指定信号(不一定杀死)
参数:
pid :取值有4种情况:
pid > 0:将信号传送给进程ID为pid的进程。
pid = 0 :将信号传送给当前进程所在进程组中的所有进程。
pid = -1 ︰将信号传送给系统内所有的进程。
pid < -1∶将信号传给指定进程组的所有进程.这进程组号等于 pid 的绝对值。
sig :信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令kill - l("l”为字母)进行相应查看,不推荐直接使用数字,应使用宏名,因为不同操作信号编号可能不同,但是名称是一致的。
返回值:
成功返回0.
失败返回-1
2、raise函数
头文件:
#include <signal.h>
函数原型:
int raise(int sig)
功能:
给当前进程发送指定信号(自己给自己发)==kill(getpid(),sig)
参数:
sig:信号编号
返回值:
成功返回0.失败返回非0
3、abort函数
头文件:
#include <stdlib.h>
函数原型:
void abort(void)
功能:
给自己发送终止信号SIGABRT并且产生core文件,==kill(getpid(),SIGABRT)
参数:
无
返回值:
无
4、alarm函数(闹钟)
头文件:
#include <unistd.h>
函数原型:
unsigned int alarm (unsigned int seconds)
功能:
设置定时器,在指定seconds后,内核会给当前进程发送14(SIGALRM)信号。每个进程只有唯一的一个定时器,因为到了时间会自杀。取消定时器alarm(0)
参数:
秒数
返回值:
返回0或者剩余的秒数。
5、setitimer函数
头文件:
#include<sys/time.h>
函数原型:
int setitimer (int which,const struct itimerval *new_value,struct itimerval* old_value)
功能:
设置定时器(闹钟),可代替alarm函数,精度微秒,可以实现周期定时。
参数:
which :指定定时方式
1)自然定时:ITIMER_REAL 14 (SIGALRM) 计算自然时间
2)虚拟空间定时:ITIMER_VIRTUAL 26(SIGVTALRM)只计算进程占用cpu时间
3)运行时间计时(用户加内核) ITIMER_PROF 27 (SIGPROF)计算占用cpu以及执行系统调用的时间。
new_value: struct itimerval ,负责指定timeout时间。
八、给信号注册自定义函数
一个进程收到信号:执行默认信号、忽略信号、执行自定义动作
注意:9、19号不能更改信号的处理方式,因为它们向用户提供了一种使进程终止的可靠方法。
1、signal函数
头文件:
#include<signal.h>
函数原型:
typedef void(*sighandler_t)(int)
sighandler_t signal(int signum,sighander_t handler)
功能:
注册信号处理函数,不能用于SIGKILL SIGSTOP信号,即确定收到信号后处理函数的入口地址。此函数不会阻塞。
参数:
sigum:信号的编号。也可是宏定义
handler:取值有三种情况:
SIG_IGN:忽略该信号
SIG_DFL:执行系统默认动作
处理信号函数名:如自定义函数名
回调函数定有如下:
void func(int signo)
{
//signo 为触发的信号,为signal()第一个参数的值。
}
返回值:
成功:第一次返回 NULL,下一次返回此信号上一次注册的信号处理函数的地址。如果需要使用此返回值,必须在前面先声明此函数指针的类型。
失败:返回SIG_ERR
案例:
void sign_handler(int sig)
{
if(sig==SIGUSR1)
{
printf("捕捉到SIGUSR1信号\n");
}
else
{
printf("没有捕捉到信号\n");
}
}
int main(int argc, char const *argv[])
{
signal(SIGUSR1,sign_handler);
raise(SIGUSR1);
return 0;
}
运行结果:
九、信号集
1、信号集的概述
在PCB中有两个非常重要的信号集。一个称之为“阻塞信号集”,另一个称之为“未决信号集”。这两个信号集都是内核使用位图机制来实现的。但操作系统不允许我们直接对其进行位操作,而需自定义另外的集合.借助信号集操作函数来对PCB中的这两个信号集进行修改。
2、自定义信号集函数
sigset_t set,set 就是一个信号集
#include <signal.h>
int sigemptyset(sigset_t *set);//将set集合置空
int sigfillset(sigset_t *set);//将所有信号加入set集合
int sigaddset(sigset_t *set, int signo);//将signo信号加入到set集合
int sigdelset(sigset_t *set,int signo);//从set集合中移除signo信号
int sigismember(const sigset_t *set,int signo);//判断信号是否存在
案例:
#include <stdio.h> #include <signal.h> int main(int argc, char const *argv[]) { //定义一个信号集合 sigset_t set; //清空信号集合 sigemptyset(&set); //将SIGINT加入到信号集合 sigaddset(&set,SIGINT); //判断SIGINT是否在信号集合中 if((sigismember(&set,SIGINT))) { printf("SIGINT在set信号集合中\n"); } return 0; }
3、信号阻塞集合
信号阻塞集也称信号屏蔽集、信号掩码。每个进程都有一个阻塞集,创建子进程时子进程将继承父进程的阻塞集。信号阻塞集用来描述哪些信号递送到该进程的时候被阻塞(在信号发生时记住它,直到进程准备好时再将信号通知进程)。所谓阻塞并不是禁止传送信号,而是暂缓信号的传送。若将被阻塞的信号从信号阻塞集中删除,且对应的信号在被阻塞时发生了,进程将会收到相应的信号。我们可以通过sigprocmaskJ修改当前的信号掩码来改变信号的阻塞情况。
1)sigprocmask函数
头文件:
#include<signal.h>
函数原型:
int sigprocmask(int how,const sigset_t *set,sigset_t *oldset)
功能:
检查或修改信号阻塞集,根据how指定的方法来对进程的阻塞信号集进行修改。新的信号阻塞集由set指定,旧的由oldset保存。
参数:
how :信号阻塞集的修改方法:
有三种情况
SIG_BLOCK:向信号阻集合中添加set信号集,新的信号掩码是set和旧信号掩码的并集。mask=mask|set
SIG_UNBLOCK:向信号阻塞集合中删除set信号集,从当前信号掩码中去除掉set中的信号。mask=mash&~set
SIG_SETMASK:将信号阻塞集合设为set信号集,相当于原来信号阻塞集的内容清空,然后按照set中的信号重新设置信号阻塞集。相当于mask=set
set:要操作的集合地址。如果set为NULL,则不改变信号阻塞集合,函数只把当前信号阻塞集合保存到oldset中
oldset:保存原先信号阻塞集地址。
返回值:
成功:0
失败:-1
案例:加入SIGINT信号进阻塞集
#include <stdio.h>
#include <signal.h>
int main(int argc, char const *argv[])
{
//定义一个信号集合
sigset_t set;
//清空信号集合
sigemptyset(&set);
//将SIGINT加入到信号集合
sigaddset(&set,SIGINT);
sigprocmask(SIG_BLOCK,&set,NULL);
//printf("五秒后将SIGINT从set阻塞集中删除\n");
//sleep(5);
//sigprocmask(SIG_UNBLOCK,&set,NULL);
getchar();
return 0;
}
运行:
分析:因为被加入阻塞信号集,所以使用ctrl+c,信号不会被传递,会变为未决信号
如果将代码中注释部分解除注释,按下Ctrl+c之后,过五秒自动会传递信号。
4、未决信号集
sigpending函数:
头文件:
#include<signal.h>
函数原型:
int sigpending(sigset_t *set)
功能:
读取当前进程的未决信号集
参数:
set:未决信号集
返回值:
成功0,失败-1
案例:
#include <stdio.h>
#include <signal.h>
int main(int argc, char const *argv[])
{
//定义一个信号集合
sigset_t set;
//清空信号集合
sigemptyset(&set);
//将SIGINT加入到信号集合
sigaddset(&set,SIGINT);
sigprocmask(SIG_BLOCK,&set,NULL);
printf("五秒后将SIGINT从set阻塞集中删除\n");
sleep(5);
sigset_t set2;
sigemptyset(&set2);
sigpending(&set2);
if(sigismember(&set2,SIGINT))
{
printf("SIGINT在信号未决集中\n");
}
sigprocmask(SIG_UNBLOCK,&set,NULL);
getchar();
return 0;
}
分析:在没有将SIGINT在阻塞集中删除掉之前按下ctrl+c,因为是被阻塞了,所以会打印,ctrl+c 在未决信号集中,过五秒后SIGINT从阻塞集中删除掉,就会自动响应。