一、信号机制
正如我们标题所说,信号就是进程间的对话,A进程想要告诉B进程一些事。比如子进程在结束之前就可以给父进程发这样一条“短信”:“嘿,我要结束了,为了避免让我成为僵死进程,快来读取我的信息吧!”这个时候父进程收到了子进程的短信,于是停下手里的工作,花少量时间处理好子进程,再继续进行自己的工作,这样,父进程再也不用傻傻的等子进程结束之后再执行,同样,多个子进程也可以通过这样的“发短信”的方式,告知父进程。这样就做到了异步处理僵死进程,使得子进程和父进程不再串行进行,父进程也不用再默默的等待子进程结束啦,从而节省了大量的时间。而“短信”,就是信号。这里我们要研究的就是,进程是怎样收发信号的,发送的信号到底是怎样内容?
其实信号的实质,就是由系统预先定义好的某些特定的事件。在《unix环境高级编程》里有写,信号其实就是一些定义在头文件里的int型正整数。
所以我们刚刚所说的子进程给父进程发信号,并不像我写的那么废话连篇,就只是一个简单的、冷冰冰的、机械的、准确的整型数字。信号可以接受,可以发送,但是都是指在进程之间进行。
总结起来就是:
在Linux中,信号是进程间通讯的一种方式,它采用的是异步机制。当信号发送到某个进程中时,操作系统会中断该进程的正常流程,并进入相应的信号处理函数执行操作,完成后再回到中断的地方继续执行。
需要说明的是,信号只是用于通知进程发生了某个事件,除了信号本身的信息之外,并不具备传递用户数据的功能。
很多条件下都会产生信号:
·当用户按下某些终端按键时,引发终端产生信号。在终端上按Ctrl+C键则产生中断信号SIGINT。这是停止一个已失去控制的程序的方法。
·引荐一场产生信号:除数为0、无效的内存引用等等,这些条件通常由引荐检测到,并将其通知内核。然后内核为该条件发生正在运行的进程产生适当的信号。例如,对执行一个无效内存引用的进程产生SIGSEGV信号。
·进程调用kill函数可将信号发送给另一个进程或进化成呢个组。自然面对此有所限制:接收信号进程和发送信号进程的所有者必须相同,或者发送信号进程的所有者必须时超级用户。
·用户课用kill命令将信号发送给其他进程。此命令只是kill函数的接口。常用此命令终止哟个时空的后台进程。
·当检测到某种软件条件已经发生,并应将其通知有关进程也产生信号。这里值得不是硬件产生打得条件(如初一0),而是软件条件。例如SIGGURG(在网络连接山川来带外数据时长生)、SIGPIPE(在管道的读进程已终止后,一个进程写此管道时产生)、,以及SIGALRM(进程所设置的闹钟始终超时时产生)。
信号是异步处理事件的经典实例。产生信号的时间对进程而言是随机出现的。进程不能简单地测试一个变量来判断是否出现了一个信号,而是必须告诉内核“在次信号出现时,请执行下列操作”。
但是内核在某个信号出现时也可以按照下列三种方式之一进行处理,我们称之为信号的处理与信号的相关动作。
1、忽略此信号。大多数信号都可以使用这种中方式进行处理,但有两种信号却绝不能贝忽略。他们是SIGKILL和SIGSTOP。
2、捕捉信号。要做到这一点,需要通知内核在魔偶中信号发生时调用一个用户函数。再次函数中,可以执行用户希望堆这种事件进行的处理。
3、执行系统默认操作。针对大多数信号的系统默认动作是终止进程。
二、signal函数
void (*signal(int signo, void (*func)(int))) (int);
返回值:成功,返回以前的信号处理函数;失败,返回SIG_ERR
说明:
signo为信号名。
func为常量值SIG_IGN(忽略此信号)/SIG_DFL(执行系统默认动作)/(接收此信号要调用的函数地址)。
signal函数的返回值是一个函数地址,指向在此之前的信号处理函数,而func指向新的信号处理函数。
由于函数原型太过复杂,也可使用下面的定义方式:
typedef void Sigfunc(int);
Sigfunc* signal(int, Sigfunc*);
看一个实例:
1 #include <stdio.h> 2 #include <signal.h> 3 4 void fun() 5 { 6 int i = 0; 7 for(; i < 3; i++) 8 { 9 printf("Hello World\n");//1秒打印一次,执行3次 10 sleep(1); 11 } 12 } 13 14 void main() 15 { 16 signal(SIGINT,fun);//信号:当用户按下Ctrl+C时,执行fun函数 17 while(1) 18 { 19 sleep(2); 20 printf("Running\n");//每2秒打印一次 21 } 22 }
执行结果:
可以看到,每次按下ctrl+c,都会执行一次打印三次Hello World,也就是说,每次按下Ctrl+C时,都会发送一个信号,让程序去执行调用并执行fun函数。
同样,我做一个小小的改动,就能实现让第一次SIGINT信号成为执行fun的命令,第二次按下时直接程序结束。我把SIGINT信号修改为默认方式接收的signal函数在fun中,为什么不放在主函数main中呢?因为我把放在signal(SIGINT,fun)之前会导致该信号又被修改为去执行fun了,而其放在之后,又直接为为默认方式,而不能同时使用两个信号。当我把signal(SIGINT,SIG_DFL)放在fun函数里时就不会发生这些事了,因为只有当第一个信号起作用时才会进入fun函数,而在fun函数里,无论我把第二个信号放在哪儿都是能实现的。
1 #include <stdio.h> 2 #include <signal.h> 3 4 void fun() 5 { 6 int i = 0; 7 for(; i < 3; i++) 8 { 9 printf("Hello World\n");//秒打印一次,执行3次 10 sleep(1); 11 signal(SIGINT,SIG_DFL);//以默认方式接收信号 12 } 13 } 14 15 void main() 16 { 17 signal(SIGINT,fun);//信号:当用户按下Ctrl+C时,执行fun函数 18 while(1) 19 { 20 sleep(2); 21 printf("Running\n"); 22 } 23 }
结果如下:
如图所示,当第二次按下Ctrl+C时,信号又变成了默认操作,直接结束程序。
还是这段代码,当我按下Ctrl+C的时机是在执行fun时,程序会在执行完fun之后直接结束。