Linux环境进程间通讯(二): 信号(上)

linux下进程间通讯的几种主要手段:php

  1. 管道(Pipe)及有名管道(named pipe):管道可用于具备亲缘关系进程间的通讯,有名管道克服了管道没有名字的限制,所以,除具备管道所具备的功能外,它还容许无亲缘关系进程间的通讯; 
  2. 信号(Signal):信号是比较复杂的通讯方式,用于通知接受进程有某种事件发生,除了用于进程间通讯外,进程还能够发送信号给进程自己;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又可以统一对外接口,用sigaction函数从新实现了signal函数); 
  3. 报文(Message)队列(消息队列):消息队列是消息的连接表,包括Posix消息队列system V消息队列。有足够权限的进程能够向队列中添加消息,被赋予读权限的进程则能够读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。 
  4. 共享内存:使得多个进程能够访问同一块内存空间,是最快的可用IPC形式。是针对其余通讯机制运行效率较低而设计的。每每与其它通讯机制,如信号量结合使用,来达到进程间的同步及互斥。 
  5. 信号量(semaphore):主要做为进程间以及同一进程不一样线程之间的同步手段。 
  6. 套接口(Socket):更为通常的进程间通讯机制,可用于不一样机器之间的进程间通讯。起初是由Unix系统的BSD分支开发出来的,但如今通常能够移植到其它类Unix系统上:LinuxSystem V的变种都支持套接字。 

本文讲述进程间通讯方法——信号html

1、信号及信号来源linux

信号本质编程

信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求能够说是同样的。信号是异步的,一个进程没必要经过任何操做来等待信号的到达,事实上,进程也不知道信号到底何时到达。session

信号是进程间通讯机制中惟一的异步通讯机制,能够看做是异步通知,通知接收信号的进程有哪些事情发生了。信号机制通过POSIX实时扩展后,功能更增强大,除了基本通知功能外,还能够传递附加信息。数据结构

信号来源异步

信号事件的发生有两个来源:硬件来源(好比咱们按下了键盘或者其它硬件故障);软件来源,最经常使用发送信号的系统函数是kill, raise, alarm和setitimer以及sigqueue函数,软件来源还包括一些非法运算等操做。函数

2、信号的种类ui

能够从两个不一样的分类角度对信号进行分类:(1)可靠性方面:可靠信号与不可靠信号;(2)与时间的关系上:实时信号与非实时信号。在《Linux环境进程间通讯(一):管道及有名管道》的附1中列出了系统所支持的全部信号。spa

一、可靠信号与不可靠信号

"不可靠信号"

Linux信号机制基本上是从Unix系统中继承过来的。早期Unix系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题,所以,把那些创建在早期机制上的信号叫作"不可靠信号",信号值小于SIGRTMIN(Red hat 7.2中,SIGRTMIN=32,SIGRTMAX=63)的信号都是不可靠信号。这就是"不可靠信号"的来源。它的主要问题是:

  • 进程每次处理信号后,就将对信号的响应设置为默认动做。在某些状况下,将致使对信号的错误处理;所以,用户若是不但愿这样的操做,那么就要在信号处理函数结尾再一次调用signal(),从新安装该信号。
  • 信号可能丢失,后面将对此详细阐述。 
    所以,早期unix下的不可靠信号主要指的是进程可能对信号作出错误的反应以及信号可能丢失。

Linux支持不可靠信号,可是对不可靠信号机制作了改进:在调用完信号处理函数后,没必要从新调用该信号的安装函数(信号安装函数是在可靠机制上的实现)。所以,Linux下的不可靠信号问题主要指的是信号可能丢失。

"可靠信号"

随着时间的发展,实践证实了有必要对信号的原始机制加以改进和扩充。因此,后来出现的各类Unix版本分别在这方面进行了研究,力图实现"可靠信号"。因为原来定义的信号已有许多应用,很差再作改动,最终只好又新增长了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。同时,信号的发送和安装也出现了新版本:信号发送函数sigqueue()及信号安装函数sigaction()。POSIX.4对可靠信号机制作了标准化。可是,POSIX只对可靠信号机制应具备的功能以及信号机制的对外接口作了标准化,对信号机制的实现没有做具体的规定。

信号值位于SIGRTMIN和SIGRTMAX之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。Linux在支持新版本的信号安装函数sigation()以及信号发送函数sigqueue()的同时,仍然支持早期的signal()信号安装函数,支持信号发送函数kill()。

注:不要有这样的误解:由sigqueue()发送、sigaction安装的信号就是可靠的。事实上,可靠信号是指后来添加的新信号(信号值位于SIGRTMIN及SIGRTMAX之间);不可靠信号是信号值小于SIGRTMIN的信号。信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。目前linux中的signal()是经过sigation()函数实现的,所以,即便经过signal()安装的信号,在信号处理函数的结尾也没必要再调用一次信号安装函数。同时,由signal()安装的实时信号支持排队,一样不会丢失。

对于目前linux的两个信号安装函数:signal()及sigaction()来讲,它们都不能把SIGRTMIN之前的信号变成可靠信号(都不支持排队,仍有可能丢失,仍然是不可靠信号),并且对SIGRTMIN之后的信号都支持排队。这两个函数的最大区别在于,通过sigaction安装的信号都能传递信息给信号处理函数(对全部信号这一点都成立),而通过signal安装的信号却不能向信号处理函数传递信息。对于信号发送函数来讲也是同样的。

二、实时信号与非实时信号

早期Unix系统只定义了32种信号,Ret hat7.2支持64种信号,编号0-63(SIGRTMIN=31,SIGRTMAX=63),未来可能进一步增长,这须要获得内核的支持。前32种信号已经有了预约义值,每一个信号有了肯定的用途及含义,而且每种信号都有各自的缺省动做。如按键盘的CTRL ^C时,会产生SIGINT信号,对该信号的默认反应就是进程终止。后32个信号表示实时信号,等同于前面阐述的可靠信号。这保证了发送的多个实时信号都被接收。实时信号是POSIX标准的一部分,可用于应用进程。

非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。

3、进程对信号的响应

进程能够经过三种方式来响应一个信号:(1)忽略信号,即对信号不作任何处理,其中,有两个信号不能忽略:SIGKILL及SIGSTOP;(2)捕捉信号。定义信号处理函数,当信号发生时,执行相应的处理函数;(3)执行缺省操做,Linux对每种信号都规定了默认操做,详细状况请参考[2]以及其它资料。注意,进程对实时信号的缺省反应是进程终止。

Linux究竟采用上述三种方式的哪个来响应信号,取决于传递给相应API函数的参数。

4、信号的发送

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

一、kill() 
#include <sys/types.h> 
#include <signal.h> 
int kill(pid_t pid,int signo) 

参数pid的值 信号的接收进程
pid>0 进程ID为pid的进程
pid=0 同一个进程组的进程
pid<0 pid!=-1 进程组ID为 -pid的全部进程
pid=-1 除发送进程自身外,全部进程ID大于1的进程

Sinno是信号值,当为0时(即空信号),实际不发送任何信号,但照常进行错误检查,所以,可用于检查目标进程是否存在,以及当前进程是否具备向目标发送信号的权限(root权限的进程能够向任何进程发送信号,非root权限的进程只能向属于同一个session或者同一个用户的进程发送信号)。

Kill()最经常使用于pid>0时的信号发送,调用成功返回 0; 不然,返回 -1。 注:对于pid<0时的状况,对于哪些进程将接受信号,各类版本说法不一,其实很简单,参阅内核源码kernal/signal.c便可,上表中的规则是参考red hat 7.2。

二、raise() 
#include <signal.h> 
int raise(int signo) 
向进程自己发送信号,参数为即将发送的信号值。调用成功返回 0;不然,返回 -1。

三、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()发送非实时信号时,仍然不支持排队,即在信号处理函数执行过程当中到来的全部相同信号,都被合并为一个信号。

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

五、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。

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

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

5、信号的安装(设置信号关联动做)

若是进程要处理某一信号,那么就要在进程中安装该信号。安装信号主要用来肯定信号值及进程针对该信号值的动做之间的映射关系,即进程将要处理哪一个信号;该信号被传递给进程时,将执行何种操做。

linux主要有两个函数实现信号的安装:signal()、sigaction()。其中signal()在可靠信号系统调用的基础上实现, 是库函数。它只有两个参数,不支持信号传递信息,主要是用于前32种非实时信号的安装;而sigaction()是较新的函数(由两个系统调用实现:sys_signal以及sys_rt_sigaction),有三个参数,支持信号传递信息,主要用来与 sigqueue() 系统调用配合使用,固然,sigaction()一样支持非实时信号的安装。sigaction()优于signal()主要体如今支持信号带有参数。

一、signal() 
#include <signal.h> 
void (*signal(int signum, void (*handler))(int)))(int); 
若是该函数原型不容易理解的话,能够参考下面的分解方式来理解: 
typedef void (*sighandler_t)(int); 
sighandler_t signal(int signum, sighandler_t handler)); 
第一个参数指定信号的值,第二个参数指定针对前面信号值的处理,能够忽略该信号(参数设为SIG_IGN);能够采用系统默认方式处理信号(参数设为SIG_DFL);也能够本身实现处理方式(参数指定一个函数地址)。 
若是signal()调用成功,返回最后一次为安装信号signum而调用signal()时的handler值;失败则返回SIG_ERR。

二、sigaction() 
#include <signal.h> 
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));

sigaction函数用于改变进程接收到特定信号后的行为。该函数的第一个参数为信号的值,能够为除SIGKILL及SIGSTOP外的任何一个特定有效的信号(为这两个信号定义本身的处理函数,将致使信号安装错误)。第二个参数是指向结构sigaction的一个实例的指针,在结构sigaction的实例中,指定了对特定信号的处理,能够为空,进程会以缺省方式对信号处理;第三个参数oldact指向的对象用来保存原来对相应信号的处理,可指定oldact为NULL。若是把第2、第三个参数都设为NULL,那么该函数可用于检查信号的有效性。

第二个参数最为重要,其中包含了对指定信号的处理、信号所传递的信息、信号处理函数执行过程当中应屏蔽掉哪些函数等等。

sigaction结构定义以下:

 struct sigaction {
          union{
            __sighandler_t _sa_handler;
            void (*_sa_sigaction)(int,struct siginfo *, void *);
            }_u
                     sigset_t sa_mask;
                    unsigned long sa_flags; 
                  void (*sa_restorer)(void);
                  }
					

其中,sa_restorer,已过期,POSIX不支持它,不该再被使用。

一、联合数据结构中的两个元素_sa_handler以及*_sa_sigaction指定信号关联函数,即用户指定的信号处理函数。除了能够是用户自定义的处理函数外,还能够为SIG_DFL(采用缺省的处理方式),也能够为SIG_IGN(忽略信号)。

二、由_sa_handler指定的处理函数只有一个参数,即信号值,因此信号不能传递除信号值以外的任何信息;由_sa_sigaction是指定的信号处理函数带有三个参数,是为实时信号而设的(固然一样支持非实时信号),它指定一个3参数信号处理函数。第一个参数为信号值,第三个参数没有使用(posix没有规范使用该参数的标准),第二个参数是指向siginfo_t结构的指针,结构中包含信号携带的数据值,参数所指向的结构以下:

 siginfo_t {
                  int      si_signo;  /* 信号值,对全部信号有意义*/
                  int      si_errno;  /* errno值,对全部信号有意义*/
                  int      si_code;   /* 信号产生的缘由,对全部信号有意义*/
        union{          /* 联合数据结构,不一样成员适应不一样信号 */  
          //确保分配足够大的存储空间
          int _pad[SI_PAD_SIZE];
          //对SIGKILL有意义的结构
          struct{
              ...
              }...
            ... ...
            ... ...          
          //对SIGILL, SIGFPE, SIGSEGV, SIGBUS有意义的结构
              struct{
              ...
              }...
            ... ...
            }
      }
			

注:为了更便于阅读,在说明问题时常把该结构表示为附录2所表示的形式。

siginfo_t结构中的联合数据成员确保该结构适应全部的信号,好比对于实时信号来讲,则实际采用下面的结构形式:

	typedef struct {
		int si_signo;
		int si_errno;			
		int si_code;			
		union sigval si_value;	
		} siginfo_t;
		

结构的第四个域一样为一个联合数据结构:

	union sigval {
		int sival_int;		
		void *sival_ptr;	
		}

采用联合数据结构,说明siginfo_t结构中的si_value要么持有一个4字节的整数值,要么持有一个指针,这就构成了与信号相关的数据。在信号的处理函数中,包含这样的信号相关数据指针,但没有规定具体如何对这些数据进行操做,操做方法应该由程序开发人员根据具体任务事先约定。

前面在讨论系统调用sigqueue发送信号时,sigqueue的第三个参数就是sigval联合数据结构,当调用sigqueue时,该数据结构中的数据就将拷贝到信号处理函数的第二个参数中。这样,在发送信号同时,就可让信号传递一些附加信息。信号能够传递信息对程序开发是很是有意义的。

信号参数的传递过程可图示以下:

 

三、sa_mask指定在信号处理程序执行过程当中,哪些信号应当被阻塞。缺省状况下当前信号自己被阻塞,防止信号的嵌套发送,除非指定SA_NODEFER或者SA_NOMASK标志位。

注:请注意sa_mask指定的信号阻塞的前提条件,是在由sigaction()安装信号的处理函数执行过程当中由sa_mask指定的信号才被阻塞。

四、sa_flags中包含了许多标志位,包括刚刚提到的SA_NODEFER及SA_NOMASK标志位。另外一个比较重要的标志位是SA_SIGINFO,当设定了该标志位时,表示信号附带的参数能够被传递到信号处理函数中,所以,应该为sigaction结构中的sa_sigaction指定处理函数,而不该该为sa_handler指定信号处理函数,不然,设置该标志变得毫无心义。即便为sa_sigaction指定了信号处理函数,若是不设置SA_SIGINFO,信号处理函数一样不能获得信号传递过来的数据,在信号处理函数中对这些信息的访问都将致使段错误(Segmentation fault)。

注:不少文献在阐述该标志位时都认为,若是设置了该标志位,就必须定义三参数信号处理函数。实际不是这样的,验证方法很简单:本身实现一个单一参数信号处理函数,并在程序中设置该标志位,能够察看程序的运行结果。实际上,能够把该标志位当作信号是否传递参数的开关,若是设置该位,则传递参数;不然,不传递参数。 

信号集被定义为一种数据类型:

	typedef struct {
			unsigned long sig[_NSIG_WORDS];
			} sigset_t

信号集用来描述信号的集合,linux所支持的全部信号能够所有或部分的出如今信号集中,主要与信号阻塞相关函数配合使用。下面是为信号集操做定义的相关函数:

	#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum)
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
sigemptyset(sigset_t *set)初始化由set指定的信号集,信号集里面的全部信号被清空;
sigfillset(sigset_t *set)调用该函数后,set指向的信号集中将包含linux支持的64种信号;
sigaddset(sigset_t *set, int signum)在set指向的信号集中加入signum信号;
sigdelset(sigset_t *set, int signum)在set指向的信号集中删除signum信号;
sigismember(const sigset_t *set, int signum)断定信号signum是否在set指向的信号集中。

7、信号阻塞与信号未决:

每一个进程都有一个用来描述哪些信号递送到进程时将被阻塞的信号集,该信号集中的全部信号在递送到进程后都将被阻塞。下面是与信号阻塞相关的几个函数:

#include <signal.h>
int  sigprocmask(int  how,  const  sigset_t *set, sigset_t *oldset));
int sigpending(sigset_t *set));
int sigsuspend(const sigset_t *mask));

sigprocmask()函数可以根据参数how来实现对信号集的操做,操做主要有三种:

参数how 进程当前信号集
SIG_BLOCK 在进程当前阻塞信号集中添加set指向信号集中的信号
SIG_UNBLOCK 若是进程阻塞信号集中包含set指向信号集中的信号,则解除对该信号的阻塞
SIG_SETMASK 更新进程阻塞信号集为set指向的信号集

sigpending(sigset_t *set))得到当前已递送到进程,却被阻塞的全部信号,在set指向的信号集中返回结果。

sigsuspend(const sigset_t *mask))用于在接收到某个信号以前, 临时用mask替换进程的信号掩码, 并暂停进程执行,直到收到信号为止。sigsuspend 返回后将恢复调用以前的信号掩码。信号处理函数完成后,进程将继续执行。该系统调用始终返回-1,并将errno设置为EINTR。

附录1:结构itimerval:

            struct itimerval {
                struct timeval it_interval; /* next value */
                struct timeval it_value;    /* current value */
            };
            struct timeval {
                long tv_sec;                /* seconds */
                long tv_usec;               /* microseconds */
            };

附录2:三参数信号处理函数中第二个参数的说明性描述:

siginfo_t {
int      si_signo;  /* 信号值,对全部信号有意义*/
int      si_errno;  /* errno值,对全部信号有意义*/
int      si_code;   /* 信号产生的缘由,对全部信号有意义*/
pid_t    si_pid;    /* 发送信号的进程ID,对kill(2),实时信号以及SIGCHLD有意义 */
uid_t    si_uid;    /* 发送信号进程的真实用户ID,对kill(2),实时信号以及SIGCHLD有意义 */
int      si_status; /* 退出状态,对SIGCHLD有意义*/
clock_t  si_utime;  /* 用户消耗的时间,对SIGCHLD有意义 */
clock_t  si_stime;  /* 内核消耗的时间,对SIGCHLD有意义 */
sigval_t si_value;  /* 信号值,对全部实时有意义,是一个联合数据结构,
                          /*能够为一个整数(由si_int标示,也能够为一个指针,由si_ptr标示)*/
	
void *   si_addr;   /* 触发fault的内存地址,对SIGILL,SIGFPE,SIGSEGV,SIGBUS 信号有意义*/
int      si_band;   /* 对SIGPOLL信号有意义 */
int      si_fd;     /* 对SIGPOLL信号有意义 */
}

实际上,除了前三个元素外,其余元素组织在一个联合结构中,在联合数据结构中,又根据不一样的信号组织成不一样的结构。注释中提到的对某种信号有意义指的是,在该信号的处理函数中能够访问这些域来得到与信号相关的有意义的信息,只不过特定信号只对特定信息感兴趣而已。

参考资料

    1. linux内核源代码情景分析(上),毛德操、胡希明著,浙江大学出版社,当要验证某个结论、想法时,最好的参考资料;
    2. UNIX环境高级编程,做者:W.Richard Stevens,译者:尤晋元等,机械工业出版社。对信号机制的发展过程阐述的比较详细。
    3. signal、sigaction、kill等手册,最直接而可靠的参考资料。
    4. http://www.linuxjournal.com/modules.php?op=modload&name=NS-help&file=man提供了许多系统调用、库函数等的在线指南。
    5. http://www.opengroup.org/onlinepubs/007904975/能够在这里对许多关键函数(包括系统调用)进行查询,很是好的一个网址。
    6. http://unix.org/whitepapers/reentrant.html对函数可重入进行了阐述。
    7. http://www.uccs.edu/~compsvcs/doc-cdrom/DOCS/HTML/APS33DTE/DOCU_006.HTM对实时信号给出了至关好的描述。

 来源:http://www.ibm.com/developerworks/cn/linux/l-ipc/part2/index1.html

相关文章
相关标签/搜索