Linux-IPC之信号

来源:互联网 发布:杨辉三角c语言程序代码 编辑:程序博客网 时间:2024/06/05 21:10

一、信号概念

信号是进程在运行过程中,由自身产生或由进程外部发过来的消息(事件)。信号是硬件中断的软件模拟(软中断)。每个信号用一个整型常量宏表示,以SIG开头,比如SIGCHLD、SIGINT等,它们在系统头文件<signal.h>中定义,也可以通过在shell下键入kill –l查看信号列表,或者键入man 7 signal查看更详细的说明。
信号的生成来自内核,让内核生成信号的请求来自3个地方:
1.用户:用户能够通过输入CTRL+c、Ctrl+\,或者是终端驱动程序分配给信号控制字符的其他任何键来请求内核产生信号;
2.内核:当进程执行出错时,内核会给进程发送一个信号,例如非法段存取(内存访问违规)、浮点数溢出等;
3.进程:一个进程可以通过系统调用kill给另一个进程发送信号,一个进程可以通过信号和另外一个进程进行通信。
由进程的某个操作产生的信号称为同步信号(synchronous signals),例如除0;由像用户击键这样的进程外部事件产生的信号叫做异步信号(asynchronous signals)。
进程接收到信号以后,可以有如下3种选择进行处理:
1.接收默认处理:接收默认处理的进程通常会导致进程本身消亡。例如连接到终端的进程,用户按下CTRL+c,将导致内核向进程发送一个SIGINT的信号,进程如果不对该信号做特殊的处理,系统将采用默认的方式处理该信号,即终止进程的执行; signal(SIGINT,SIG_DFL);
2.忽略信号:进程可以通过代码,显示地忽略某个信号的处理,例如:signal(SIGINT,SIG_IGN);但是某些信号是不能被忽略的,例如9号信号;
3.捕捉信号并处理:进程可以事先注册信号处理函数,当接收到信号时,由信号处理函数自动捕捉并且处理信号。

有两个信号既不能被忽略也不能被捕捉,它们是SIGKILL和SIGSTOP。即进程接收到这两个信号后,只能接受系统的默认处理,即终止进程。SIGSTOP是暂停进程。

二、signal信号处理机制

可以用函数signal注册一个信号捕捉函数。原型为:
#include <signal.h>typedef void (*sighandler_t)(int);  //函数指针sighandler_t  signal(int signum, sighandler_t handler);

signal的第1个参数signum表示要捕捉的信号,第2个参数是个函数指针,表示要对该信号进行捕捉的函数,该参数也可以是SIG_DFL(表示交由系统缺省处理,相当于白注册了)或SIG_IGN(表示忽略掉该信号而不做任何处理)。signal如果调用成功,返回以前该信号的处理函数的地址,否则返回SIG_ERR。
sighandler_t是信号捕捉函数,由signal函数注册,注册以后,在整个进程运行过程中均有效,并且对不同的信号可以注册同一个信号捕捉函数。该函数只有一个整型参数,表示信号值。

示例:
//捕捉终端Ctrl+c产生的SIGINT信号#include <stdio.h>#include <signal.h>#include <stdlib.h>int main(){typedef void (*sigp)(int);sigp p;p=signal(SIGINT,SIG_IGN);if(SIG_ERR==p){perror("signal");return -1;}while(1);return 0;}

运行结果:

捕捉Ctrl+c产生的信号,所以Ctrl+c无法终止进程,Ctrl+\结束进程

//捕捉终端Ctrl+\产生的SIGQUIT信号,signal第二个参数直接传函数名#include <stdio.h>#include <signal.h>void sig(int signum){printf("signum is %d\n",signum);}int main(){signal(SIGQUIT,sig);while(1);return 01;}

运行结果:

捕捉ctrl+c产生的SIGQUIT信号,输出SIGQUIT的值,Ctrl+c结束进程

累计发送只会执行两次:


//多个信号#include <stdio.h>#include <signal.h>void sig(int signum){printf("signum is %d\n",signum);sleep(5);printf("after sleep , the signum is %d\n",signum);}int main(){signal(SIGQUIT,sig);//Ctrl+\产生的信号signal(SIGINT,sig);//Ctrl+c产生的信号while(1);return 01;}

运行结果:


结论:
1、自己不能打断自己
2、可以互相打断,不能累计

在signal处理机制下,还有许多特殊情况需要考虑:
1、注册一个信号处理函数,并且处理完毕一个信号之后,是否需要重新注册,才能够捕捉下一个信号;(不需要)
2、如果信号处理函数正在处理信号,并且还没有处理完毕时,又发生了一个同类型的信号,这时该怎么处理;(挨着执行),后续相同信号忽略(会多执行一次)。
3、如果信号处理函数正在处理信号,并且还没有处理完毕时,又发生了一个不同类型的信号,这时该怎么处理;(跳转去执行另一个信号,之后再执行剩下的没有处理完的信号)
4、如果程序阻塞在一个系统调用(如read(...))时,发生了一个信号,这时是让系统调用返回错误再接着进入信号处理函数,还是先跳转到信号处理函数,等信号处理完毕后,系统调用再返回。


示例:
//signla_read.c#include <stdio.h>#include <signal.h>void sig(int signum){printf("signum is %d\n",signum);sleep(5);printf("after sleep , the signum is %d\n",signum);}int main(){signal(SIGINT,sig);int ret;char buf[10]={0};ret=read(0,buf,sizeof(buf));printf("buf is %s\n",buf);return 0;}

运行结果:


结论:
如果不输入字符串,程序一直阻塞,但可以捕捉信号

三、sigaction信号处理注册

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

sigaction也用于注册一个信号处理函数
参数signum为需要捕捉的信号
参数act是一个结构体,里面包含信号处理函数地址、处理方式等信息
参数oldact是一个传出参数,sigaction函数调用成功后,oldact里面包含以前对signum的处理方式的信息,通常为NULL
如果函数调用成功,将返回0,否则返回-1

结构体 struct sigaction(注意名称与函数sigaction相同)的原型为:
struct sigaction {void (*sa_handler)(int);//老类型的信号处理函数指针,不常用void (*sa_sigaction)(int, siginfo_t *, void *);//新类型的信号处理函数指针sigset_t sa_mask;//将要被阻塞的信号集合int sa_flags;//信号处理方式掩码 (SA_SIGINFO)void (*sa_restorer)(void);        //保留,不常使用};

该结构体的各字段含义及使用方式:
1、字段sa_handler是一个函数指针,用于指向原型为void handler(int)的信号处理函数地址,即老类型的信号处理函数(如果用这个再将sa_flags = 0,就等同于signal()函数)
2、字段sa_sigaction也是一个函数指针,用于指向原型为:
void handler(int iSignNum, siginfo_t *pSignInfo, void *pReserved);
的信号处理函数,即新类型的信号处理函数
该函数的三个参数含义为:
iSignNum:传入的信号
pSignInfo:与该信号相关的一些信息,它是个结构体
pReserved:保留,现没用,通常为NULL
3、字段sa_handler和sa_sigaction只应该有一个生效,如果想采用老的信号处理机制,就应该让sa_handler指向正确的信号处理函数,并且让字段sa_flags为0;否则应该让sa_sigaction指向正确的信号处理函数,并且让字段sa_flags包含SA_SIGINFO选项
4、字段sa_mask是一个包含信号集合的结构体,该结构体内的信号表示在进行信号处理时,将要被阻塞的信号。针对sigset_t结构体,有一组专门的函数对它进行处理,它们是:

#include <signal.h>int sigemptyset(sigset_t *set);//清空信号集合setint sigfillset(sigset_t *set);//将所有信号填充进set中int sigaddset(sigset_t *set, int signum);//往set中添加信号signumint sigdelset(sigset_t *set, int signum);//从set中移除信号signumint sigismember(const sigset_t *set, int signum);//判断signum是否包含在set中(是:返回1,否:0)int sigpending(sigset_t *set);//将被阻塞的信号集合由参数set指针返回(挂起信号)

其中,对于函数sigismember而言,如果signum在set集中,则返回1;不在,则返回0;出错时返回-1.其他的函数都是成功返回0,失败返回-1.

5、字段sa_flags是一组掩码的合成值,指示信号处理时所应该采取的一些行为,各掩码的含义为:

掩码

描述

SA_RESETHAND

处理完毕要捕捉的信号后,将自动撤消信号处理函数的注册,即必须再重新注册信号处理函数,才能继续处理接下来产生的信号。该选项不符合一般的信号处理流程,现已经被废弃。

SA_NODEFER

在处理信号时,如果又发生了其它的信号,则立即进入其它信号的处理,等其它信号处理完毕后,再继续处理当前的信号,即递规地处理。如果sa_flags包含了该掩码,则结构体sigaction的sa_mask将无效!(不常用)不断重入,次数不丢失

SA_RESTART

如果在发生信号时,程序正阻塞在某个系统调用,例如调用read()函数,则在处理完毕信号后,接着从阻塞的系统返回。如果不指定该参数,中断处理完毕之后,read函数读取失败。

SA_SIGINFO

指示结构体的信号处理函数指针是哪个有效,如果sa_flags包含该掩码,则sa_sigaction指针有效,否则是sa_handler指针有效。(常用)


//SA_SIGINFO获取Ctrl+c信号#include <stdio.h>#include <signal.h>#include <strings.h>#include <stdlib.h>void siga(int signum,siginfo_t *p,void* p1){printf("the signum is %d\n",signum);}int main(){struct sigaction act;bzero(&act,sizeof(act));act.sa_sigaction=siga;act.sa_flags=SA_SIGINFO;sigemptyset(&act.sa_mask);int ret;ret=sigaction(SIGINT,&act,NULL);if(-1==ret){perror("sigactoin");return -1;}while(1);return 0;}

运行结果:


//SA_RESTART//获取信号Ctrl+c后read函数会继续读取#include <stdio.h>#include <signal.h>#include <strings.h>#include <stdlib.h>#include <unistd.h>void siga(int signum,siginfo_t *p,void* p1){printf("the signum is %d\n",signum);}int main(){struct sigaction act;bzero(&act,sizeof(act));act.sa_sigaction=siga;act.sa_flags=SA_SIGINFO|SA_RESTART;sigemptyset(&act.sa_mask);int ret;ret=sigaction(SIGINT,&act,NULL);if(-1==ret){perror("sigactoin");return -1;}char buf[16];bzero(buf,sizeof(buf));read(0,buf,sizeof(buf));printf("the buf is %s\n",buf);return 0;}

运行结果:


//SA_NODEFER打断#include <stdio.h>#include <signal.h>#include <strings.h>#include <stdlib.h>void siga(int signum,siginfo_t *p,void* p1){printf("the signum is %d\n",signum);sleep(3);printf("after sleep signum is %d\n",signum);}int main(){struct sigaction act;bzero(&act,sizeof(act));act.sa_sigaction=siga;act.sa_flags=SA_SIGINFO|SA_NODEFER;sigemptyset(&act.sa_mask);int ret;ret=sigaction(SIGINT,&act,NULL);if(-1==ret){perror("sigactoin_SIGINT");return -1;}ret=sigaction(SIGQUIT,&act,NULL);if(-1==ret){perror("sigactoin_SIGQUIT");return -1;}while(1);return 0;}

运行结果:


用kill -9结束进程

//只获取一次信号#include <stdio.h>#include <signal.h>#include <strings.h>#include <stdlib.h>void siga(int signum,siginfo_t *p,void* p1){printf("the signum is %d\n",signum);}int main(){struct sigaction act;bzero(&act,sizeof(act));act.sa_sigaction=siga;act.sa_flags=SA_SIGINFO|SA_RESETHAND;sigemptyset(&act.sa_mask);int ret;ret=sigaction(SIGINT,&act,NULL);if(-1==ret){perror("sigactoin");return -1;}while(1);return 0;}

运行结果:


//将信号SIGQUIT加入信号阻塞队列,不可打断#include <stdio.h>#include <signal.h>#include <strings.h>#include <stdlib.h>void siga(int signum,siginfo_t *p,void* p1){printf("the signum is %d\n",signum);sleep(3);printf("after sleep signum is %d\n",signum);}int main(){struct sigaction act;bzero(&act,sizeof(act));act.sa_sigaction=siga;act.sa_flags=SA_SIGINFO|SA_NODEFER;sigemptyset(&act.sa_mask);int ret;ret=sigaddset(&act.sa_mask,SIGQUIT);if(-1==ret){perror("sigaddset");return -1;}ret=sigaction(SIGINT,&act,NULL);if(-1==ret){perror("sigactoin_SIGINT");return -1;}while(1);return 0;}

运行结果:


//把所有信号都放入阻塞队列#include <stdio.h>#include <signal.h>#include <strings.h>#include <stdlib.h>void siga(int signum,siginfo_t *p,void* p1){printf("the signum is %d\n",signum);sleep(3);printf("after sleep signum is %d\n",signum);}int main(){struct sigaction act;bzero(&act,sizeof(act));act.sa_sigaction=siga;act.sa_flags=SA_SIGINFO|SA_NODEFER;sigemptyset(&act.sa_mask);int ret;ret=sigfillset(&act.sa_mask);if(-1==ret){perror("sigaddset");return -1;}ret=sigaction(SIGINT,&act,NULL);if(-1==ret){perror("sigactoin_SIGINT");return -1;}while(1);return 0;}

//获取挂起的信号#include <stdio.h>#include <signal.h>#include <strings.h>#include <stdlib.h>void siga(int signum,siginfo_t *p,void* p1){printf("the signum is %d\n",signum);sleep(3);sigset_t set;sigemptyset(&set);int ret;ret=sigpending(&set);if(-1==ret){perror("sigpending");return;}if(1==sigismember(&set,SIGINT)){printf("the SIGINT is pending\n");}if(1==sigismember(&set,SIGQUIT)){printf("the SIGQUIT is pending\n");}printf("after sleep signum is %d\n",signum);}int main(){struct sigaction act;bzero(&act,sizeof(act));act.sa_sigaction=siga;act.sa_flags=SA_SIGINFO|SA_NODEFER;sigemptyset(&act.sa_mask);int ret;ret=sigfillset(&act.sa_mask);if(-1==ret){perror("sigaddset");return -1;}ret=sigaction(SIGINT,&act,NULL);//设置信号处理行为if(-1==ret){perror("sigactoin_SIGINT");return -1;}while(1);return 0;}

运行结果:


//获取信息#include <stdio.h>#include <signal.h>#include <strings.h>#include <stdlib.h>void siga(int signum,siginfo_t *p,void* p1){printf("the signum is %d\n",signum);printf("the send process is %d\n",p->si_pid);printf("the send uid is %d\n",p->si_uid);}int main(){struct sigaction act;bzero(&act,sizeof(act));act.sa_sigaction=siga;act.sa_flags=SA_SIGINFO;sigemptyset(&act.sa_mask);int ret;ret=sigaction(SIGINT,&act,NULL);if(-1==ret){perror("sigactoin");return -1;}while(1);return 0;}

运行结果:


需要kill -2 进程pid

四、sigprocmask信号阻塞


函数sigaction中设置的被阻塞信号集合只是针对于要处理的信号,例如:

struct sigaction act;sigemptyset(&act.sa_mask);sigaddset(&act.sa_mask,SIGQUIT);sigaction(SIGINT,&act,NULL);

表示只有在处理信号SIGINT时,才阻塞信号SIGQUIT;(重点区分)
函数sigprocmask是全程阻塞,在sigprocmask中设置了阻塞集合后,被阻塞的信号将不能再被信号处理函数捕捉,直到重新设置阻塞信号集合。
原型:
#include <signal.h>int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

参数how的值为如下3者之一:
a:SIG_BLOCK ,将参数2的信号集合添加到进程原有的阻塞信号集合中
b:SIG_UNBLOCK ,从进程原有的阻塞信号集合移除参数2中包含的信号
c:SIG_SETMASK,重新设置进程的阻塞信号集为参数2的信号集
参数set为阻塞信号集
参数oldset是传出参数,存放进程原有的信号集,通常为NULL

//全程阻塞SIGINT信号和SIGQUIT信号#include <stdio.h>#include <signal.h>#include <stdlib.h>int main(){sigset_t set;sigemptyset(&set);int ret;ret=sigaddset(&set,SIGINT);if(-1==ret){perror("sigaddset");return -1;}ret=sigaddset(&set,SIGQUIT);if(-1==ret){perror("sigaddset");return -1;}ret=sigprocmask(SIG_BLOCK,&set,NULL);if(-1==ret){perror("sigprocmask");return -1;}while(1);return 0;}

运行结果:


//SIG_UNBLOCK移除set中的信号#include <stdio.h>#include <signal.h>#include <stdlib.h>int main(){sigset_t set;sigemptyset(&set);int ret;ret=sigaddset(&set,SIGINT);if(-1==ret){perror("sigaddset");return -1;}ret=sigaddset(&set,SIGQUIT);if(-1==ret){perror("sigaddset");return -1;}ret=sigprocmask(SIG_BLOCK,&set,NULL);if(-1==ret){perror("sigprocmask");return -1;}ret=sigprocmask(SIG_UNBLOCK,&set,NULL);if(-1==ret){perror("sigprocmask");return -1;}while(1);return 0;}

结果:Ctrl+c和Ctrl+\都会结束进程

//2号信号全程阻塞#include <stdio.h>#include <signal.h>#include <stdlib.h>int main(){sigset_t set;sigemptyset(&set);int ret;ret=sigaddset(&set,SIGINT);if(-1==ret){perror("sigaddset");return -1;}ret=sigprocmask(SIG_BLOCK,&set,NULL);if(-1==ret){perror("sigprocmask");return -1;}sleep(3);sigemptyset(&set);sigpending(&set);if(1==sigismember(&set,SIGINT)){printf("the SIGINT is pending\n");}while(1);return 0;}

运行结果:


五、kill信号发送函数


原型:
#include <sys/types.h>#include <signal.h>int kill(pid_t pid, int sig);

参数pid为将要接受信号的进程的pid,可以通过getpid()函数获得来给自身发送信号,还可以发送信号给指定的进程,此时pid有如下描述:
pid > 0 将信号发给ID为pid的进程
pid == 0 将信号发送给与发送进程属于同一个进程组的所有进程
pid < 0 将信号发送给进程组ID等于pid绝对值的所有进程
pid == -1 将信号发送给该进程有权限发送的系统里的所有进程
参数sig为要发送的信号
如果成功,返回0,否则为-1

//向自己发送2号信号#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <signal.h>void siga(int signum){printf("the signum is %d\n",signum);}int main(){signal(SIGINT,siga);while(1){if(getchar()==EOF)kill(getpid(),SIGINT);}return 0;}



//向指定的进程发送信号//kill_1.c#include <stdio.h>int main(){while(1);return 0;}//kill_2.c#include <stdio.h>#include <signal.h>#include <sys/types.h>#include <unistd.h>int main(int argc,char* argv[]){int pid=atoi(argv[1]);kill(pid,SIGINT);return 0;}

六、计时器与信号


1、睡眠函数


Linux下有两个睡眠函数,原型为:

#include <unistd.h>unsigned int sleep(unsigned int seconds);void usleep(unsigned long usec);

函数sleep让进程睡眠seconds秒,函数usleep让进程睡眠usec微秒。
sleep睡眠函数内部是用信号机制进行处理的,用到的函数有:

#include <unistd.h>unsigned int alarm(unsigned int seconds);//告知自身进程,要进程在seconds秒后自动产生一个SIGALRM的信号int pause(void); //将自身进程挂起,直到有信号发生时才从pause返回

//睡眠2秒显示信号id号#include <stdio.h>#include <stdlib.h>#include <signal.h>#include <sys/types.h>#include <unistd.h>void sig(int p){printf("the signum is %d\n",p);}int main(){signal(SIGALRM,sig);alarm(3);while(1);return 0;}

//pause.c遇到信号就返回#include <stdio.h>#include <stdlib.h>#include <signal.h>#include <sys/types.h>#include <unistd.h>void sig(int p){printf("the signum is %d\n",p);}int main(){signal(SIGALRM,sig);pause();return 0;}

2、时钟处理


Linux为每个进程维护3个计时器,分别是真实计时器、虚拟计时器和实用计时器。
1.真实计时器计算的是程序运行的实际时间;---直接
2.虚拟计时器计算的是程序运行在用户态时所消耗的时间(可认为是实际时间减掉(系统调用和程序睡眠所消耗)的时间);---需要了解内核
3.实用计时器计算的是程序处于用户态和处于内核态所消耗的时间之和。---常用
例如:有一程序运行,在用户态运行了5秒,在内核态运行了6秒,还睡眠了7秒,则真实计算器计算的结果是18秒,虚拟计时器计算的是5秒,实用计时器计算的是11秒。
用指定的初始间隔和重复间隔时间为进程设定好一个计时器后,该计时器就会定时地向进程发送时钟信号。3个计时器发送的时钟信号分别为:SIGALRM,SIGVTALRM和SIGPROF。
用到的函数与数据结构:

//1.#include <sys/time.h>int getitimer(int which, struct itimerval *value);    //获取计时器的设置

参数which指定哪个计时器,可选项为ITIMER_REAL(真实计时器)、ITIMER_VIRTUAL(虚拟计时器、ITIMER_PROF(实用计时器))
参数value为一结构体的传出参数,用于传出该计时器的初始间隔时间和重复间隔时间
返回值:如果成功,返回0,否则-1

//2.#include <sys/time.h>int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue); //设置计时器

参数which指定哪个计时器,可选项为ITIMER_REAL(真实计时器)、ITIMER_VIRTUAL(虚拟计时器、ITIMER_PROF(实用计时器))
参数value为一结构体的传入参数,指定该计时器的初始间隔时间和重复间隔时间
参数ovalue为一结构体传出参数,用于传出以前的计时器时间设置。
返回值:如果成功,返回0,否则-1


struct itimerval {struct timeval it_interval; /* next value *///重复间隔struct timeval it_value;  /* current value *///初始间隔};struct timeval {long tv_sec;            /* seconds *///时间的秒数部分long tv_usec;           /* microseconds *///时间的微秒部分};

//初始间隔3秒,重复间隔2秒打印时间#include <stdio.h>#include <stdlib.h>#include <sys/time.h>#include <signal.h>#include <time.h>#include <strings.h>void sig(int p){time_t td;time(&td);printf("the time is %s\n",ctime(&td));}int main(){signal(SIGALRM,sig);struct itimerval it;bzero(&it,sizeof(it));it.it_value.tv_sec=3;it.it_interval.tv_sec=2;int ret;ret=setitimer(ITIMER_REAL,&it,NULL);if(-1==ret){perror("setitimer");return -1;}while(1);return 0;}

运行结果:



#include <stdio.h>#include <stdlib.h>#include <signal.h>#include <sys/time.h>#include <time.h>#include <sys/types.h>#include <unistd.h>#include <strings.h>void sig(int p){time_t tp;time(&tp);printf("the time is %s\n",ctime(&tp));}int main(){signal(SIGALRM,sig);kill(getpid(),SIGALRM);//开始运行程序立刻就向自己发送信号,打印时间struct itimerval it;bzero(&it,sizeof(it));it.it_value.tv_sec=3;it.it_interval.tv_sec=2;int ret;ret=setitimer(ITIMER_REAL,&it,NULL);if(-1==ret){perror("setitimer");return -1;}sleep(5);printf("I am here\n");//sleep被唤醒该语句不能执行while(1);return 0;}

运行结果:


0 0
原创粉丝点击