初识信号---进程间的交流

一、信号机制

  正如我们标题所说,信号就是进程间的对话,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
");//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
");//每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
");//秒打印一次,执行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
");
22     }
23 }

  结果如下:

初识信号---进程间的交流

  如图所示,当第二次按下Ctrl+C时,信号又变成了默认操作,直接结束程序。

  还是这段代码,当我按下Ctrl+C的时机是在执行fun时,程序会在执行完fun之后直接结束。

初识信号---进程间的交流