信号
信号的原理
对于 Linux来说,实际信号是软中断,可参考:点个外卖,我把「软中断」搞懂了,许多重要的程序都需要处理信号。
信号,为 Linux 提供了一种处理异步事件的方法。比如,终端用户输入了ctrl+c来中断程序,会通过信号机制停止一个程序。
信号概述
1、信号的名字和编号:
每个信号都有一个名字和编号,这些名字都以“SIG”开头,例如“SIGIO ”、“SIGCHLD”等等。
信号定义在signal.h
头文件中,信号名都定义为正整数。
具体的信号名称可以 使用kill -l
来查看信号 的名字以及序号,
信号是 从1开始编号的,不存在0号信号。kill对于信号0又特殊的应用。
2、信号的处理:
信号的处理有三种方法,分别是:忽略、捕捉和默认动作
忽略信号
大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是 SIGKILL和SIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景。
==SIG_IGN为系统自带的宏函数。==例:
signal(SIGINT,SIG_IGN);//将SIGINT信号(ctrl+C、2)忽略
捕捉信号
需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
系统默认动作
对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。
具体的信号默认动作可以使用man 7 signal来查看系统的具体定义。在此,我就不详细展开了,需要查看的,可以自行查看。也可以参考 《UNIX 环境高级编程(第三部)》的 P251——P256中间对于每个信号有详细的说明。
了解了信号的概述,那么,信号是如何来使用呢?
其实对于常用的 kill 命令就是一个发送信号的工具,kill 9 PID来杀死进程。比如,我在后台运行了一个 top 工具,通过 ps 命令可以查看他的 PID,通过 kill 9 来发送了一个终止进程的信号来结束了 top 进程。如果查看信号编号和名称,可以发现9对应的是 9) SIGKILL,正是杀死该进程的信号。而以下的执行过程实际也就是执行了9号信号的默认动作——杀死进程。
kill -9 进程PID
kill -SIGKILL 进程PID
这两句实现功能一样。
对于信号来说,最大的意义不是为了杀死信号,而是实现一些异步通讯的手段,那么如何来自定义信号的处理函数呢?
创建
常用API
信号处理函数的注册
入门版:函数signal
高级版:函数sigaction
信号处理发送函数
1.入门版:kill
2.高级版:sigqueue
在正式开始了解这两个函数之前,可以先来思考一下,处理中断都需要处理什么问题。
按照我们之前思路来看,可以发送的信号类型是多种多样的,每种信号的处理可能不一定相同,那么,我们肯定需要知道到底发生了什么信号。
另外,虽然我们知道了系统发出来的是哪种信号,但是还有一点也很重要,就是系统产生了一个信号,是由谁来响应?
如果系统通过 ctrl+c 产生了一个 SIGINT(中断信号),显然不是所有程序同时结束,那么,信号一定需要有一个接收者。对于处理信号的程序来说,接收者就是自己。
signal函数
signal 的函数原型
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
函数原型由两部分组成,一个是真实处理信号的函数,另一个是注册函数了。
注册函数:
sighandler_t signal(int signum, sighandler_t handler);
函数来说,
signum :信号的编号。
handler :中断函数的指针。
handler函数
真实处理信号的函数:
typedef void (*sighandler_t)(int);
中断函数的原型中,有一个参数是 int 类型,显然也是信号产生的类型,方便使用一个函数来处理多个信号。
我们先来看看简单一个信号注册的代码示例吧。
实例1:信号的处理:捕捉动作
domo1.c
信号处理函数的注册
#include<stdio.h>
#include <signal.h>
//真实处理信号的函数
void handler(int signum)
{
printf("get signum=%d\n",signum);
switch(signum){
case 2:
printf("SIGINT\n");
break;
case 10:
printf("SIGUSR1\n");
break;
}
}
int main()
{
signal(SIGINT,handler);//信号处理函数的注册
signal(SIGUSR1,handler);//信号处理函数的注册
while(1);
return 0;
}
domo2.c
demo2主要是将kill信号处理发送函数置于程序中,同时也可以使用system来实现kill。
信号处理发送函数:
#include<stdio.h>
#include <signal.h>
#include <sys/types.h>
int main(int argc,char **argv)
{
int signum=0;
int pid=0;
char cmd[128]={0};//设置system函数处理命令的大小
signum= atoi(argv[1]);//转为整型
pid= atoi(argv[2]);//转为整型
printf("num=%d\n",signum);
printf("pid=%d\n",pid);
kill(pid,signum);
// sprintf(cmd,"kill -%d %d",signum,pid);
// system(cmd);
printf("send signal ok\n");
return 0;
}
注:
1、atoi (表示 ascii to integer)是把字符串转换成整型数的一个函数。
2、sprintf指的是字符串格式化命令,
函数声明为 int sprintf(char *string, char *format [,argument,…]);,
主要功能是把格式化的数据写入某个字符串中,即发送格式化输出到 string 所指向的字符串。
3、
printf(cmd,"kill -%d %d",signum,pid);
system(cmd);
也可以实现kill(pid,signum);的功能
实验结果:
domo1实现:
domo2实现:
demo2主要是将kill信号处理发送函数置于程序中,同时也可以使用system来实现kill。
实例2:信号的处理:忽略
#include<stdio.h>
#include <signal.h>
void handler(int signum)
{
printf("get signum=%d\n",signum);
switch(signum){
case 2:
printf("SIGINT\n");
break;
case 10:
printf("SIGUSR1\n");
break;
}
}
int main()
{
signal(SIGINT,SIG_IGN);//将SIGINT信号(ctrl+C、2)忽略
signal(SIGUSR1,SIG_IGN);//将SIGUSR1信号(10)忽略
while(1);
return 0;
}
注:SIG_IGN:为系统自带的宏
实验结果:
会发现ctrl+c与kill -10 pid已经对进程不起作用了,这两个信号被进程忽略。