Linux学习笔记20——sigaction 注册信号的更健壮的方法

1,sigaction原型

前面我们已经使用signal函数去注册信号。

但是,该方法有两个缺点:

  1. 使用c99标准编译的话,该方法注册的信号只能被触发一次。想要再次触发就必须再次注册;
  2. 如果在执行信号处理程序的时候,又捕获到了信号,则该信号会丢失;

所以现在推荐使用的是一个更健壮的函数sigaction来实现相同的功能。不过它的调用要比signal更复杂。

其原型如下:

#include <signal.h>
int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact);
//执行成功返回0;失败返回-1

signo代表信号,不用多说。比较麻烦的是,这里引入了一个新的结构体sigaction,其定义如下:

struct sigaction {
    void (*sa_handler)(int); //信号处理程序
    sigset_t sa_mask;        //信号屏蔽字
    int sa_flags;            //信号处理选项
    void (*sa_sigaction)(int, siginfo_t*, void*);//先别管吧。。。
}

act和oact分别代表修改后和修改前的。oact可以为空,为空就不赋值。

在使用中,sa_mask直接调用sigemptyset()初始化为空就行了。sa_handler也直接赋值句柄就OK。比较麻烦的是sa_flags。针对这一项,有多个宏定义,代表不同的含义。同时,不同的系统对他们的支持还不同。

列举两个最常用的吧:

  1. SA_INTERRUPT,由此信号终止的系统调用不自动重启(还记得17节里面的那个带限时功能的read函数吧?)
  2. SA_RESTART,由此信号终止的系统调用会自动重启;
  3. SA_RESETHAND,捕捉到该信号后,将信号的处理方式重置为默认(SIG_DFL),也就是C99里signal的处理方式,信号注册一次就只能触发一次;

主要我们的sa_flags不取SA_RESETHAND,我们的信号就可以一次注册,多次触发;

同时,sigaction与signal最大的不同是,在调用信号处理程序之前,我们会屏蔽当前信号。这样一来,在执行信号处理程序的时候如果又发来了信号,则这个信号是未决的,等信号处理程序返回,我们会恢复信号屏蔽字。如此,则之前pending的信号这回会被捕获、触发。所以,不用担心在执行信号处理程序的时候发生信号丢失。

2,例子

我们用sigaction代替signal,注册SIGUSR1信号

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <setjmp.h>

typedef void Sigfunc(int); //信号处理程序的函数类型
void sig_usr_new(int signo);//声明信号处理程序

int main(){
	//10.14 使用sigaction注册信号
	//代替signal(SIGUSR1, sig_usr_new)
	struct sigaction act, oact;
	act.sa_handler = sig_usr_new;//设置信号处理程序
	sigemptyset(&act.sa_mask);	 //初始化信号屏蔽字

	//设置信号处理选项
	act.sa_flags = 0;
	act.sa_flags |= SA_INTERRUPT;

	//调用sigaction
	if (sigaction(SIGUSR1, &act, &oact) < 0)
	{
		printf("sigaction error!\n");
		exit(1);
	}

	//死循环,为了防止进程退出
	while(1){
		pause();
	}
}

//定义信号处理程序
void sig_usr_new(int signo)
{
	printf("recived SIGUSR, signo = %d\n",signo);
	sleep(7);
	printf("return form sig_usr_new\n");
}

我们故意在信号处理程序里休眠了7秒中,在这7秒种内我们可以不停地发送信号,看看它是怎么处理的:

➜  code g++ -g -W -o study_Linux study_Linux.c
➜  code ./study_Linux &
[2] 169
➜  code kill -USR1 169     #第一次发送信号触发回调
recived SIGUSR, signo = 10  #信号处理程序开始执行,接下来休眠7秒
➜  code kill -USR1 169     #在7秒内连续发送3次信号
➜  code kill -USR1 169
➜  code kill -USR1 169
➜  code return form sig_usr_new #7秒结束,信号处理程序返回,返回前取消屏蔽信号
recived SIGUSR, signo = 10       #休眠期发送的信号触发了回调

➜  code return form sig_usr_new #7秒后,信号处理程序返回

我们可以看到,休眠期内发送的信号并没有丢失,也没有被马上触发,而是等到之前的信号处理程序执行完之后才被触发;这是因为在执行信号处理程序时,操作系统建立的新信号屏蔽字自动包括当前正在处理的信号。换句话说,用sigaction或signal注册信号的时候,操作系统自动屏蔽当前正在触发的信号;等信号处理函数返回前,操作系统再取消对其的屏蔽,准备捕获下一个同样的信号。所以在休眠期再发送相同的信号是不会有问题的。

不过,当前的系统也没有对信号排队,只触发了一次,而不是三次。

 

如果我们把main函数代码调整一下,变成如下:

int main(){
	//改为调用signal
	if(signal(SIGUSR1, sig_usr_new) == SIG_ERR)
	{
		printf("sigaction error!\n");
		exit(1);
	}

	if(signal(SIGUSR2, sig_usr_new) == SIG_ERR)
	{
		printf("sigaction error!\n");
		exit(1);
	}

	//死循环,为了防止进程退出
	while(1){
		pause();
	}
}

这时我们会发现,在休眠时,不管是发送SIGUSR1还是SIGUSR2,都是立马执行信号处理程序,根本不会等待原先的信号处理程序执行完。

结果如下:

➜  code kill -USR1 388    #发送USR1
recived SIGUSR, signo = 10 #执行USR1的回调
➜  code kill -USR2 388    #发送USR2
recived SIGUSR, signo = 12 #立刻执行USR2的回调
➜  code return form sig_usr_new, signo = 12 #USR2的回调返回
return form sig_usr_new, signo = 10 #USR1的回调返回

我们可以看到USR2的回调甚至在USR1的回调之前返回。

我们可以把代码再修改下,改成执行USR1的回调的时候,屏蔽USR2.这样做的好处是,确保USR1的回调不会因为受到USR2信号而被打断,在执行完USR1的回调后再继续执行USR2的回调。

int main()
{
	//10.14 使用sigaction注册信号
	//代替signal(SIGUSR1, sig_usr_new)
	struct sigaction act, oact;
	act.sa_handler = sig_usr_new;//设置信号处理程序
	sigemptyset(&act.sa_mask);	 //初始化信号屏蔽字
	sigaddset(&act.sa_mask, SIGUSR2);//屏蔽SIGUSR2

	//设置信号处理选项
	act.sa_flags = 0;
	act.sa_flags |= SA_INTERRUPT;

	//调用sigaction,注册SIGUSR1
	if (sigaction(SIGUSR1, &act, &oact) < 0)
	{
		printf("sigaction error!\n");
		exit(1);
	}

    //调用signal,注册SIGUSR2
	if(signal(SIGUSR2, sig_usr_new) == SIG_ERR)
	{
		printf("sigaction error!\n");
		exit(1);
	}

	//死循环,为了防止进程退出
	while(1){
		pause();
	}
}

执行结果如下:

➜  code  kill -USR1 448    #发送USR1
recived SIGUSR, signo = 10  #执行USR1回调,屏蔽了USR2
➜  code  kill -USR2 448    #休眠期发送USR2回调
➜  code return form sig_usr_new, signo = 10 #USR1的回调返回,取消屏蔽USR2
recived SIGUSR, signo = 12  #开始执行USR2的回调
return form sig_usr_new, signo = 12 #USR2的回调返回

现在不用担心在执行USR1回调的期间开始执行USR2的回调了。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值