进程间通信--信号

来源:互联网 发布:windows下安装ansible 编辑:程序博客网 时间:2024/04/19 12:00

1、信号的作用

(1) 使用它来通知一个或者多个进程异步事件的发生

 

2、信号的发送方向

信号不但能从内核发往一个进程,也能从一个进程发往另一个进程。即:

(1) 内核-->进程

(2) 进程-->另一个进程

3、信号的使用范围

由信号特点决定:不能用信号来作进程间的直接数据传送,而把它用作对非正常情况的处理。

4、linux中信号的种类

信号类型在linux系统库中 bits/signum.h 中有定义

 SIGHUP           内核-->进程。   当终止一个终端时,内核就把这一种信号发送给该终端所控制的所有进程

 SIGINT             内核-->进程。   当用户按了中断键(一般为Ctrl+C后),内核就向与该终端关联的所有进程发送这种信号

 SIGQUIT          内核-->进程。   当用户按了退出键时(一般为Ctrl+\后),内核就发出这种信号

 SIGILL              内核-->进程。   当一个进程企图执行一条非法指令时,内核就会发出这种信号

 SIGTRAP                                     由调试程序使用的专用信号

 SIGFPE            内核-->进程。   当产生浮点错误时(比如溢出),内核就会发出这种信号

 SIGKILL           进程-->进程。(内核偶尔也会发出这种信号)这是一个相当特殊的信号,它从一个进程发送到另一个进程,使接收到该信号的进程终止。SIGKILL特点:它不能被忽略和捕捉,只能通过用户定义的响应中断处理程序而处理该信号。因为所有的信号能被忽略和捕捉,所以只有这种信号能绝对保证终止一个进程。

 SIGALRM         内核-->进程。   当一个定时起到的时候,内核就向该进程发出这种信号

 SIGTERM         进程-->进程。   这种信号是有系统提供给普通用户使用,按照规定,它被用来终止一个进程

 SIGTOP                                      这种信号使进程暂时中止运行,系统控制权转回正在等待运行的下一个进程

 SIGUSR1 和 SIGUSR2           进程-->进程。和SIGTERM一样,这两种信号不是由内核发出的,可以用于用户做任何事情。

 SIGCHLD       子进程结束信号。unix中用它来实现系统调用 exit()和 wait()。执行exit()函数时,就向父进程发送 SIGCHLD 信号。如果父进程正在执行 wait() ,则它被唤醒;若果父进程不是在执行wait() ,则父进程不会捕捉到SIGCHLD信号,因此信号不起作用。

 

5、信号的处理函数

这些信号定义在linux系统库 signal.h 文件中,函数如下:

 int signal(int sig,_sighandler _t handler);

(1) 第1个参数: sig --> 指明了所要处理的信号类型,他可以取除了 SIGKILL 和 SIGTOP 外的任何一种信号。

(2)第2个参数:handler-->指向一个对信号sig处理的操作函数。当进程一旦接收到sig的信号时,就立刻执行handler所指定的函数,不管进程正在执行的那一部分程序。handler可以取值为:SIG_IGN 和 SIG_ DEL.

SIG_IGN:  这个符号表示武略此信号

SIG_DEL:  这个信号表示恢复系统对信号的默认处理

(3)函数返回值: 如果调用成功,函数返回进程被中断的那一点继续执行;若果失败,函数返回值是 SIG_ERR。

例子1:

进程忽略 SIGINT 信号

#include <stdlib.h>#include <stdio.h>#include <signal.h>int main(){signal(SIGINT,SIG_IGN); //告诉进程将SIGINT信号忽略printf("xixi\n);sleep(10);printf("end\n");return;}//如果在程序中需要重新恢复系统对该信号的缺省处理,就是用下面的语句:signal(SIGINT,SIG_DEL);

在linux中常常利用SIG_INT 和 SIG_DEL 屏蔽SIGINT 和 SIGQUIT 来保证执行重要任务的程序不被中断。

例2:

#include <stdlib.h>#include <stdio.h>#include <signal.h>int catch(int sig)int main(){signal(SIGINT,catch);printf("xixi\n");sleep(10);return;}int catch(int sig){printf("Catch succeed!\n");return 1;}//当程序中按下中断键(Ctrl+C),函数catch就被执行。若果我们希望一个进程被信号终止前能够完成一些处理工作,如删除工作中使用的临时文件等,就可以设计一个信号处理函数完成工作。例如:int catch(int sig){printf("Catch succeed!\n");exit(1);}

 

思考:这里,在子程序中使用exit(1) 和 return 返回主程序的时候有什么区别呢????

 

例3:
signal()返回原先与指定信号相关联的处理函数,这样,我们就可以保存和恢复原来对指定信号的
处理动作。下面代码说明这一技术:

int (*oldptr)newcath();  //设定SIGINT的关联,同时保存原来的关联oldptr=signal(SIGINT,newcatch);  /*工作代码段*/............/*恢复原来的关联*/signal(SIGINT,oldptr);

 6、信号和系统的调用关系

当一个进程正在执行一个系统调用的时候,如果向该进程发送一个信号,那么对于大多数系统调用而言,这个信号在系统调用完成之前将不起作用,因为这些系统调用不能被信号打断。但是有少数几个系统调用能被信号打断。例如:wait()、pause()以及read()、write()、open()等。如果一个系用调用被打断,它就返回-1。

下面代码说明这种情况:

if(write(tfd,buf,SIZE)<0){if(errno == EINTR){warn("Write interrupt");......}}


7、信号的复位

在linux中,当一个信号的信号处理函数执行时,如果进程又接收到了信号,则进行判断:

如果进程执行过程中,接收同一类型的信号,则信号处理函数不会被中断,直到信号处理函数执行完毕再中心调用相应的处理函数。

如果进程执行过程中,接收不同类型的信号,则中断信号处理函数,执行与之对应的信号处理函数,直到信号处理完毕,返回原来信号处理的函数。

 

(1)、进程执行过程当中,接收同一类型的信号

#include <signal.h>int interrupt(){printf("Interrupt called\n");sleep(3);printf("Interrupt Func Ended\n");}int main(){signal(SIGINT,interrupt);printf("Interrrupt set for SIGINT\n");sleep(10);printf("program normal ended\n");return;}执行结果interrupt set for SIGINT<ctrl+c>Interrupt called <ctrl+c>Interrupt Func Endedprogram normal ended

(2)、进程执行过程当中,接收不同类型的信号

#include <signal.h>int interrupt(){printf("Interrupt called\n");sleep(3);printf("Interrupt Func Ended\n");}int catchquit(){printf("Quit called\n");sleep(3);printf("Quit ended\n);}int main(){signal(SIGINT,interrupt);signal(SIGQUIT,catchquit);printf("Interrupt set for SIGINT\n");sleep(10);printf("Program NORMAL ended\n");return;}执行结果:Interrupt set for SIGINT<ctrl+c>Interrupt calledQuit calledQuit calledInterrupt Func EndedProgram NORMAL ended

8、在进程间发送信号

一个进程通过对 signal() 的调用来处理其它进程发送来的信号。同时,一个进程也可以向其它的进程发送信号。这一操作由系统调用kill()来完成kill()函数在linux系统库signal.h中的函数声明:

int kill(pid_t pid,int sig);

例子1:

下面是一个使用kill()调用的例子。这个程序建立连个进程,并通过向对方发送信号SIGUSR1来实现他们之间的同步。这两个进程都处于一个死循环,在对方发送信号之前,都处于暂停等待中。这是通过pause()来实现的,它能够使一个程序暂停,直到一个信号到达,然后输出信息,并用kill发送一个信号给对方。当用户按了按键中断,这两个进程都将终止。

#include <signal.h>int ntimes=0;P_action(){printf("parent caught signal #%d\n",++ntimes);}c_action(){printf("child caught signal #%d\n",++ntimes);main(){int pid,ppid;int p_action,c_action;signal(SIGUSR1,P_action);switch(pid = fock()){case -1:perror("synchro");exit(1);case 0:signal(SIGUSR1,c_action);ppid = getppid();for(;;){sleep(1);kill(ppid,SIGUSR1);pause();}break;default:for(;;){pause();sleep(1);kill(pid,SIGUSR1);}}}程序运行结果是:patent caught signal #1child caught signal #1patent caught signal #2child caught signal #2patent caught signal #3child caught signal #3patent caught signal #4child caught signal #4<ctrl+c>

(1)、为什么父子进程同时打印出同一个“数字”????

答:通过向对方发送信号SIGUSR1来实现父子进程同步,所以父子进程是同步运行。

(2)、为什么父子进程在按下<ctrl+c> 后同时终止呢 ??

答:按下 <ctrl+c> 后,父子进程同时发送kill信号给对方,所以父子进程同时终止

(3)、kill一般用于父子进程之间??

答:因为调用kill()的进程需要知道信号发往的进程的标识符,所以这种信号的发送通常在关系密切的进程之间进行,比如父子进程。


9、信号和系统调用alarm()和pause()之间的关系

alarm()函数:

(1)、alarm()函数在系统调用linux系统库unistd.h中的函数中声明如下:

unsigned int alarm(unsigned int seconds);

(2)、alarm()函数在fork()函数调用之前,在子进程中有效;alarm()函数在fork()函数调用之后,在子进程中失效;

(3)、alarm()使用的基本方法:

先调用alarm()设置报警时钟,然后进程做某一项工作。如果进程在规定时间以内完成这一工作,就再调用alarm(0)时报警时钟失效。如果在规定时间内,未能完成这一项工作,系统内核就发出SIGALARM信号中断进程,执行信号处理函数。

#include <stdio.h>#include <signal.h>#define MAXTRIES 5#define LINESIZE 100#define TIMEOUT 5#define BELL '\007'#define TRUE 1#define FALSE 0static int time_out;static char inputline[LINESIZE];char *quickreply(char *prompt);int main(){printf("%s\n",quickreply("input"));return;}char *quickreply(char *prompt){int (*was)(),catch(),ntries;char *answer;was = signal(SIGALRM,catch);   //设定捕捉SIGALRM的关联函数,并且保存原有关联for(ntries=0;ntries<MAXTRIES;ntries++){time_out = FALSE;printf("\n%S>",prompt);alarm(TIMEOUT);        //设定定时器answer = gets(inputline);alarm(0);             //关闭定时器if(!time_out)   // 设定定时器后,信号一直在等待(超出定时器时间时,信号有内核发出,time_out变成true,继续下一次循环,最多循环5次),直到有输入后才退出(time_out在没有信号触发,一直是false)。{break;}}signal(SIGALRM,was);return(time_out?((char*)0):answer);}catch(){time_out = TRUE;putchar(BELL);}

 pause()函数:

(1)、pause()函数在linux系统函数库unistd.h中的函数声明如下

int pause(void)

(2)、作用:系统调用pause()能够使调用进程暂停执行,直到接收到某种信号为止

 

#include <stdio.h>#include <signal.h>#define FALSE 0#define TRUE 1#define BELLS "\007\007\007"int alarm_flag = FALSE;setflag(){alarm_flag = TRUE;}int main(){int nsecs;int i;if((nsecs = atoi(argv[1]*60) <= 0){fprintf(stderr,"Invalid time\n");exit(2);}signal(SIGALRM,setflag);  //设定信号关联动作alarm(nsecs);   //设定定时器pause();   //调用等待信号if(alarm_flag){printf(BELLS);for(i=2;i<argc;i++){printf("%s\n",argv[i]);}}exit(0);}

 

 10、信号和系统调用 setjmp() 和 longjmp() 函数之间的关系

(1)、在linux系统库函数中 setjmp.h 中调用

int setjmp(jmp_buf env);

void longjmp(jmp_buf env,int val);

(2)、 setjmp()函数-->能够保存程序中的当前位置(是通过保存堆栈环境实现的)

        longjmp()函数-->能够把控制转回到被保存的位置

 #include <stdio.h>

#include <setjmp.h>

#include <signal.h>


jmp_buf position;

int main()

{

int goback();

.....

.....

setjmp(position);    //保存当前的堆栈环境

signal(SIGINT,goback);

domenu();

.....

.....

}

int goback()

{

fprintf(stderr,"\nInterrupted\n");

longjmp(position,1);     //跳转回被保存的断点

}