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

Posted lytwajue

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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时的情况,对于哪些进程将接受信号。各种版本号说法不一,事实上非常easy,參阅内核源代码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); 
假设该函数原型不easy理解的话,能够參考以下的分解方式来理解:
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:1020935219@qq.com || hu1020935219@gmail.com
> 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)。

注:非常多文献在阐述该标志位时都觉得,假设设置了该标志位,就必须定义三參数信号处理函数。实际不是这种,验证方法非常easy:自己实现一个单一參数信号处理函数,并在程序中设置该标志位。能够察看程序的执行结果。实际上,能够把该标志位看成信号是否传递參数的开关。假设设置该位,则传递參数。否则,不传递參数。

以下是一个演示样例程序。七其完毕的基本功能能够使用signal替代:

/*************************************************************************
	> File Name: sigaction.c
	> Author:SuooL 
	> Mail:1020935219@qq.com || hu1020935219@gmail.com
	> 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:1020935219@qq.com || hu1020935219@gmail.com
	> 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:1020935219@qq.com || hu1020935219@gmail.com
> 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:1020935219@qq.com || hu1020935219@gmail.com
> 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:1020935219@qq.com || hu1020935219@gmail.com
> 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程序设计学习笔记——异步信号处理机制的主要内容,如果未能解决你的问题,请参考以下文章

(笔记)Linux内核学习之中断推后处理机制

《javascript高级程序设计》学习笔记 | 11.1.异步编程

python学习笔记

安卓学习笔记之AsyncTask机制浅析

KOA学习笔记

Linux之异步通知机制分析