Linux信号透彻分析理解与各种实例讲解( 五 )


上面的程序是用pause去保护临界区 , 首先用sigprocmask去阻塞SIGINT信号 , 执行临界区代码 , 然后解除阻塞 。 最后调用pause()函数等待信号的发生 。 但此时会产生一个问题 , 如果信号在解除阻塞与pause之间发生的话 , 信号就可能丢失 。 这将是一个不可靠的信号机制 。
因此 , 采用sigsuspend可以避免上述情况发生 。
使用sigsuspend对临界区的保护:
Linux信号透彻分析理解与各种实例讲解文章插图
使用sigsuspend对临界区的保护就不会产生上述问题了 。
3. 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,//改变信号的处理模式for (i=1; i<12; i++){printf("signal %d handler is : ",i);sigaction (i,NULL,signal_set(//如果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 , 则调用信号处理函数 , 打印出结果 。
第二 , 第三行分别打印对应的结果 。
【Linux信号透彻分析理解与各种实例讲解】第四行发送一个默认处理为终止进程的信号 。
但此时 , 但不会终止程序 , 由于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(//首先将阻塞集合设置为空 , 即不阻塞任何信号//注册信号处理函数sigaction(SIGRTMIN+10,//开始阻塞sigemptyset(sigaddset(printf("SIGRTMIN+10 blocked/n");sigprocmask(SIG_BLOCK,sleep(20);//为了发出信号printf("now begin to get pending mask/n");if(sigpending(}if(sigismember(}sigprocmask(SIG_UNBLOCK,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=http://kandian.youth.cn/index/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(if(res!=0){perror("信号量初始化失败");}newact.sa_sigaction=myhandler;newact.sa_flags=SA_SIGINFO;sigaction(SIGUSR1,sem_wait(printf("xxxx/n");value=http://kandian.youth.cn/index/1;sleep(10);sigqueue(getpid(),SIGUSR1,val);//sigqueue发送带参数的信号sem_post(sleep(10);exit(0);}void myhandler(int signo,siginfo_t *si,void *vcontext){sem_wait(value=0;sem_post(}