进程间通信五(信号)

1.什么是信号?

A.信号是在软件层次上对中断机制的一种模拟。在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。

B.信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上进程也不知道信号到底什么时候到达。

C.信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。它可以在任何时候发给某一个进程,而无需知道该进程的状态。如果该信号当前并未处于执行态(Running),则该信号由内核保存起来,直到该进程恢复执行再传递给它为止。如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。

D.信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事件发生了。信号机制除了基本通知外,还可以传递附加信息。

2.信号来源

A.硬件来源。如我们按下了键盘上的按钮 或者出现其他硬件故障;

B.软件来源。最常用发送信号的系统函数有kill()、raise()、alarm()、setitimer()和sigqueue()等,软件来源还包括一些非法运算等操作。

3.进程响应信号的方式

A.忽略信号。忽略信号即对信号不做处理,其中,有两个信号不能忽略:SIGKILL和SIGSTOP。

B.捕捉信号。定义信号处理函数,当信号发生时,执行响应的处理函数。

C.执行默认操作。Linux对每种信号都规定了默认操作,如下表所示:

4.信号的生命周期

  一个完整的信号生命周期可以分为3个重要阶段,这3个阶段由4个重要事件来刻画的;信号产生、信号在进程中注册、信号在进程中注销、执行信号处理函数。这里信号的产生、注册、注销等是指信号的内部实现机制,而不是信号的函数实现(不受我们的掌控)。因此信号注册与否与后面讲到的发送信号函数(如 kill()等)及信号安装函数(如 signal()等)无关,只与信号值有关。

相邻两个事件的时间间隔构成信号生命周期的一个阶段,如下图,注意这里的信号处理有多种方式,一般是由内核完成的,当然也可以由用户进程来完成。

信号的处理包括信号的发送、捕捉和处理,它们有各自相对应的常见函数:

发生信号的函数: kill()、raise()。

捕捉信号的函数: alarm()、pause()。

处理信号的函数: signal()、sigaction()。

5.发送信号的主要函数有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。

函数说明:

5.1 kill()函数可以发送信号给进程或进程组。

下表列出了kill()的格式

5.2 raise() 
#include <signal.h> 
int raise(int signo) 
向进程本身发送信号,参数为即将发送的信号值。调用成功返回 0;否则,返回 -1。

5.3 sigqueue() 
#include <sys/types.h> 
#include <signal.h> 
int sigqueue(pid_t pid, int sig, const union sigval val) 
调用成功返回 0;否则,返回 -1。

sigqueue()是比较新的发送信号系统调用,主要是针对实时信号提出的(当然也支持前32种),支持信号带有参数,与函数sigaction()配合使用。

sigqueue的第一个参数是指定接收信号的进程ID,第二个参数确定即将发送的信号,第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。

 	typedef union sigval {
 		int  sival_int;
 		void *sival_ptr;
 	}sigval_t;

sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。如果signo=0,将会执行错误检查,但实际上不发送任何信号,0值信号可用于检查pid的有效性以及当前进程是否有权限向目标进程发送信号。

在调用sigqueue时,sigval_t指定的信息会拷贝到3参数信号处理函数(3参数信号处理函数指的是信号处理函数由sigaction安装,并设定了sa_sigaction指针,稍后将阐述)的siginfo_t结构中,这样信号处理函数就可以处理这些信息了。由于sigqueue系统调用支持发送带参数信号,所以比kill()系统调用的功能要灵活和强大得多。

注:sigqueue()发送非实时信号时,第三个参数包含的信息仍然能够传递给信号处理函数; sigqueue()发送非实时信号时,仍然不支持排队,即在信号处理函数执行过程中到来的所有相同信号,都被合并为一个信号。

5.4 alarm() 
#include <unistd.h> 
unsigned int alarm(unsigned int seconds) 
专门为SIGALRM信号而设,在指定的时间seconds秒后,将向进程本身发送SIGALRM信号,又称为闹钟时间。进程调用alarm后,任何以前的alarm()调用都将无效。如果参数seconds为零,那么进程内将不再包含任何闹钟时间。 
返回值,如果调用alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。

5.5 setitimer() 
#include <sys/time.h> 
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue)); 
setitimer()比alarm功能强大,支持3种类型的定时器:

  • ITIMER_REAL: 设定绝对时间;经过指定的时间后,内核将发送SIGALRM信号给本进程;
  • ITIMER_VIRTUAL 设定程序执行时间;经过指定的时间后,内核将发送SIGVTALRM信号给本进程;
  • ITIMER_PROF 设定进程执行以及内核因本进程而消耗的时间和,经过指定的时间后,内核将发送ITIMER_VIRTUAL信号给本进程;

Setitimer()第一个参数which指定定时器类型(上面三种之一);第二个参数是结构itimerval的一个实例,结构itimerval形式见附录1。第三个参数可不做处理。

Setitimer()调用成功返回0,否则返回-1。

5.6 abort() 
#include <stdlib.h> 
void abort(void);

向进程发送SIGABORT信号,默认情况下进程会异常退出,当然可定义自己的信号处理函数。即使SIGABORT被进程设置为阻塞信号,调用abort()后,SIGABORT仍然能被进程接收。该函数无返回值。

6.实例

该例首先使用 fork()创建了一个子进程,接着为了保证子进程不在父进程调用kill()之前退出,在子进程中使用raise()函数向自身发送 SIGSTOP信号,使子进程暂停。接下来在父进程中调用kill()向子进程发送信号,在该实验中使用的是SIGKILL。实验代码如下:

#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
  pid_t pid;
  int ret;
  /*创建一个子进程*/
  if((pid=fork())<0) /*出错处理*/
  {
    printf("Fork error ");
    exit(1);
  }
  if(pid==0) /*子进程*/
  {
    /*在子进程中使用raise()函数发出SIGSTOP信号,使子进程暂停*/
    printf("I am child progress(pid:%d).I am waiting for any signal ",getpid());
    raise(SIGSTOP);
    printf("I am child progress(pid:%d).I am killed by progress:%d ",getpid(),getppid());
    exit(0);
  }
  else /*父进程*/
  {
    sleep(2); /*先让父进程休眠,让子进程执行,建议你把这句话去掉试一试*/
    /*在父进程中收集子进程发出的信号,并调用kill()函数进行相应的操作*/
    if((waitpid(pid,NULL,WNOHANG))==0)
    { /*若pid指向的子进程没有退出,则返回0,且父进程不阻塞,继续执行下边的语句*/
      if((ret=kill(pid,SIGKILL))==0)
      {
        printf("I am parent progress(pid:%d).I kill %d ",getpid(),pid);
      }
    }
    waitpid(pid,NULL,0);/*等待子进程退出,否则就一直阻塞*/
    exit(0);
  }
}/* end */

编译运行

[wss@localhost sig2]$gcc signal.c
[wss@localhost sig2]$./a.out
I am child progress(pid:22021).I am waiting for any signal
I am parent progress(pid:22020).I kill 22021
[wss@localhost sig2]$

7.信号捕捉函数: alarm()、pause()

函数说明:

alarm()也称为闹钟函数,它可以在进程中设置一个定时器,当定时器指定的时间到时,它就向进程发送SIGALARM信号。要注意的是,一个进程只能有一个闹钟时间,如果在调用alarm()之前已设置过闹钟时间,则任何以前的闹钟时间都被新值所代替。

pause()函数用于将调用进程挂起直至捕捉到信号为止。这个函数很常用,通常可以用于判断信号是否已到。

例子:

#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
int main()
{
  int ret;
  ret = alarm(5);
  pause();
  printf("I have wake up!");
  return 0;
}

[wss@localhost test]$gcc test.c
[wss@localhost test]$./a.out
Alarm clock
[wss@localhost test]$


可以看到12行的语句根本就没执行,其实想想程序的执行流程就很清除,首先程序定时,执行到11行pause();时进程会被挂起,当计时到,发出信号SIGALARM,这时pause()捕捉到信号,进程直接被终止。

以上   转自:http://blog.csdn.net/mybelief321/article/details/9078193

信号的处理

signal函数
程序可用使用signal函数来处理指定的信号,主要通过忽略和恢复其默认行为来工作。signal函数的原型如下:
#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);
signal()是一个带有sig和func两个参数的函数,func是一个类型为void (*)(int)的函数指针。准备捕获的信号的参数由sig给出,接收到的指定信号后要调用的函数由参数func给出。其实这个函数的使用是相当简单的,通过下面的例子就可以知道。注意信号处理函数的原型必须为void func(int),或者是下面的特殊值:
SIG_IGN:忽略信号
SIG_DFL:恢复信号的默认行为

说了这么多,还是给出一个例子,代码如下:
#include <signal.h> 
#include <stdio.h> 
#include <unistd.h> 

void ouch(int sig) 

  printf(" OUCH! - I got signal %d ", sig); 
  //恢复终端中断信号SIGINT的默认行为 
  (void) signal(SIGINT, SIG_DFL); 


int main() 

  //改变终端中断信号SIGINT的默认行为,使之执行ouch函数 
  //而不是终止程序的执行 
  (void) signal(SIGINT, ouch); 
  while(1) 
  { 
    printf("Hello World! "); 
    sleep(1); 
  } 
  return 0; 

运行结果如下:

[wss@localhost test2]$gcc test.c
[wss@localhost test2]$./a.out
Hello World!
Hello World!
Hello World!
Hello World!
^C
OUCH! - I got signal 2
Hello World!
Hello World!
^C
[wss@localhost test2]$

可以看到,第一次按下终止命令(ctrl+c)时,进程并没有被终止,面是输出OUCH! - I got signal 2,因为SIGINT的默认行为被signal函数改变了,当进程接受到信号SIGINT时,它就去调用函数ouch去处理,注意ouch函数把信号SIGINT的处理方式改变成默认的方式,所以当你再按一次ctrl+c时,进程就像之前那样被终止了。

信号处理——sigaction函数
前面我们看到了signal函数对信号的处理,但是一般情况下我们可以使用一个更加健壮的信号接口——sigaction函数。它的原型为:
#include <signal.h>
int sigaction(int sig, const struct sigaction *act, struct sigaction *oact);
该函数与signal函数一样,用于设置与信号sig关联的动作,而oact如果不是空指针的话,就用它来保存原先对该信号的动作的位置,act则用于设置指定信号的动作。

sigaction结构体定义在signal.h中,但是它至少包括以下成员:
void (*) (int) sa_handler;处理函数指针,相当于signal函数的func参数。
sigset_t sa_mask; 指定一个。信号集,在调用sa_handler所指向的信号处理函数之前,该信号集将被加入到进程的信号屏蔽字中。信号屏蔽字是指当前被阻塞的一组信号,它们不能被当前进程接收到
int sa_flags;信号处理修改器;

sa_mask的值通常是通过使用信号集函数来设置的,关于信号集函数,我将会在我的下一篇文章——Linux进程间通信——信号集函数,详细讲述。
sa_flags,通常可以取以下的值:


此外,现在有一个这样的问题,我们使用signal或sigaction函数来指定处理信号的函数,但是如果这个信号处理函数建立之前就接收到要处理的信号的话,进程会有怎样的反应呢?它就不会像我们想像的那样用我们设定的处理函数来处理了。sa_mask就可以解决这样的问题,sa_mask指定了一个信号集,在调用sa_handler所指向的信号处理函数之前,该信号集将被加入到进程的信号屏蔽字中,设置信号屏蔽字可以防止信号在它的处理函数还未运行结束时就被接收到的情况,即使用sa_mask字段可以消除这一竞态条件。

承接上面的例子,下面给出用sigaction函数重写的例子代码,源文件为signal2.c,代码如下:
#include <unistd.h>
#include <stdio.h>
#include <signal.h>

void ouch(int sig)
{
  printf(" OUCH! - I got signal %d ", sig);
}

int main()
{
  struct sigaction act;
  act.sa_handler = ouch;
  //创建空的信号屏蔽字,即不屏蔽任何信息
  sigemptyset(&act.sa_mask);
  //使sigaction函数重置为默认行为
  act.sa_flags = SA_RESETHAND;

  sigaction(SIGINT, &act, 0);

  while(1)
  {
    printf("Hello World! ");
    sleep(1);
  }
  return 0;
}
运行结果与前一个例子中的相同。注意sigaction函数在默认情况下是不被重置的,如果要想它重置,则sa_flags就要为SA_RESETHAND。

以上转自:http://blog.csdn.net/ljianhui/article/details/10128731

原文地址:https://www.cnblogs.com/thinkinglife/p/5520955.html