Linux程序设计学习笔记——异步信号处理机制

转载请注明出处: http://blog.csdn.net/suool/article/details/38453333

Linux常见信号与处理

基本概念

Linux的信号是一种进程间异步的通信机制,在实现上一种软中断。信号可以导致一个正在运行的进程被异步打断,转而去处理一个突发事件。异步事件不可预知,只能通过一些特定方式预防,或者说,当该异步事件发生时根据原来的设定完成相应的操作。

信号本质

信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。

信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。信号机制经过POSIX实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息。

信号来源

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


Ubuntu下所有支持的信号如下:

具体的信号说明参见这些博文:

http://blog.csdn.net/cloudtech/article/details/3758962

http://www.cnblogs.com/taobataoma/archive/2007/08/30/875743.html

这些信号在Linux源码的signum.h中的定义如下:

/* Signals.  */
#define	SIGHUP		1	/* Hangup (POSIX).  */
#define	SIGINT		2	/* Interrupt (ANSI).  */
#define	SIGQUIT		3	/* Quit (POSIX).  */
#define	SIGILL		4	/* Illegal instruction (ANSI).  */
#define	SIGTRAP		5	/* Trace trap (POSIX).  */
#define	SIGABRT		6	/* Abort (ANSI).  */
#define	SIGIOT		6	/* IOT trap (4.2 BSD).  */
#define	SIGBUS		7	/* BUS error (4.2 BSD).  */
#define	SIGFPE		8	/* Floating-point exception (ANSI).  */
#define	SIGKILL		9	/* Kill, unblockable (POSIX).  */
#define	SIGUSR1		10	/* User-defined signal 1 (POSIX).  */
#define	SIGSEGV		11	/* Segmentation violation (ANSI).  */
#define	SIGUSR2		12	/* User-defined signal 2 (POSIX).  */
#define	SIGPIPE		13	/* Broken pipe (POSIX).  */
#define	SIGALRM		14	/* Alarm clock (POSIX).  */
#define	SIGTERM		15	/* Termination (ANSI).  */
#define	SIGSTKFLT	16	/* Stack fault.  */
#define	SIGCLD		SIGCHLD	/* Same as SIGCHLD (System V).  */
#define	SIGCHLD		17	/* Child status has changed (POSIX).  */
#define	SIGCONT		18	/* Continue (POSIX).  */
#define	SIGSTOP		19	/* Stop, unblockable (POSIX).  */
#define	SIGTSTP		20	/* Keyboard stop (POSIX).  */
#define	SIGTTIN		21	/* Background read from tty (POSIX).  */
#define	SIGTTOU		22	/* Background write to tty (POSIX).  */
#define	SIGURG		23	/* Urgent condition on socket (4.2 BSD).  */
#define	SIGXCPU		24	/* CPU limit exceeded (4.2 BSD).  */
#define	SIGXFSZ		25	/* File size limit exceeded (4.2 BSD).  */
#define	SIGVTALRM	26	/* Virtual alarm clock (4.2 BSD).  */
#define	SIGPROF		27	/* Profiling alarm clock (4.2 BSD).  */
#define	SIGWINCH	28	/* Window size change (4.3 BSD, Sun).  */
#define	SIGPOLL		SIGIO	/* Pollable event occurred (System V).  */
#define	SIGIO		29	/* I/O now possible (4.2 BSD).  */
#define	SIGPWR		30	/* Power failure restart (System V).  */
#define SIGSYS		31	/* Bad system call.  */
#define SIGUNUSED	31

#define	_NSIG		65	/* Biggest signal number + 1
				   (including real-time signals).  */
与信号中断处理相关的术语:

发送信号:产生信号,有多种发送信号的方式。一个进程向另一个进程发送特定的信号,内核向用户进程发送一个信号,一个进程想自己发送信号,

安装中断:设置信号到来时的不再执行默认操作,而是执行自定义的代码,即期望某个信号到来时让进程执行相应的中断服务。

递送信号:一个信号被OS发送到目标进程。

捕获信号:被递送的信号在目标进程引起某段处理程序的执行

屏蔽信号:进程告诉OS暂时不接受信号。若在屏蔽期间向进程发送了某信号,该信号不会被捕获,但是在屏蔽结束后,该信号将被捕获。

忽略信号:进程被递送到目标进程,但是目标进程不会处理,直接丢弃。

未决信号:信号已经产生,但是因为目标进程屏蔽信号导致暂时不能被目标进程捕获的信号。

可靠信号与不可靠信号:

Linux信号机制基本上是从Unix系统中继承过来的。把那些建立在早期机制上的信号叫做"不可靠信号",信号值小于SIGRTMIN的信号都是不可靠信号。这就是"不可靠信号"的来源。它的主要问题是:

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

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

可靠信号

随着时间的发展,实践证明了有必要对信号的原始机制加以改进和扩充。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。同时,信号的发送和安装也出现了新版本:信号发送函数sigqueue()及信号安装函数sigaction()。但是,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标准的一部分,可用于应用进程。

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

信号生命周期

1、在目标进程安装信号

2、信号被某个进程产生,同时设置此信号的目标进程

3、信号在目标进程被注册

4、信号在进程中注销

5、信号生命终止

信号的发送

信号的发送不是直接由发送的而是有OS转发的。

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

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

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

3.unalarm定时

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

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。

函数setitimer()和getitimer根据逝去时间、在用户进程执行时间、总的执行时间设置/独处超时定时器的信息,定时器将在超时后产生相应的信号。

gettitimer()函数用来获取和设置上述的三种定时器。

下面是一个示例:

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <signal.h>

int main(void)
{	
	struct itimerval setvalue;
	setvalue.it_interval.tv_sec=3;
	setvalue.it_interval.tv_usec=0;
	setvalue.it_value.tv_sec=3;
	setvalue.it_value.tv_usec=0;
	setitimer(ITIMER_REAL,&setvalue,NULL);
	
	setvalue.it_interval.tv_sec=3;
	setvalue.it_interval.tv_usec=0;
	setvalue.it_value.tv_sec=3;
	setvalue.it_value.tv_usec=0;
	setitimer(ITIMER_VIRTUAL,&setvalue,NULL);

	setvalue.it_interval.tv_sec=3;
	setvalue.it_interval.tv_usec=0;
	setvalue.it_value.tv_sec=1;
	setvalue.it_value.tv_usec=0;
	setitimer(ITIMER_PROF,&setvalue,NULL);

	while(1)
	{
		struct itimerval value;
		getitimer(ITIMER_REAL,&value);
		printf("ITIMER_REAL: internal:%ds%dms,remain:%ds%dms\n",value.it_interval.tv_sec,value.it_interval.tv_usec,value.it_value.tv_sec,value.it_value.tv_usec);

		getitimer(ITIMER_VIRTUAL,&value);
		printf("ITIMER_VIRTUAL:internal:%ds%dms,remain:%ds%dms\n",value.it_interval.tv_sec,value.it_interval.tv_usec,value.it_value.tv_sec,value.it_value.tv_usec);

		getitimer(ITIMER_PROF,&value);
		printf("ITIMER_PROF: internal:%ds%dms,remain:%ds%dms\n\n",value.it_interval.tv_sec,value.it_interval.tv_usec,value.it_value.tv_sec,value.it_value.tv_usec);
		sleep(1);
	}
}


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

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


进程对信号的响应

进程可以通过三种方式来响应一个信号:(1)忽略信号,即对信号不做任何处理,其中,有两个信号不能忽略:SIGKILL及SIGSTOP;(2)捕捉信号。定义信号处理函数,当信号发生时,执行相应的处理函数;(3)执行缺省操作,Linux对每种信号都规定了默认操作。

信号的安装

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

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。

下面是一个使用signal安装信号的示例,该程序会在shell终端向其发送特定的信号:

/*************************************************************************
> File Name: kill_signal.c
> Author:SuooL 
> Mail:[email protected] || [email protected]
> Website:http://blog.csdn.net/suool | http://suool.net
> Created Time: 2014年08月09日 星期六 10时14分14秒
> Description: 使用signal安装信号示例
************************************************************************/

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void sig_usr(int sig);

int main(int argc,char *argv[])
{ 
    int i = 0;
    if(signal(SIGUSR1,sig_usr) == SIG_ERR)  // 安装处理信号,并指示该信号到来时执行的操作
    printf("Cannot catch SIGUSR1\n");       // 同时判断信号是否为SIGUSE1
    if (signal(SIGUSR2,sig_usr) == SIG_ERR) // 安装信号处理,判断信号是否为SIGUSE2 
    printf("Cannot catch SIGUSR2\n");
    while(1) 
    {
        printf("%2d\n", i);                 // 等待信号的到来
        pause(); 
        /* pause until signal handler
        has processed signal */
        i++;
    }
    return 0;
}

void sig_usr(int sig)                       // 信号处理函数
{
    if (sig == SIGUSR1)
    printf("Received SIGUSR1\n");
    else if (sig == SIGUSR2)
    printf("Received SIGUSR2\n");
    else
    printf("Undeclared signal %d\n", sig);
}

sigaction安装信号

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

sigaction函数用于改变进程接收到特定信号后的行为。该函数的第一个参数为信号的值,可以为除SIGKILL及SIGSTOP外的任何一个特定有效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误)。第二个参数是指向结构sigaction的一个实例的指针,在结构sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理;第三个参数oldact指向的对象用来保存原来对相应信号的处理,可指定oldact为NULL。如果把第二、第三个参数都设为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);      // 过时不用
                  }

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

2、由_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{
              ...
              }...
            ... ...
            }
      }
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;	
		}

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

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

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

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

下面是一个示例程序,七其完成的基本功能可以使用signal替代:

/*************************************************************************
	> File Name: sigaction.c
	> Author:SuooL 
	> Mail:[email protected] || [email protected]
	> Website:http://blog.csdn.net/suool | http://suool.net
	> Created Time: 2014年08月09日 星期六 10时30分14秒
	> Description: 使用sigaction函数的示例程序
 ************************************************************************/

#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
void myHandler(int sig);
int main(int argc,char *argv[])
{ 
  	struct sigaction act, oact;

  	act.sa_handler = myHandler;
  	sigemptyset(&act.sa_mask); /*initial. to empty mask*/
  	act.sa_flags = 0;
  	sigaction(SIGUSR1, &act, &oact); // 设置信号处理方式
    while (1) 
	{ 
		printf("Hello world.\n"); 
		pause();           // 等待信号发生
	}
  }

  void myHandler(int sig)       // 信号处理程序
  {
    printf("I got signal: %d.\n", sig);
  }
  // to end program, <Ctrl + \> to generate SIGQUIT


signal由于有系统漏洞,不建议使用。但是我在本机测试的时候没有发现该漏洞,估计是被修补了。

信号集及其操作

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

typedef struct {       // 此结构体占据32*32=1024bit,每个bit对应一个信号,val[0]的0-31位对应常用的1-31号
			unsigned long sig[_NSIG_WORDS];
			} sigset_t
信号集用来描述信号的集合,linux所支持的所有信号可以全部或部分的出现在信号集中,主要与信号阻塞相关函数配合使用。下面是为信号集操作定义的相关函数:

信号集用来描述信号的集合,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指向的信号集中。
sigprocmask()用来设置进程的屏蔽信号集,函数的使用方式请参阅相关文档.

信号集操作示例

1.信号集存储结构

/*************************************************************************
	> File Name: sig_setmember.c
	> Author:SuooL 
	> Mail:[email protected] || [email protected]
	> Website:http://blog.csdn.net/suool | http://suool.net
	> Created Time: 2014年08月09日 星期六 10时53分14秒
	> Description: 将某个信号添加到信号集后,该命令值的变化测试
 ************************************************************************/
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
int output(sigset_t set);

int main()
{
	sigset_t set;
	printf("after empty the set:\n");
	sigemptyset(&set);
	output(set);

	printf("after add signo=2:\n");
	sigaddset(&set,2);
	output(set);
	printf("after add signo=10:\n");
	sigaddset(&set,10);
	output(set);
	
	sigfillset(&set);
	printf("after  fill all:\n");
	output(set);
	return 0;
}

int output(sigset_t set)
{
	int i=0;
	for(i=0;i<1;i++)	//can test i<32
	{
		printf("0x%8x\n",set.__val[i]);
		if((i+1)%8==0)
			printf("\n");
	}
}


2.进程屏蔽信号应用示例

/*************************************************************************
> File Name: sig_setmember.c
> Author:SuooL 
> Mail:[email protected] || [email protected]
> Website:http://blog.csdn.net/suool | http://suool.net
> Created Time: 2014年08月09日 星期六 10时59分14秒
> Description: 进程屏蔽信号应用
************************************************************************/
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>

static void sig_quit(int);

int main(int argc,char *argv[])
{
    sigset_t    newmask, oldmask, pendmask;
    if (signal(SIGQUIT, sig_quit) == SIG_ERR)     // 安装信号处理函数
    { 
        perror("signal");
        exit(EXIT_FAILURE);
    }
    printf("install sig_quit\n");

    // Block SIGQUIT and save current signal mask.
    sigemptyset(&newmask);             // 清理所有信号集
    sigaddset(&newmask, SIGQUIT);       // 添加sigquit到信号集

    if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
    {                                    // 设置进程屏蔽newmask,原来值读到oldmask
     perror("signal");
     exit(EXIT_FAILURE);
    }
    printf("Block SIGQUIT,wait 15 second\n");
    sleep(15);   /* SIGQUIT here will remain pending */   // 等待15秒
    if (sigpending(&pendmask) < 0)            // 保存屏蔽信号
    { 
        perror("signal");
        exit(EXIT_FAILURE);
    }

    if (sigismember(&pendmask, SIGQUIT)) // 检测SIGQUIT是否在信号集
    printf("\nSIGQUIT pending\n");

    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)  // 替换进程掩码.即清除SIGQUIT
    { 
        perror("signal");
        exit(EXIT_FAILURE);
    }
    printf("SIGQUIT unblocked\n");

    sleep(15);   /* SIGQUIT here will terminate with core file */
    return 0;
}

static void sig_quit(int signo)             // 信号处理函数
{
    printf("caught SIGQUIT,the process will quit\n");
    if (signal(SIGQUIT, SIG_DFL) == SIG_ERR)    // 再次安装
    { 
        perror("signal");
        exit(EXIT_FAILURE);
    }
}

3.进程在捕获信号的过程中屏蔽信号

使用sigaction安装信号时,结构体struct sigaction成员sa_mask表示在处理信号过程中添加到当前屏蔽信号集中的信号.一下测试了程序在各个阶段输出了相应的信号集的值,从而查看屏蔽信号集的变化:

/*************************************************************************
> File Name: sigaction_setr.c
> Author:SuooL 
> Mail:[email protected] || [email protected]
> Website:http://blog.csdn.net/suool | http://suool.net
> Created Time: 2014年08月09日 星期六 11时13分14秒
> Description: 进程屏蔽信号应用
************************************************************************/
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>

int output(sigset_t set)
{
	printf("set.val[0]=%x\n",set.__val[0]);  // 输出信号集
}
void handler(int sig)        // sigalrm信号处理函数
{
	int i;
	sigset_t sysset;
	printf("\nin handler sig=%d\n",sig);
	sigprocmask(SIG_SETMASK,NULL,&sysset);
	output(sysset);			//in handler to see the process mask set
	printf("return\n");
}
int main(int argc,char *argv[])
{
	struct sigaction act;
	sigset_t set,sysset,newset;
	
	sigemptyset(&set);
	sigemptyset(&newset);
	sigaddset(&set,SIGUSR1);     // 将sigusr1添加到set中
	sigaddset(&newset,SIGUSR2);  // sigusr2添加到newset
	
	printf("\nadd SIGUSR1,the value of set:");
	output(set);
	
	printf("\nadd SIGUSR2,the value of newset:");
	output(newset);
	
	printf("\nafter set proc block set ,and then read to sysset\n");
	sigprocmask(SIG_SETMASK,&set,NULL);       // 设置当前信号屏蔽集
	sigprocmask(SIG_SETMASK,NULL,&sysset);    // 读取验证设置是否成功
	printf("system mask is:\n");
	output(sysset);

	printf("install SIGALRM,and the act.sa_mask is newset(SIGUSR2)\n");
	act.sa_handler=handler;
	act.sa_flags=0;
	act.sa_mask=newset;             // 处理过程中将newset添加到集合
	sigaction(SIGALRM,&act,NULL);   // 安装SIGALRM信号
	pause();                        // 等待信号
	
	printf("after exec ISR\n");
	sigemptyset(&sysset);
	sigprocmask(SIG_SETMASK,NULL,&sysset);   // 信号完成处理,重新读取值
	output(sysset);
}

等待信号

使用pause()函数和sigsuspend函数.

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

#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。

结构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 */
            };

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

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信号有意义 */
}

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

信号应用示例

基本功能

本示例程序创建了两个进程:

父进程执行文件复制操作(请使用M级别以上的文件),如果收到SIGUSR1信号,将打印当前的复制进度,因此父进程需要安装SIGUSR1信号.

子进程每隔一段时间(时间由ualrm函数产生的SIGALRM信号决定)向父进程发送SIGUSR1信号,因此子进程需要安装SIGALRM信号
.

源码及分析

/*************************************************************************
> File Name: sig_setmember.c
> Author:SuooL 
> Mail:[email protected] || [email protected]
> Website:http://blog.csdn.net/suool | http://suool.net
> Created Time: 2014年08月09日 星期六 11时13分14秒
> Description: 信号应用
************************************************************************/

#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>
#include<stdlib.h>

//the copy fie size must>M          // 请使用M级别的文件
int count;				//current copy number, 当前复制大小
int file_size;			//the file size 文件大小,因在中断无法传递普通参数,故用全局变量
void sig_alarm(int arg);        // 处理alarm信号
void sig_usr(int sig);          // 处理普通信号SIGUSR1

int main(int argc,char *argv[])
{
	pid_t pid;
	int i;
	int fd_src,fd_des;
	char buf[128];       //in order to infirm the problem, buf can set small 复制临时操作空间
	
	if(argc!=3)     // 参数检查
	{
		printf("check the format:comm src_file des_file\n");
		return -1;
	}
	if( ( fd_src=open(argv[1],O_RDONLY) )==-1 )
	{
		perror("open file src");     // 只读打开源文件
		exit(EXIT_FAILURE);
	}
	
	file_size=lseek(fd_src,0,SEEK_END);   // 获取文件大小
	lseek(fd_src,0,SEEK_SET);             // 重新设置读写文件头
	
	if( (fd_des=open(argv[2],O_RDWR|O_CREAT,0644) )==-1 ) 
	{                             // 以读写方式打开目标文件,如不存在则创建
		perror("open fd_fdes");
		exit(EXIT_FAILURE);
	}
	
	if( (pid=fork())==-1)     // 创建子进程
	{
		perror("fork");
		exit(EXIT_FAILURE);
	}
	else if(pid>0)           // 在父进程中,复制文件处于子进程信号请求
	{                        // 并在信号处理时打印复制进程
		signal(SIGUSR1,sig_usr);    // 向父进程安装SIGUSR1信号
		do
		{
			memset(buf,'\0',128);
			if((i=read(fd_src,buf,1))==-1)	//the copy number may modify
			{                       // 复制数据,为验证结果可以改变每次复制的大小
				perror("read");
				exit(EXIT_FAILURE);
			}
			else if(i==0)  // 复制完毕,向子进程发送SIGINT信号
			{              // 终止子进程
				kill(pid,SIGINT);
				break;
			}
			else
			{
				if(write(fd_des,buf,i)==-1)  // 否则执行复制操作
				{
					perror("write");
					exit(EXIT_FAILURE);
				}
				count+=i;      // 更新已经复制的大小
				//usleep(1);
			}
		}while(i!=0);
		
		wait(pid,NULL,0);      // 等待子进程退出
		exit(EXIT_SUCCESS);
	}

	else if(pid==0)      // 子进程中每隔一段时发送信号给父进程,请求父进程复制进度
	{
		usleep(1);	//wait for parent to install signal
		
		signal(SIGALRM,sig_alarm);  // 安装信号
		ualarm(1,1);	//if alarm ,in sig_alarm function to install again
		while(1)              // 一直执行
		{
			;
		}
		exit(EXIT_SUCCESS);
	}

}

void sig_alarm(int arg)
{
	//alarm(1);			//if alarm may add this line
	kill(getppid(),SIGUSR1);       //在子进程的SIGQLRM信号处理中向父进程发送SIGUSR1信号
}

void sig_usr(int sig)      // 父进程的SIGUSR信号处理函数
{
	float i;
	i=(float)count/(float)file_size;	//得出复制进程
	printf("curent over :%0.0f%%\n",i*100);
}


NEXT

System V 进程通信

多线程编程


转载请注明出处: http://blog.csdn.net/suool/article/details/38453333

Linux程序设计学习笔记——异步信号处理机制,古老的榕树,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。