本文将从以下几个方面来论述信号:

(1)信号的基本知识

(2)信号生命周期与处理过程剖析

(3) 根本的信号处理函数

(4) 爱护临界区不被中断

(5) 信号的继承与执行

(6)实时信号中锁的钻研

第一局部: 信号的基本知识

1.信号实质:

信号的实质是软件档次上对中断的一种模仿。它是一种异步通信的解决机制,事实上,过程并不知道信号何时到来。

2.信号起源

(1)程序谬误,如非法拜访内存

(2)内部信号,如按下了CTRL+C

(3)通过kill或sigqueue向另外一个过程发送信号

3.信号品种

信号分为牢靠信号与不牢靠信号,牢靠信号又称为实时信号,非牢靠信号又称为非实时信号。

信号代码从1到32是不牢靠信号,不牢靠信号次要有以下问题:

(1)每次信号处理完之后,就会复原成默认解决,这可能是调用者不心愿看到的

(2)存在信号失落的问题

当初的Linux对信号机制进行了改良,因而,不牢靠信号次要是指信号失落。

信号代码从SIGRTMIN到SIGRTMAX之间的信号是牢靠信号。牢靠信号不存在失落,由sigqueue发送,牢靠信号反对排队。

牢靠信号注册机制:

内核每收到一个牢靠信号都会去注册这个信号,在信号的未决信号链中调配sigqueue构造,因而,不会存在信号失落的问题。

不牢靠信号的注册机制:

而对于不牢靠的信号,如果内核曾经注册了这个信号,那么便不会再去注册,对于过程来说,便不会晓得本次信号的产生。

牢靠信号与不牢靠信号与发送函数没有关系,取决于信号代码,后面的32种信号就是不牢靠信号,而前面的32种信号就是牢靠信号。

4.信号响应的形式

(1)采纳零碎默认解决SIG_DFL,执行缺省操作

(2)捕获信号处理,即用户自定义的信号处理函数来解决

(3)疏忽信号SIG_IGN ,但有两种信号不能被疏忽SIGKILL,SIGSTOP

第二局部: 信号的生命周期与处理过程剖析

  1. 信号的生命周期

信号产生->信号注册->信号在过程中登记->信号处理函数执行结束

(1)信号的产生是指触发信号的事件的产生

(2)信号注册

指的是在指标过程中注册,该指标过程中有未决信号的信息:

struct sigpending pending:struct sigpending{struct sigqueue *head, **tail;sigset_t signal;};struct sigqueue{struct sigqueue *next;siginfo_t info;}

其中 sigqueue构造组成的链称之为未决信号链,sigset_t称之为未决信号集。

head,*tail别离指向未决信号链的头部与尾部。

siginfo_t info是信号所携带的信息。

信号注册的过程就是将信号值退出到未决信号集siginfo_t中,将信号所携带的信息退出到未决信号链的某一个sigqueue中去。

因而,对于牢靠的信号,可能存在多个未决信号的sigqueue构造,对于每次信号到来都会注册。而不牢靠信号只注册一次,只有一个sigqueue构造。

只有信号在过程的未决信号集中,表明过程曾经晓得这些信号了,还没来得及解决,或者是这些信号被阻塞。

(3)信号在指标过程中登记

在过程的执行过程中,每次从零碎调用或中断返回用户空间的时候,都会查看是否有信号没有被解决。如果这些信号没有被阻塞,那么就调用相应的信号处理函数来解决这些信号。则调用信号处理函数之前,过程会把信号在未决信号链中的sigqueue构造卸掉。是否从未决信号集中把信号删除掉,对于实时信号与非实时信号是不雷同的。

非实时信号:因为非实时信号在未决信号链中只有一个sigqueue构造,因而将它删除的同时将信号从未决信号集中删除。

实时信号:因为实时信号在未决信号链中可能有多个sigqueue构造,如果只有一个,也将信号从未决信号集中删除掉。如果有多个那么不从未决信号集中删除信号,登记结束。

(4)信号处理函数执行结束

执行处理函数,本次信号在过程中响应结束。

在第4步,只简略的形容了信号处理函数执行结束,就实现了本次信号的响应,但这个信号处理函数空间是怎么解决的呢? 内核栈与用户栈是怎么工作的呢? 这就波及到了信号处理函数的过程。

信号处理函数的过程:

(1)注册信号处理函数

信号的解决是由内核来代理的,首先程序通过sigal或sigaction函数为每个信号注册处理函数,而内核中保护一张信号向量表,对应信号处理机制。这样,在信号在过程中登记结束之后,会调用相应的处理函数进行解决。

(2)信号的检测与响应机会

在零碎调用或中断返回用户态的前夕,内核会查看未决信号集,进行相应的信号处理。

(3)处理过程:

程序运行在用户态时->过程因为零碎调用或中断进入内核->转向用户态执行信号处理函数->信号处理函数结束后进入内核->返回用户态持续执行程序

首先程序执行在用户态,在过程陷入内核并从内核返回的前夕,会去查看有没有信号没有被解决,如果有且没有被阻塞就会调用相应的信号处理程序去解决。首先,内核在用户栈上创立一个层,该层中将返回地址设置成信号处理函数的地址,这样,从内核返回用户态时,就会执行这个信号处理函数。当信号处理函数执行完,会再次进入内核,次要是检测有没有信号没有解决,以及复原原先程序中断执行点,复原内核栈等工作,这样,当从内核返回后便返回到原先程序执行的中央了。

信号处理函数的过程大略是这样了。

须要C/C++ Linux高级服务器架构师学习材料后盾加群563998835(包含C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等)

第三局部: 根本的信号处理函数

首先看一个两个概念: 信号未决与信号阻塞

信号未决: 指的是信号的产生到信号处理之前所处的一种状态。确切的说,是信号的产生到信号登记之间的状态。

信号阻塞: 指的是阻塞信号被解决,是一种信号处理形式。

  1. 信号操作

信号操作最罕用的办法是信号的屏蔽,信号屏蔽次要用到以下几个函数:

int sigemptyset(sigset_t *set);int sigfillset(sigset_t *set);int sigaddset(sigset_t *set,int signo);int sigdelset(sigset_t *set,int signo);int sigismemeber(sigset_t* set,int signo);int sigprocmask(int how,const sigset_t*set,sigset_t *oset);

信号集,信号掩码,未决集

信号集: 所有的信号阻塞函数都应用一个称之为信号集的构造表明其所受到的影响。

信号掩码:以后正在被阻塞的信号集。

未决集: 过程在收到信号时到信号在未被解决之前信号所处的汇合称为未决集。

能够看出,这三个概念没有必然的分割,信号集指的是一个泛泛的概念,而未决集与信号掩码指的是具体的信号状态。

对于信号集的初始化有两种办法: 一种是用sigemptyset使信号集中不蕴含任何信号,而后用sigaddset把信号退出到信号集中去。

另一种是用sigfillset让信号集中蕴含所有信号,而后用sigdelset删除信号来初始化。

sigemptyset()函数初始化信号集set并将set设置为空。

sigfillset()函数初始化信号集,但将信号集set设置为所有信号的汇合。

sigaddset()将信号signo退出到信号集中去。

sigdelset()从信号集中删除signo信号。

sigprocmask()将指定的信号汇合退出到过程的信号阻塞汇合中去。如果提供了oset,那么以后的信号阻塞汇合将会保留到oset集全中去。

参数how决定了操作的形式:

SIG_BLOCK 减少一个信号汇合到以后过程的阻塞汇合中去

SIG_UNBLOCK 从以后的阻塞汇合中删除一个信号汇合

SIG_SETMASK 将以后的信号汇合设置为信号阻塞汇合

上面看一个例子:

#includeint sigaction( int signo,const struct sigaction *act,struct sigaction *oldact );

执行后果:

SIGINT singal blocked
block 0
block 1
block 2
block 3
block 4
block 5
block 6
block 7
block 8
block 9
在执行到block 3时按下了CTRL+C并不会终止,直到执行到block9后将汇合从阻塞汇合中移除。
[root@localhost C]# ./s1
SIGINT singal blocked
block 0
block 1
block 2
block 3
block 4
block 5
block 6
block 7
block 8
block 9
SIGINT SINGAL unblokced
unblock 0
unblock 1
因为此时曾经解除了阻塞,在unblock1后按下CTRL+C则立刻终止。

  1. 信号处理函数
struct sigaction {void (*sa_handler)(int);void (*sa_sigaction)(int,siginfo_t*,void*);sigset_t sa_mask;int sa_flags;void (*sa_restorer)(void);} 

这个函数次要是用于扭转或检测信号的行为。

第一个参数是变更signo指定的信号,它能够指向任何值,SIGKILL,SIGSTOP除外

第二个参数,第三个参数是对信号进行细粒度的管制。

如果act不为空,oldact不为空,那么oldact将会存储信号以前的行为。如果act为空,*oldact不为空,那么oldact将会存储信号当初的行为。

struct sigaction {void (*sa_handler)(int);void (*sa_sigaction)(int,siginfo_t*,void*);sigset_t sa_mask;int sa_flags;void (*sa_restorer)(void);} 

参数含意:

sa_handler是一个函数指针,次要是示意接管到信号时所要采取的口头。此字段的值能够是SIG_DFL,SIG_IGN.别离代表默认操作与内核将疏忽过程的信号。这个函数只传递一个参数那就是信号代码。

当SA_SIGINFO被设定在sa_flags中,那么则会应用sa_sigaction来批示信号处理函数,而非sa_handler.

sa_mask设置了掩码集,在程序执行期间会阻挡掩码集中的信号。

sa_flags设置了一些标记, SA_RESETHAND当该函数解决实现之后,设定为为零碎默认的解决模式。SA_NODEFER 在处理函数中,如果再次达到此信号时,将不会阻塞。默认状况下,同一信号两次达到时,如果此时处于信号处理程序中,那么此信号将会阻塞。

SA_SIGINFO示意用sa_sigaction批示的函数。

sa_restorer曾经被废除。

sa_sigaction所指向的函数原型:

void my_handler(int signo,siginfo_t si,void ucontext);

第一个参数: 信号编号

第二个参数:指向一个siginfo_t构造。

第三个参数是一个ucontext_t构造。

其中siginfo_t构造体中蕴含了大量的信号携带信息,能够看出,这个函数比sa_handler要弱小,因为前者只能传递一个信号代码,而后者能够传递siginfo_t信息。

typedef struct siginfo_t{int si_signo;//信号编号int si_errno;//如果为非零值则错误代码与之关联int si_code;//阐明过程如何接管信号以及从何处收到pid_t si_pid;//实用于SIGCHLD,代表被终止过程的PIDpid_t si_uid;//实用于SIGCHLD,代表被终止过程所领有过程的UIDint si_status;//实用于SIGCHLD,代表被终止过程的状态clock_t si_utime;//实用于SIGCHLD,代表被终止过程所耗费的用户工夫clock_t si_stime;//实用于SIGCHLD,代表被终止过程所耗费零碎的工夫sigval_t si_value;int si_int;void * si_ptr;void* si_addr;int si_band;int si_fd;};
 sigqueue(pid_t pid,int signo,const union sigval value)union sigval{int sival_int, void*sival_ptr};

sigqueue函数相似于kill,也是一个过程向另外一个过程发送信号的。

但它比kill函数弱小。

第一个参数指定指标过程的pid.

第二个参数是一个信号代码。

第三个参数是一个共用体,每次只能应用一个,用来过程发送信号传递的数据。

或者传递整形数据,或者是传递指针。

发送的数据被sa_sigaction所批示的函数的siginfo_t构造体中的si_ptr或者是si_int所接管。

sigpending的用法

sigpending(sigset_t set);

这个函数的作用是返回未决的信号到信号集set中。即未决信号集,未决信号集不仅包含被阻塞的信号,也可能包含曾经达到但没有被解决的信号。

示例1: sigaction函数的用法

struct sigaction {void (*sa_handler)(int);void (*sa_sigaction)(int,siginfo_t*,void*);sigset_t sa_mask;int sa_flags;void (*sa_restorer)(void);} 

运行后果:
[root@localhost C]# ./s2
signal 1 handler is : using default hander
signal 2 handler is : 8048437
signal 3 handler is : using default hander
signal 4 handler is : using default hander
signal 5 handler is : using default hander
signal 6 handler is : using default hander
signal 7 handler is : using default hander
signal 8 handler is : using default hander
signal 9 handler is : using default hander
signal 10 handler is : using default hander
signal 11 handler is : using default hander
xxxxx
解释:

sigaction(i,NULL,&oldact);

signal_set(&oldact);

因为act为NULL,那么oldact保留的是以后信号的行为,以后的第二个信号的行为是执行自定义的处理程序。

当按下CTRL+C时会执行信号处理程序,输入xxxxxx,再按一下CTRL+C会进行,是因为SA_RESETHAND复原成默认的解决模式,即终止程序。

如果没有设置SA_NODEFER,那么在处理函数执行过程中按一下CTRL+C将会被阻塞,那么程序会停在那里。

示例2: sigqueue向本过程发送数据的信号

#include#include#include#include#includevoid myhandler(int signo,siginfo_t *si,void *ucontext);int main(){union sigval val;//定义一个携带数据的共用体struct sigaction oldact,act;act.sa_sigaction=myhandler;act.sa_flags=SA_SIGINFO;//示意应用sa_sigaction批示的函数,解决完复原默认,不阻塞处理过程中达到下在被解决的信号//注册信号处理函数sigaction(SIGUSR1,&act,&oldact);char data[100];int num=0;while(num<10){sleep(2);printf("期待SIGUSR1信号的到来/n");sprintf(data,"%d",num++);val.sival_ptr=data;sigqueue(getpid(),SIGUSR1,val);//向本过程发送一个信号}}void myhandler(int signo,siginfo_t *si,void *ucontext){printf("曾经收到SIGUSR1信号/n");printf("%s/n",(char*)(si->si_ptr));}

程序执行的后果是:

期待SIGUSR1信号的到来
曾经收到SIGUSR1信号
0
期待SIGUSR1信号的到来
曾经收到SIGUSR1信号
1
期待SIGUSR1信号的到来
曾经收到SIGUSR1信号
2
期待SIGUSR1信号的到来
曾经收到SIGUSR1信号
3
期待SIGUSR1信号的到来
曾经收到SIGUSR1信号
4
期待SIGUSR1信号的到来
曾经收到SIGUSR1信号
5
期待SIGUSR1信号的到来
曾经收到SIGUSR1信号
6
期待SIGUSR1信号的到来
曾经收到SIGUSR1信号
7
期待SIGUSR1信号的到来
曾经收到SIGUSR1信号
8
期待SIGUSR1信号的到来
曾经收到SIGUSR1信号
9

解释: 本程序用sigqueue不停的向本身发送信号,并且携带数据,数据被放到处理函数的第二个参数siginfo_t构造体中的si_ptr指针,当num<10时不再发。

一般而言,sigqueue与sigaction配合应用,而kill与signal配合应用。

示例3: 一个过程向另外一个过程发送信号,并携带信息

发送端:

#include#include#include#include#includeint main(){union sigval value;value.sival_int=10;if(sigqueue(4403,SIGUSR1,value)==-1){//4403是指标过程pidperror("信号发送失败/n");}sleep(2);}

接收端:

#include#include#include#include#includevoid myhandler(int signo,siginfo_t*si,void *ucontext);int main(){struct sigaction oldact,act;act.sa_sigaction=myhandler;act.sa_flags=SA_SIGINFO|SA_NODEFER;//示意执行后复原,用sa_sigaction批示的处理函数,在执行期间依然能够接管信号sigaction(SIGUSR1,&act,&oldact);while(1){sleep(2);printf("期待信号的到来/n");}}void myhandler(int signo,siginfo_t *si,void *ucontext){ printf("the value is %d/n",si->si_int);}

示例4: sigpending的用法

sigpending(sigset_t *set)将未决信号放到指定的set信号集中去,未决信号包含被阻塞的信号和信号达到时但还没来得及解决的信号

#include#include#include#include#includevoid myhandler(int signo,siginfo_t *si,void *ucontext);int main(){struct sigaction oldact,act;sigset_t oldmask,newmask,pendingmask;act.sa_sigaction=myhandler;act.sa_flags=SA_SIGINFO;sigemptyset(&act.sa_mask);//首先将阻塞汇合设置为空,即不阻塞任何信号//注册信号处理函数sigaction(SIGRTMIN+10,&act,&oldact);//开始阻塞sigemptyset(&newmask);sigaddset(&newmask,SIGRTMIN+10);printf("SIGRTMIN+10 blocked/n");sigprocmask(SIG_BLOCK,&newmask,&oldmask);sleep(20);//为了发出信号printf("now begin to get pending mask/n");if(sigpending(&pendingmask)<0){perror("pendingmask error");}if(sigismember(&pendingmask,SIGRTMIN+10)){printf("SIGRTMIN+10 is in the pending mask/n");}sigprocmask(SIG_UNBLOCK,&newmask,&oldmask);printf("SIGRTMIN+10 unblocked/n");}//信号处理函数void myhandler(int signo,siginfo_t *si,void *ucontext){printf("receive signal %d/n",si->si_signo);} 

程序执行:

在另一个shell发送信号:

kill -44 4579

SIGRTMIN+10 blocked
now begin to get pending mask
SIGRTMIN+10 is in the pending mask
receive signal 44
SIGRTMIN+10 unblocked

能够看到SIGRTMIN因为被阻塞所以处于未决信号集中。

对于根本的信号处理函数就介绍到这了。

第四局部: 爱护临界区不被中断

  1. 函数的可重入性

函数的可重入性是指能够多于一个工作并发应用函数,而不用放心数据谬误。相同,不可重入性是指不能多于一个工作共享函数,除非能放弃函数互斥(或者应用信号量,或者在代码的要害局部禁用中断)。可重入函数能够在任意时刻被中断,稍后继续执行,而不会失落数据。

可重入函数:

  • 不为间断的调用持有静态数据。
  • 不返回指向静态数据的指针;所有数据都由函数的调用者提供。
  • 应用本地数据,或者通过制作全局数据的本地拷贝来爱护全局数据。
  • 绝不调用任何不可重入函数。

不可重入函数可能导致凌乱景象,如果以后过程的操作与信号处理程序同时对一个文件进行写操作或者是调用malloc(),那么就可能呈现凌乱,当从信号处理程序返回时,造成了状态不统一。从而引发谬误。

因而,信号的解决必须是可重入函数。

简略的说,可重入函数是指在一个程序中调用了此函数,在信号处理程序中又调用了此函数,但依然可能失去正确的后果。

printf,malloc函数都是不可重入函数。printf函数如果打印缓冲区一半时,又有一个printf函数,那么此时会造成凌乱。而malloc函数应用了零碎全局内存调配表。

  1. 爱护临界区不被中断

因为临界区的代码是要害代码,是十分重要的局部,因而,有必要对临界区进行爱护,不心愿信号来中断临界区操作。这里通过信号屏蔽字来阻塞信号的产生。

上面介绍两个与爱护临界区不被信号中断的相干函数。

int pause(void);

int sigsuspend(const sigset_t *sigmask);

pause函数挂起一个过程,直到一个信号产生。

sigsuspend函数的执行过程如下:

(1)设置新的mask去阻塞以后过程

(2)收到信号,调用信号的处理函数

(3)将mask设置为原先的掩码

(4)sigsuspend函数返回

能够看出,sigsuspend函数是期待一个信号产生,当期待的信号产生时,执行完信号处理函数后就会返回。它是一个原子操作。

爱护临界区的中断:

(1)首先用sigprocmask去阻塞信号

(2)执行后要害代码后,用sigsuspend去捕捉信号

(3)而后sigprocmask去除阻塞

这样信号就不会失落了,而且不会中断临界区。

应用pause函数对临界区的爱护:

下面的程序是用pause去爱护临界区,首先用sigprocmask去阻塞SIGINT信号,执行临界区代码,而后解除阻塞。最初调用pause()函数期待信号的产生。但此时会产生一个问题,如果信号在解除阻塞与pause之间产生的话,信号就可能失落。这将是一个不牢靠的信号机制。

因而,采纳sigsuspend能够防止上述情况产生。

应用sigsuspend对临界区的爱护:

应用sigsuspend对临界区的爱护就不会产生上述问题了。

  1. sigsuspend函数的用法

sigsuspend函数是期待的信号产生时才会返回。

sigsuspend函数遇到完结时不会返回,这一点很重要。

示例:

上面的例子可能解决信号SIGUSR1,SIGUSR2,SIGSEGV,其它的信号被屏蔽,该程序输入对应的信号,而后持续期待其它信号的呈现。

#include#includevoid signal_set1(int);//信号处理函数,只传递一个参数信号代码void signal_set(struct sigaction *act) {switch(act->sa_flags){case (int)SIG_DFL:printf("using default hander/n");break;case (int)SIG_IGN:printf("ignore the signal/n");break;default:printf("%0x/n",act->sa_handler);}}void signal_set1(int x){//信号处理函数printf("xxxxx/n");while(1){}}int main(int argc,char** argv) {int i;struct sigaction act,oldact;act.sa_handler = signal_set1;act.sa_flags = SA_RESETHAND;//SA_RESETHANDD 在解决完信号之后,将信号复原成默认解决//SA_NODEFER在信号处理程序执行期间依然能够接管信号sigaction (SIGINT,&act,&oldact) ;//扭转信号的解决模式for (i=1; i<12; i++){printf("signal %d handler is : ",i);sigaction (i,NULL,&oldact) ;signal_set(&oldact);//如果act为NULL,oldact会存储信号以后的行为//act不为空,oldact不为空,则oldact会存储信号以前的解决模式}while(1){//期待信号的到来}return 0;}

程序运行后果:

received sigusr1 signal

received sigusr2 signal

received sigsegv signal

received sigusr1 signal

已终止

另一个终端用于发送信号:

先失去以后过程的pid, ps aux|grep 程序名

kill -SIGUSR1 4901

kill -SIGUSR2 4901

kill -SIGSEGV 4901

kill -SIGTERM 4901

kill -SIGUSR1 4901

解释:

第一行发送SIGUSR1,则调用信号处理函数,打印出后果。

第二,第三行别离打印对应的后果。

第四行发送一个默认解决为终止过程的信号。

但此时,但不会终止程序,因为sigsuspend遇到终止过程信号并不会返回,此时并不会打印出"已终止",这个信号被阻塞了。当再次发送SIGURS1信号时,过程的信号阻塞复原成默认的值,因而,此时将会解除阻塞SIGTERM信号,所以过程被终止。

第五局部: 信号的继承与执行

当应用fork()函数时,子过程会继承父过程完全相同的信号语义,这也是有情理的,因为父子过程共享一个地址空间,所以父过程的信号处理程序也存在于子过程中。

示例: 子过程继承父过程的信号处理函数

#include#include#include#include#includevoid myhandler(int signo,siginfo_t *si,void *ucontext);int main(){struct sigaction oldact,act;sigset_t oldmask,newmask,pendingmask;act.sa_sigaction=myhandler;act.sa_flags=SA_SIGINFO;sigemptyset(&act.sa_mask);//首先将阻塞汇合设置为空,即不阻塞任何信号//注册信号处理函数sigaction(SIGRTMIN+10,&act,&oldact);//开始阻塞sigemptyset(&newmask);sigaddset(&newmask,SIGRTMIN+10);printf("SIGRTMIN+10 blocked/n");sigprocmask(SIG_BLOCK,&newmask,&oldmask);sleep(20);//为了发出信号printf("now begin to get pending mask/n");if(sigpending(&pendingmask)<0){perror("pendingmask error");}if(sigismember(&pendingmask,SIGRTMIN+10)){printf("SIGRTMIN+10 is in the pending mask/n");}sigprocmask(SIG_UNBLOCK,&newmask,&oldmask);printf("SIGRTMIN+10 unblocked/n");}//信号处理函数void myhandler(int signo,siginfo_t *si,void *ucontext){printf("receive signal %d/n",si->si_signo);} 

输入的后果为:

子过程
信号处理
10
父过程
信号处理
20

能够看进去,子过程继承了父过程的信号处理函数。

第六局部: 实时信号中锁的钻研

  1. 信号处理函数与主函数之间的死锁

当主函数拜访临界资源时,通常须要加锁,如果主函数在拜访临界区时,给临界资源上锁,此时产生了一个信号,那么转入信号处理函数,如果此时信号处理函数也对临界资源进行拜访,那么信号处理函数也会加锁,因为主程序持有锁,信号处理程序期待主程序开释锁。又因为信号处理函数曾经抢占了主函数,因而,主函数在信号处理函数完结之前不能运行。因而,必然造成死锁。

示例1: 主函数与信号处理函数之间的死锁

#include#include#include#include#include#includeint value=0;sem_t sem_lock;//定义信号量void myhandler(int signo,siginfo_t *si,void *vcontext);//过程处理函数申明int main(){union sigval val;val.sival_int=1;struct sigaction oldact,newact;int res;res=sem_init(&sem_lock,0,1);if(res!=0){perror("信号量初始化失败");}newact.sa_sigaction=myhandler;newact.sa_flags=SA_SIGINFO;sigaction(SIGUSR1,&newact,&oldact);sem_wait(&sem_lock);printf("xxxx/n");value=1;sleep(10);sigqueue(getpid(),SIGUSR1,val);//sigqueue发送带参数的信号sem_post(&sem_lock);sleep(10);exit(0);}void myhandler(int signo,siginfo_t *si,void *vcontext){sem_wait(&sem_lock);value=0;sem_post(&sem_lock);}

此程序将始终阻塞在信号处理函数的sem_wait函数处。

  1. 利用测试锁解决死锁

sem_trywait(&sem_lock);是非阻塞的sem_wait,如果加锁失败或者是超时,则返回-1。

示例2: 用sem_trywait来解决死锁

#include#include#include#include#include#includeint value=0;sem_t sem_lock;//定义信号量void myhandler(int signo,siginfo_t *si,void *vcontext);//过程处理函数申明int main(){union sigval val;val.sival_int=1;struct sigaction oldact,newact;int res;res=sem_init(&sem_lock,0,1);if(res!=0){perror("信号量初始化失败");}newact.sa_sigaction=myhandler;newact.sa_flags=SA_SIGINFO;sigaction(SIGUSR1,&newact,&oldact);sem_wait(&sem_lock);printf("xxxx/n");value=1;sleep(10);sigqueue(getpid(),SIGUSR1,val);//sigqueue发送带参数的信号sem_post(&sem_lock);sleep(10);sigqueue(getpid(),SIGUSR1,val);exit(0);}void myhandler(int signo,siginfo_t *si,void *vcontext){if(sem_trywait(&sem_lock)==0){value=0;sem_post(&sem_lock);}}

第一次发送sigqueue时,因为主函数持有锁,因而,sem_trywait返回-1,当第二次发送sigqueue时,主函数曾经开释锁,此时就能够在信号处理函数中对临界资源加锁了。

但这种办法显著失落了一个信号,不是很好的解决办法。

  1. 利用双线程来解决主函数与信号处理函数死锁

咱们晓得,当过程收到一个信号时,会抉择其中的某个线程进行解决,前提是这个线程没有屏蔽此信号。因而,能够在主线程中屏蔽信号,另选一个线程去解决这个信号。因为主线程与另外一个线程是平行执行的,因而,期待主线程执行完临界区时,开释锁,这个线程去执行信号处理函数,直到执行结束开释临界资源。

这里用到一个线程的信号处理函数: pthread_sigmask

int pthread_sigmask(int how,const sigset_t set,sigset_t oldset);

这个函数与sigprocmask很类似。

how:

SIG_BLOCK 将信号集退出到线程的阻塞集中去

SIG_UNBLOCK 将信号集从阻塞集中删除

SIG_SETMASK 将以后汇合设置为线程的阻塞集

示例: 利用双线程来解决主函数与信号处理函数之间的死锁

#include#include#include#include#include#include#includevoid*thread_function(void *arg);//线程处理函数void myhandler(int signo,siginfo_t *si,void *vcontext);//信号处理函数int value;sem_t semlock;int main(){int res;pthread_t mythread;void *thread_result;res=pthread_create(&mythread,NULL,thread_function,NULL);//创立一个子线程if(res!=0){perror("线程创立失败");}//在主线程中将信号屏蔽sigset_t empty;sigemptyset(&empty);sigaddset(&empty,SIGUSR1);pthread_sigmask(SIG_BLOCK,&empty,NULL);//主线程中对临界资源的拜访if(sem_init(&semlock,0,1)!=0){perror("信号量创立失败");}sem_wait(&semlock);printf("主线程曾经执行/n");value=1;sleep(10);sem_post(&semlock);res=pthread_join(mythread,&thread_result);//期待子线程退出exit(EXIT_SUCCESS);}void *thread_function(void *arg){struct sigaction oldact,newact;newact.sa_sigaction=myhandler;newact.sa_flags=SA_SIGINFO;//注册信号处理函数sigaction(SIGUSR1,&newact,&oldact);union sigval val;val.sival_int=1;printf("子线程睡眠3秒/n");sleep(3);sigqueue(getpid(),SIGUSR1,val);pthread_exit(0);//线程完结}void myhandler(int signo,siginfo_t *si,void *vcontext){sem_wait(&semlock);value=0;printf("信号处理结束/n");sem_post(&semlock);}

运行后果如下:
主线程曾经执行
子线程睡眠3秒
信号处理完

解释一下:

在主线线程中阻塞了SIGUSR1信号,首先让子线程睡眠3秒,目标让主线程先运行,而后当主线程拜访临界资源时,让线程sleep(10),在这期间,子线程发送信号,此时子线程会去解决信号,而主线程仍旧平行的运行,子线程被阻止信号处理函数的sem_wait处,期待主线程10后,信号处理函数失去锁,而后进行临界资源的拜访。这就解决了主函数与信号处理函数之间的死锁问题。

扩大: 如果有多个信号达到时,还能够用多线程来解决多个信号,从而达到并行的目标,这个很好实现的,能够尝试一下。