linux:信号的初识
一.信号是什么
在生活中,我们也可以能会见到各种各样的信号,比如你正在上课,突然铃声一响,那就意味着下课了,铃声就是一种信号,我们大家都约定好的,只要铃声一响那就代表下课了
在linux中,比如你正在执行一个程序,你突然按下Ctrl -c,程序就会结束执行,这是因为给操作系统发了一个2号信号SIGINT,此信号的作用是中止进程,CPU正在执行代码,收到此信号后就会结束进程,CPU具体处理信号的流程请往下看
二.都有哪些信号?
命令:
kill -l
//可以查看在操作系统中都有哪些信号
由图来看:共有62种信号,因为序号31和34之间少了32和33,所以一共62种信号 1-31是普通信号
此类信号是不可靠信号,不支持排队,所以可能会造成数据丢失
34-64是实时信号
此类信号是可靠信号,支持排队,故不会造成数据丢失
三.常见的信号
(1)2--->SIGINT--->终止进程(用于通知前台进程组终止进程)
Ctrl -c产生
(2)3--->SIGQUIT--->和SIGINT类似,进程终止,但是此信号会产生core文件
Ctrl -\产生
(3)6--->SIGABRT--->调用abort函数生成的信号
(4)8--->SIGFPE--->在发生致命的算术运算错误时发出,不仅包括浮点运算错
误,还包括溢出及除数为0等错误
(5) 9--->SIGKILL--->杀死进程(本信号不能被阻塞、处理和忽略)
(6)11--->SIGSEGV--->试图访问未分配给自己的内存或者试图往没有写权限
的内存地址写数据
ps:段错误就会发此信号
(7)13--->SIGPIPE--->管道读端关闭尝试写(管道破裂,此信号通常在进程
间通信产生)
(8)14--->SIGALRM--->时钟定时信号,计算的是实际的时间或时钟时间
alarm函数使用的就是此信号
(9)15--->SIGTERM--->程序结束信号,与SIGKILL不同的是该信号可以被阻塞
和处理,通常用来要求程序自己正常退出
ps: 此信号不会被立即处理,而是等进程继续运行之前才会处理,
默认处理动作是终止进程
(10)17--->SIGCHLD--->子进程结束,父进程会收到这个信号
(11)18---->SIGCONT---->让一个停止(stopped)的进程继续执行,本信号
不能被阻塞(即就是继续运行停止的进程)
(12)20--->SIGTSTP--->停止进程的运行,此信号不能被处理和忽略
Ctrl -z产生
(13)21---->SIGTTIN---->当后台作业要从用户终端读取数据时,那该作业中
的所有进程都会收到此信号,此信号会让进程停止执行
(14)29--->SIGIO--->文件描述符准备就绪,可以开始进行输入/输出操作
中止进程的信号:SIGINT/SIGQUIT
程序不可捕获、阻塞或忽略的信号:SIGKILL
默认会导致进程流产的信号:SIGABRT 、SIGFPE 、SIGQUIT
默认会导致进程退出的信号:SIGALRM 、SIGKILL 、SIGPIPE、 SIGTERM
默认会导致进程停止的信号:SIGTSTP、SIGTTIN
四.信号的产生方式
1.由用户终端按键产生
例如当一个正在运行的时候,用户可以输入Ctrl -c来停止进程的运行,Ctrl -z 是2号信号SIGINT,作用是停止进程的运行
2.硬件异常产生
此种信号是由硬件检测到并通知内核,然后内核向当前进程发送适当的信号
(1)当前进程执行了除以0的操作,CPU的运算单元会产生异常,内核会给此进程发送SIGFPE,即就是8号信号,该信号的作用是在发生致命的算术运算错误时产生,包括浮点运算错误、溢出、除数为0等错误
(2)MMU异常,当前进程访问了非法的内存(虚拟地址空间不存在或者是虚拟地址空间没有权限访问),MMU会产生异常,并把此异常报给给操作系统内核,操作系统会给此进程发送11号信号即就是SIGSEGV,该信号的作用就是当非法访问内存时,就会此信号
3.由调用系统函数时产生
4.由软件条件产生
第一种:由用户终端按键产生
SIGINT的默认处理动作是中止进程,SIGQUIT的默认处理动作也是中止进程并产生Core Dump文件,SIGINT由Ctrl -z产生,SIGQUIT由Ctrl -\产生
1.Core Dump是什么?
当一个进程要异常终止时,可以选择把进程的用户空间内存数据保存到磁盘上,文件名通常是Core Dump
Core Dump文件(核心转储文件)就是相当于一个文件在要死亡时的临终遗言,他会把自自己的遗言都封装在这个文件里,比如发生车祸时,我们需要来保护车祸现场,以防止以后的需要,那Core Dump文件相当于车祸现场,是用来保存在进程异常终止时,进程在用户空间中的内存数据
2.怎样产生Core Dump文件?
一个进程允许产生多大的Core Dump文件取决于进程的Resource Limit(这个信息保存在PCB中)
注意:是不允许产生core文件的,因为core文件中可能包含用户密码等敏感信息,不安全
3.相关命令
ulimit -c+文件大小
//umilit -c可以创建core文件并且指定此文件的大小ulimit -a
//查看是否成功创建core文件
执行一个死循环程序,然后按下Ctrl -\ ,来中止进程,并且产生core文件
代码:
#include<stdio.h>
int main()
{
while(1)
{
printf("pid is =%d\n",getpid());
sleep(1);
}
return 0;
}
结果:
第二种:通过系统调用系统函数产生
1.kill函数
功能:给指定的进程发送指定的信号
头文件:#include<signal.h>
int kill(pid_t pid,int signo);
参数说明:
pid--->进程ID,给那个进程发送信号
signo--->发送几号信号
返回值:成功返回0,失败返回-1
示例
创建了两个进程,通过kill()来中止指定的进程
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>
int count=0;
int main()
{
pid_t pid;
pid=fork();
if(pid<0)
{
perror("fork");
exit(1);
}
while(1){
if(pid==0){
//子进程
printf("I am child process.\n");
sleep(1);
}
else{
count++;
if(count>5)
kill(pid,SIGKILL);//循环6次后,中止子进程
printf("I am father process.\n");
sleep(1);
}
}
return 0;
}
运行结果:
2.raise函数
功能:给当前的进程发送指定的信号
头文件:#include<signal.h>
int raise(int signo);
返回值:成功返回0,失败返回-1
示例
(1)给当前进程发送9号信号,杀死进程
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
int main()
{
int count=0;
pid_t pid=getpid();
while(1)
{
if(count>10)
raise(SIGKILL);
printf("pid is %d\n",pid);
count++;
sleep(1);
}
return 0;
}
运行结果:
(2)给当前进程发送11号信号
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
int main()
{
int count=0;
pid_t pid=getpid();
while(1)
{
if(count>10)
raise(11);
printf("pid is %d\n",pid);
count++;
sleep(1);
}
return 0;
}
运行结果:
11号信号的作用是,如果非法访问内存,就会发此信号,即就是段错误时发送的信号
3.abort函数
功能:使当前进程接收到信号而异常终止
头文件:#include<signal.h>
int abort(void);
无返回值,可参考exit()
第三种:由硬件异常产生
(1)除数为0
#include<stdio.h>
#include<stdlib.h>
int main()
{
int a=0;
int b=20;
int c=b/a;
printf("c=%d\n",c);
return 0;
}
运行结果:
除数为0是致命的算术运算错误,操作系统会给此进程发送8号信号—>SIGFPE,此信号的作用是在发生致命的算术运算错误,比如除数为0、溢出、浮点运算错误,此时操作系统就会给进程发送8号信号
(2)非法访问内存
#include<stdio.h>
#include<stdlib.h>
int main()
{
int*p=NULL;
*p=100;
printf("%d\n",*p);
return 0;
}
运行结果:
非法访问内存,操作系统会给进程发送11号信号—>SIGSEGV,此信号的作用是当访问没有权限的虚拟地址空间或者虚拟地址空间不存在时,就会 产生此种信号
ps:解引用空指针的后果:
1.直接使程序结束;
2.进程会收到信号结束;
3.产生MMU异常
MMU发现错误的虚拟地址空间或者此虚拟地址空间没有权限访问,就会报告给操作系统内核,然后操作系统会给此进程发送11号信号
第四种:由软件条件产生
alarm()
功能:此函数可以设置一个闹钟,也就是告诉内核在seconds秒之后给进程发
送SIGALARM信号,该信号的默认处理动作是终止当前进程
返回值:0或者以前设定的闹钟时间剩下的秒数
unsigned int alarm(unsigned int seconds)
示例
使用alarm函数设置一个闹钟,闹钟的时间到了之后,就会结束进程
#include<stdio.h>
int main()
{
alarm(2);
while(1)
{
printf("hello\n");
sleep(1);
}
return 0;
}
运行结果:
结果解析:本来应该是死循环的输出hello,但使用alarm函数给进程设置了一个闹钟,时间到就会结束进程
五.信号的处理方式
1.忽略
2.执行该信号的默认处理操作
3.自定义,即就是提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行此函数,这种方式称为捕捉信号
示例:使用14号信号—>SIGALRM,该信号的作用是给进程设置一个闹钟,到了指定时间后,此信号的默认处理动作是终止进程
signal()函数是对指定的信号采取一些相应的处理方法 (1)忽略SIGALRM信号
#include<stdio.h>
#include<signal.h>
int main()
{
signal(SIGALRM,SIG_IGN);
alarm(1);//1秒钟后执行SIGALRM信号的处理方法
printf("1s时间已经结束\n");
while(1)
{
printf("hello\n");
usleep(100000);
}
return 0;
}
运行结果:
(2)对信号SIGALRM 的处理采取默认行为
#include<stdio.h>
#include<signal.h>
void Hander(int sig)
{
printf("hahahha\n");
}
int main()
{
signal(SIGALRM,SIG_DFL);
alarm(1);//1秒钟后执行SIGALRM信号的处理方法
while(1)
{
printf("hello\n");
usleep(100000);
}
return 0;
}
运行结果:
(3)对信号SIGALRM采取自定义的处理方法
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void Hander(int sig)
{
printf("hahahha\n");
exit(1);//终止进程
}
int main()
{
signal(SIGALRM,Hander);
alarm(1);//1秒钟后执行SIGALRM信号的处理方法
while(1)
{
printf("hello\n");
usleep(100000);
}
return 0;
}
运行结果: