信号的基本概念及产生
一、信号的基本概念
一些小细节:
1、Ctrl-C产生的信号只能发给前台进程。一个命令后面加上一个&可以放到后台运行,这样shell不必等待进程结束就可以接受新的命令,启动新的进程。
2、shell可以运行一个前台程序和任意多个后台进程,只有前台进程才能接到想Ctrl -C这样控制键产生的信号。
3、前台进程在运行过程中用户随时可能按下Ctrl-C而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到SIGINT信号而终止,,所以信号相对于进程的控制流程来说是异步的。
我们可以使用kill -l命令可以观察看系统定义的信号列表
我们可以看到信号的编号是1~64,我们通常把1~31的信号称为普通信号,把32~64的信号称为实时信号。
信号常见的处理方式:
1、忽略此信号
2、执行该信号的默认处理动作
3、提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉(Catch)一个信号。
二、信号产生的方式:
1、通过键盘按键产生
用户在终端下按下某些键时,终端驱动程序会发送信号给前台进程,例如Ctrl-C产生SININT信号,Ctrl-\产生SIGQUIT信号,Ctrl-Z产生SIGTSTP信号;
2、调用系统函数向进程发信号
硬件异常产生信号,这些条件由硬件检测到并通知内核,然后内核向当前进程发送适当的信号,例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程,再比如当前进程访问了非法的内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。
例如:我们在执行死循环时:
- 3179是a.out进程的id,之所以出现这种现象:
-
是因为3179进程终止掉之前已经回到了shell提示符等待用户输入下一条命令,shell不希望segmentation fault信息和用户的输入交错在一起,所以等用户输入命令之后才显示。 - 我们可以写成kill - SIGSEGV 3179或者写成kill -11 3279,其中11是信号的编号,我们一般知道这个段错误是非法内存访问产生的。而这个程序本身没有错,将它发SIGSEGV也能产生段错误。
kill命令是调用kill函数来实现的,kill函数可以给指定的进程发信号,raise函数可以给当前进程发送指定的信号(自己给自己发送信号)。
这两个函数都成功返回0,错误返回-1
kill函数的实现
1 #include <stdio.h>
2 #include <signal.h>
3
4 int main(int argc,char *argv[])
5 {
6 if(argc !=3){
7 printf("usage: %s signo proc_id\n",argv[0]);
8 return 1;
9 }
10
11 kill(atoi(argv[2]),atoi(argv[1]));//argv[1]为pid
12 }
13
~
代码实现的结果:我们看到我们自己写的kill命令可以杀死进程。
abort函数使当前进程接收到信号而异常终止
像exit函数一样,abort函数总是会执行成功的,所以没有返回值。abort()函数会导致进程的异常终止除非SIGABRT信号被捕捉并且信号处理句柄没有返回。
下面我们来看一段代码:
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int main()
5 {
6 int i=0;
7 for(;i<100;i++){
8 printf("%d\n",i);
9 if(i==5)
10
11 abort();
12 }
13 return 0;
14 }
15
~
执行结果为
我们可以看到程序执行结束之后,遇到abort函数就异常终止了。abort()函数会导致进程的异常终止除非SIGABRT信号被捕捉并且信号处理句柄没有返回。
3、由软件条件产生的信号
SIGPIPE是一种由软件产生的信号,在“管道”中已经介绍过了,这次就认识alarm函数,和SIGALRM信号。
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号,该信号的默认处理动作是终止当前进程。
这个函数的给返回值时0或者是以前设定闹钟时间余下的秒数,打个比方来说,某人要小睡一觉,设定闹钟为30分钟之后响铃,20分钟后被人吵醒了一次,还想多睡一会,于是重新设定闹钟的值为15分钟之后响铃,以前设定的闹钟余下的时间还有10分钟,如果seconds值为0,表示取消以前设定的闹钟的值,函数返回值仍然是以前设定闹钟时间还余下的秒数(自己验证一下?)
1 #include <stdio.h>
2 #include <unistd.h>
3
4 int main()
5 {
6 int count=14;
7 alarm(1);
8 for(;1;count++)
9 {
10 printf("count=%d\n",count);
11 }
12 return 0;
13 }
1 #include <stdio.h>
2 #include <unistd.h>
3
4
5 int count=0;
6 void handler(int signo)
7 {
8 printf("get a signo: %d,count is: %d\n",signo,count);
9 exit(1);
10 }
11
12 int main()
13 {
14 signal(14,handler);
15 alarm(1);
16 while(、1)
17 {
18 count++;
19 // printf("haha: %d\n",count++);printf往显示器输出,是外设,访问外设是I/O计算功能受到拖累,当printf去掉后,发现执行计数的功能速度加强。
20 }
21 return 0;
22 }
最后我们也验证了CPU的计数速度远远大于外设的执行速度。
推荐阅读
-
信号的基本概念及产生
-
mysql-bin.000001文件是怎么产生的及处理方法
-
Java Scanner类用法及nextLine()产生的换行符问题实例分析
-
详解java中产生死锁的原因及如何避免
-
浅析点击对搜索引擎排名产生的影响及点击原理
-
INTEL主板CPURST#信号的产生及维修思路
-
[环境配置]Ubuntu 16.04 源码编译安装OpenCV-3.2.0+OpenCV_contrib-3.2.0及产生的问题
-
揭幕黑客软件:如何盗别人的微信号密码最简单方法及怎么盗取微信密码?
-
2345浏览器如何清除浏览网页后产生的垃圾文件及个人信息
-
Win8笔记本搜索不到周围WiFi信号怎么办 Win8电脑搜索不到WiFi的多种原因及解决方法