本节讲述重点:
1.信号的基本概念:
2.一些常用的特殊信号的深入了解(用代码呈现)
3.了解核心转储Core Dump
4.信号的产生方式:(4种)(mykill代码)
1.信号的基本概念:
首先使用kill -l命令查看系统定义的信号列表:
由于没有32、33信号,所以信号共有62个。其中编号34以上为实时信号,我们先来讨论34以下的信号。
我们来看看signal(7),可以看到这些信号的产生条件及默认的处理动作。
那么现在问题来了,这些信号都是什么作用?是谁发给谁的呢?发送的方式又是什么呢?
答案很简单,想想我们在日常生活中也有很多信号,比如常见的红绿灯信号。所以linux中的信号也是类似的。它无非是想提供一个机制在需要的时候告诉某个进程该怎样做。是一种规定,便于系统操作。就像我们都知道”红灯停,绿灯行“一样。
信号的发送者有很多,比如终端驱动程序,进程,操作系统等。而接收者大多是一个进程。
发送的方式:即是修改目标进程PCB中对应的信号位图。只需要修改一个比特位(操作系统完成):收到信号就置1。
产生信号的方式概览:
- 用户在终端按某些键时,终端驱动程序会发送信号给前台进程。
- 硬件异常产生信号,这些条件又硬件检测到并通知内核,然后内核向当前进程发送适当的信号。
- 一个进程调用Kill(2)函数可以发送信号给另一个进程,
- 软件条件产生。
信号处理的方式:
- 忽略此信号
- 执行该信号的默认处理动作(大多数信号的默认处理动作都为终止进程)
- 提供一个信号处理函数,要求内核在处理该信号时切换到用用户态执行此处理函数,这种方式为捕捉信号。
在此介绍一个信号捕捉函数:
函数原型为:
typedef void(*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler);
在此声明:9号信号不能进行捕捉。
如下代码为捕捉2号信号。
#include<stdio.h>
2 #include<signal.h>
3 int handler(int signo)
4 {
5 printf("I am a sig: %d\n",signo);
6
7 }
8 int main()
9 {
10 signal(2,handler);
13 while(1);
14 return 0;
15 }
2.一些常用的特殊信号的深入了解
- 2号信号SIGINT(ctrl-c)。
此信号的默认动作为终止进程。但是ctrl-c只能发送至前台。下面用代码来呈现:
先写一个死循环,并用进程命令查看。观察输入Ctrl -C前后系统中的进程变。
1 #include<stdio.h>
2 #include<signal.h>
8 int main()
9 {
13 while(1);
14 return 0;
15 }
上面说到ctrl-c只能发给前台,那么什么是前台呢,什么又是后台呢?
以上边程序为例,我们在运行程序时在命令后边加一个”&“符号,及在后台进行,即为后台进程再用命令查看系统中的进程,就会变成这样。
会发现毫无反应,即可说明ctrl -c 只能给前台发信号。
对于前台进程与后台进程,我们需要知道以下几点:
1.用命令查看时,发现后台进程STAT状态栏是R,所以有+表示前台进程,无+表示后台进程。
可以看到./sig为后台进程。
2.Ctrl-C产生的信号只能发给前台进程。因为后台进程使Shell(Shell俗称壳(用来区别于核),是指“提供使用者使用界面”的软件(命令解析器)。它类似于DOS下的command.com和后来的cmd.exe。它接收用户命令,然后调用相应的应用程序。)不必等待进程结束就可以接受新的命令,启动新的进程。而前台进程运行时占用shell,它运行的时候SHELL不能接受其他命令。
3.前台进程在运行过程中用户随时可能按下Ctrl-C而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到SIGINT信号而终止,所以信号相对于进程的控制流程来说是异步的。
2.3号信号SIGQUIT:
- Ctrl \产生SIGQUIT信号,该信号的默认处理动作为终止进程并且Core Dump。
上面我们说过了2号信号,发现2号信号与3号信号的区别是:3号信号的默认动作是终止进程并且Core Dump.
下面来重点讲讲Core Dump.
3.Core Dump:
Core Dump的概念:当一个进程要异常终止时,可以选择把金成德用户空间内数据全部保存到磁盘上,文件名通常为Core,这叫做Core Dump。也称为核心转储。
进程异常终止通常是因为有 Bug,比如非法内存访问导致段错误,事后可以用调试器检查core文件以查清错误原因,这叫做 Post-mortem Debug(事后调试)。
首先用ulimit命令查看core文件:
那么我们要允许Core文件最大为1024K:则命令为ulimit -c 1024
编写一个野指针的代码:运行后会出现Core Dump
#include<stdio.h>
2 #include<signal.h>
8 int main()
9 {
11 int *p;
12 *p=0;
13 while(1);
14 return 0;
15 }
~
下面来用gdb进行调试。
4.信号的产生方式:(4种)(mykill代码)
(1).通过终端按键产生信号:上边的集中Ctrl-c与ctrl \均属于此种方式,不做详细解释。*
(2).硬件异常产生信号:
- 硬件异常产生信号是由硬件检测到并通知内核,然后内核向当前进程发送的信号。
例如当前进程执行了除以0的指令,CPU的运算单元会产生异常。内核将这个异常解释为SIGFPE信号发送给进程。
再⽐如当前进程访问了非法内存地址,MMU会产⽣生异常。内核将这个异常解释为SIGSEGV信号(11号)发送给进程:
如下代码:
1 #include<stdio.h>
2 #include<signal.h>
8 int main()
9 {
11 int *p;
12 *p=0;
13 while(1);
14 return 0;
15 }
运行之后会形成核心转储:
(3).调用系统函数像进程发信号
- kill命令:可以给任意进程发任意信号。
举例如下:给一个死循环程序,然后用kill命令给它发SIGSEGV(11)号信号。
1 #include<stdio.h>
2 #include<signal.h>
8 int main()
9 {
13 while(1);
14 return 0;
15 }
因为11号信号是以往遇到段错误都是由非法内存访问产生的。而此程序没有错误,给它发11号信号产生段错误。
下面来介绍系统函数:
1.kill函数:
kill命令就是调用kill函数实现的,kill函数可以给一个指定的进程发送指定的信号。
函数原型:
#include<signal.h>
int kill(pid_t pid,int signo);
返回值:成功返回0,错误返回-1.
#include<stdio.h>
2 #include<signal.h>
3 #include<sys/types.h>
4
5 int main(int argc,char*argv[])
6 {
16 if(argc!=3){
17 printf("Usage:%s signo pid\n",argv[0]);
18 return 1;
19 }
20 int signo=atoi(argv[1]);//字符串转成int
21 int pid=atoi(argv[2]);
22
23 kill(pid,signo);
24 return 0;
25 }
运行结果为:
2.raise函数:自己给自己发信号
函数原型:
#include<signal.h>
int raise(int signo);
返回值:成功返回0,错误返回-1.
#include<stdio.h>
2 #include<signal.h>
3 #include<sys/types.h>
4
5 int main(int argc,char*argv[])
6 {
7 int count=0;
8 while(1){
9 printf("hello %d\n",count++);
10 if(count>=1000){
11 raise(2);//当到1000时自己给自己发2号信号,终止进程
13 }
14 }
15 }
3.abort函数:使当前进程收到信号而异常中止。
函数原型:
#include<signal.h>
void abort(void);
像exit函数一样,函数总是会成功的,所以没有返回值。
#include<stdio.h>
2 #include<signal.h>
3 #include<sys/types.h>
4
5 int main(int argc,char*argv[])
6 {
7 int count=0;
8 while(1){
9 printf("hello %d\n",count++);
10 if(count>=1000){
11 abort();
13 }
14 }
15 }
4.由软件条件产生信号:
主要介绍alarm函数和SIGALRM信号。
alarm函数:
- 函数原型:
unsigned int alarm(unsigned int seconds)
在头文件unistd.h中。 - 作用: 调用alarm函数可以设定⼀个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终⽌当前进程。这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。
下面程序的作用是1秒钟之内不停地数数,1秒钟到了就被SIGALRM信号终止。
#include<stdio.h>
2 #include<signal.h>
3 #include<sys/types.h>
4
5 int main(int argc,char*argv[])
6 {
7 int count=0;
8 alarm(1);
9 while(1){
16 count++;
17 printf("count=%d\n",count);
18 }