linux系统编程之信号(一)
今天起,开始新的知识的学习,对于上个系列进程的学习还差一个理论上的总结,这个会下次补回来,以便通过实践之后,再用理论将其巩固一下,好了,话不多说,开始进入这个主题的学习----信号,很重要,但不是太容易理解,所以得一步一步来!
中断【纯概念,但是很重要】:
在学习信号之前,首先需要理解一下什么是中断,因为信号与中断有很多的相似之处,中断,顾名思义就是中途打断:
![](http://images0.cnblogs.com/blog/324374/201401/121003060353.png)
那什么是异步事件呢?它是没有一定时序关系,随机发生的事件,在中断技术出现之前,计算机对异步事件处理能力是有限的,通过是通过查询的方式来处理的,举一个现实生活中的例子:
比如张三正在看书,这时厨房里又正在烧开水,这时,张三看书时并不知道水是否烧开了,他就需要跑到厨房当中"查询"一下水是否烧开了,然后再回来看书,这时又不放心厨房水是否烧开了,于是又跑进厨房查询,如此循环,直到水烧开之后,他才能够静下心来看自己的书,这就是典型的查询技术。
有了中断技术后,又是如何的呢?还是以上面这个例子:
张三在看书的同时,设置一个闹钟,比如说是10分钟的闹钟,当水烧开的时候,会响铃,其中响铃可以看作是一个中断信号,闹钟可以看成是一个中断源,当闹钟响之后,张三就会去处理这一次的中断事件,也就是跑到厨房将煤气给关闭,将开水倒进热水瓶当中,这叫中断执行程序,实际上张三在做这件事之前,需要保护现场,记住当前看到了第几页,当执行完中断执行程序之后,则恢复现场,继续从之前看到的页数开始看书,这就是中断执行的整个流程,总结一下:
中断源 -> 中断屏蔽 -> 保护现场 -> 中断处理程序 -> 恢复现场
那中断处理程序是保存在哪的呢?实际上,它的入口地址是保存在中断向量表当中的,由于计算机中的中断个数是固定的,一般在操作系统启动的时候,会初始化一个中断向量表,它会保存固定个数的中断处理程序入口的地址,这样的话,CPU就可以根据中断号,从中断向量表当中找到对应中断的中断处理程序的入口地址,从而调用处理程序。
对于闹钟这个中断源,产生了一个中断信号,不同的中断源会产生不同的中断信号,再回到这个例子,张三在看书的时候,可能闹钟响的同时,会听到外面有人敲门的中断信号到来,还有可能是电话响起来产生另外一个中断信号,但是对于同时到来的中断,张三可以决定哪个中断先处理,这也就是中断的优先级,他觉得开水烧开的中断优化级最高。另外张三也有可能屏蔽一些不必要的中断,比如说电话响铃了,在看书之时,他觉得这个中断是可以屏蔽的,也就是中断屏蔽.
中断分类:
![](http://images0.cnblogs.com/blog/324374/201401/121033320048.png)
比如键盘产生的中断、鼠标产生的中断、打印机产生的中断等,这些都是属于硬件中断。
![](http://images0.cnblogs.com/blog/324374/201401/121033460821.png)
比如说除0中断、单步执行、对于X86平台来说执行了一个INT指令,从用户空间到内核空间
信号【核心】:
![](http://images0.cnblogs.com/blog/324374/201401/121038031136.png)
信号与中断总结:
![](http://images0.cnblogs.com/blog/324374/201401/121041201769.png)
![](http://images0.cnblogs.com/blog/324374/201401/121041548944.png)
信号名称:
那在linux/unix下,到底有哪些信号呢?我们可以通过一个命令进行查看:
![](http://images0.cnblogs.com/blog/324374/201401/121046537851.png)
这些信号都有它们不同的涵义:
![](http://images0.cnblogs.com/blog/324374/201401/121049549417.png)
![](http://images0.cnblogs.com/blog/324374/201401/121050187077.png)
对于这些信号的默认处理行为(也就是我们可以自定义自己的行为),可以在man手册上查询到:
![](http://images0.cnblogs.com/blog/324374/201401/121051225510.png)
![](http://images0.cnblogs.com/blog/324374/201401/121053077851.png)
进程对信号的三种响应:
当对于到来的信号,可以有三种不同的响应
![](http://images0.cnblogs.com/blog/324374/201401/121055444410.png)
为什么呢?SIGKILL是杀死进程的信号,它是9号进程:
![](http://images0.cnblogs.com/blog/324374/201401/121058250190.png)
在shell命令中,我们可以用kill -9 pid来对一个进程进行杀死,如果进程能够忽略9号信号的话,意味着当管理员向进程发送9号信号时,如果进程可以屏蔽不处理的话,那就无法杀死一个非法的进程了,而同理,SIGSTOP是停止一个进程信号,
![](http://images0.cnblogs.com/blog/324374/201401/121101104888.png)
![](http://images0.cnblogs.com/blog/324374/201401/121102050194.png)
![](http://images0.cnblogs.com/blog/324374/201401/121056296761.png)
也就是我们看到的信号对应的action,上面有介绍过:
![](http://images0.cnblogs.com/blog/324374/201401/121103308487.png)
signal:
下面来学一下安装信号函数:
![](http://images0.cnblogs.com/blog/324374/201401/132117525805.png)
下面以代码来进行说明:
![](http://images0.cnblogs.com/blog/324374/201401/132126589705.png)
编译运行:
![](http://images0.cnblogs.com/blog/324374/201401/132128292529.png)
按下ctrl+c:
![](http://images0.cnblogs.com/blog/324374/201401/132130258306.png)
![](http://images0.cnblogs.com/blog/324374/201401/132131174398.png)
可以信号是一种异步事件的响应,当响应完之后,会还原现场,又回到了for死循环代码上了:
![](http://images0.cnblogs.com/blog/324374/201401/132133472053.png)
这时按下ctrl+\退出:
![](http://images0.cnblogs.com/blog/324374/201401/132135131582.png)
实际上,ctrl+\会产生一个退出信号:
![](http://images0.cnblogs.com/blog/324374/201401/132135533612.png)
由于我们程序没有注册该信号,所以由系统默认处理,既程序结束。
对于signal函数,它的返回值为它注册的上一个信号的处理程序,这样说有点空洞,下面以程序来说明一下:
#include <unistd.h> #include <sys/stat.h> #include <sys/wait.h> #include <sys/types.h> #include <fcntl.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <signal.h> #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) void handler(int sig); int main(int argc, char *argv[]) { __sighandler_t oldhandler; oldhandler = signal(SIGINT, handler);//这时返回的处理程序是注册handler之前的,也就是系统默认的处理程序 if (oldhandler == SIG_ERR) ERR_EXIT("signal error"); while (getchar() != '\n')//死循环是为了测试,当按了ctrl+c之后,会不断死循环,直到按了回车键 ; if (signal(SIGINT, oldhandler) == SIG_ERR)//这时,再次注册信号,但是这次是注册成了默认处理程序,而ctrl+c的默认处理就是终止程序 ERR_EXIT("signal error"); for (;;) ; return 0; } void handler(int sig) { printf("recv a sig=%d\n", sig); }
编译运行:
按回车键:
这时,再按ctrl+c:
实际上,恢复默认的处理行为,还可以用它来代替:
编译运行,输出效果一样:
好了,今天的学习先到这,下回见!