十_信号10 - sigaction(),区别于 signal()

博客指出signal()存在两个缺陷,一是多信号共用处理函数时可能重入导致段错误,二是不能指定接收信号来源。sigaction()可弥补这些缺陷,前者以守护进程为例展示重入问题,后者以漏桶实例说明需指定信号来源,还提到用sigaction()实现多信号共用处理函数不会重入。

signal()的缺陷1 :当多个信号共用一个信号处理函数的时候,可能会发生重入,导致段错误。所以我们希望,在响应一个信号的时候,将其他信号阻塞。 sigaction()可以弥补

signal()的缺陷2 :不能指定接收信号来源, sigaction()可以弥补

在这里插入图片描述

  struct sigaction {
       	/*
       	响应函数 多信号共用,避免重入现象
       	sa_handler:指定信号捕捉后的处理函数名(即注册函数)。也可赋值为SIG_IGN表忽略 或 SIG_DFL表执行默认动作;
       	*/
           void     (*sa_handler)(int); 
           void     (*sa_sigaction)(int, siginfo_t *, void *);//三参响应函数,sa_sigaction同样可以多信号共用,siginfo_t 信号来源识别

		/*
		调用信号处理函数时,所要屏蔽的信号集合(信号屏蔽字)。
		注意:仅在处理函数被调用期间屏蔽生效,是临时性设置
		*/
           sigset_t   sa_mask; //需要阻塞的其他信号集合
         /*
         sa_flags:通常设置为0,表示用默认属性。
         默认属性即为:sa_mask中将自己屏蔽,即该信号的注册函数执行期间,再次向进程发送该信号,该信号不能递达,处于未决状态。
         SA_NOCLDWAIT属性,阻止子进程变成僵尸状态,从而免去收僵尸的麻烦
         */
           int        sa_flags; //特殊要求
           void     (*sa_restorer)(void);
       };

两者取其一,信号的响应处理,都可以多信号共用,避免重入现象。区别在于 后者可以指定 信号来源。

void     (*sa_handler)(int);
void     (*sa_sigaction)(int, siginfo_t *, void *);


  siginfo_t {
               int      si_signo;     /* Signal number */
               int      si_errno;     /* An errno value */
               int      si_code;      /* Signal code */ 信号值
              .....
           }
si_code指定信号来源
       SI_USER
              kill(2).

       SI_KERNEL
              Sent by the kernel.
       ...

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
signal()的缺陷1 试验:当多个信号共用一个信号处理函数的时候,可能会发生重入

以前面的守护进程为例:

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
#include <fcntl.h>

#define FILENAME "/tmp/out"

static int craetdeamon(void)
{
	pid_t pid;
	int fd;	
	
	pid = fork();
	if(pid < 0)
	{
		perror("fork()");
		return -1;
	}
	
	if(pid > 0) //父进程结束
	{
		printf("%d\n",getpid());
		exit(0);
	}
		

	fd = open("/dev/null",O_RDWR);
	if(fd < 0)
	{	
		perror("open()");
		return -1;
	}

//重定向 0 1  2 文件描述符
	dup2(fd,0);
	dup2(fd,1);
	dup2(fd,2);	
	if(fd > 2)
	{
		close(fd);
	}

//创建守护进程
	setsid();
	
	chdir("/");

	return 0;
}

int main(int argc,char* argv[])
{

	FILE* fp;
	int i;


	if(craetdeamon())
	{
		exit(1);
	}
	
	fp = fopen(FILENAME,"w");
	if(fp == NULL)
	{
		perror("fopen()");
		exit(1);
	}

	for(i = 0; ;i++)
	{
		fprintf(fp,"%d\n",i);
		fflush(fp);
		sleep(1);
	}
	
	
	exit(0);
}

该程序并不完善,因为该守护进程 已经脱离了控制终端,符合守护进程的特点,该程序只能异常终止。即 kill(),那么作如下修改:

添加信号处理,接受特定信号,终止守护进程,伪代码:

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <syslog.h>

#define FILENAME "/tmp/out"

static int craetdeamon(void)
{
	pid_t pid;
	int fd;	
	
	pid = fork();
	if(pid < 0)
	{
		perror("fork()");
		return -1;
	}

	if(pid > 0)
	{
		printf("%d\n",getpid());
		exit(0);
	}

	fd = open("/dev/null",O_RDWR);
	if(fd < 0)
	{	
		perror("open()");
		return -1;
	}

	dup2(fd,0);
	dup2(fd,1);
	dup2(fd,2);	
	if(fd > 2)
	{
		close(fd);
	}
	setsid();return

	chdir("/");
	return 0;
}

static void exitdeamon(int s)
{
	fclose(fb);
	closelog();
}


int main(int argc,char* argv[])
{
	FILE* fp;
	int i;

signal(SIGINT,exitdeamon);
signal(SIGQUIT,exitdeamon);
signal(SIGTERM,exitdeamon);

	openlog("craetdeamon",LOG_PID,LOG_DAEMON);
	if(craetdeamon())
	{
		syslog(LOG_ERR,"craetdeamon failed!");
		exit(1);
	}else{
		syslog(LOG_INFO,"craetdeamon successded!");
	}

	fp = fopen(FILENAME,"w");
	if(fp == NULL)
	{
		syslog(LOG_ERR,"fopen %s failed!",FILENAME);
		exit(1);
	}

	syslog(LOG_INFO,"fopen %s successede!",FILENAME);

	for(i = 0; ;i++)
	{
		fprintf(fp,"%d\n",i);
		fflush(fp);
		syslog(LOG_DEBUG,"%d is printed!",i);
		sleep(1);
	}
	exit(0);

}

程序的本意是,守护进程 接收到 SIGINT,SIGQUIT,SIGTERM 三个信号任意信号 的时候终止守护进程,多个信号共用一个信号处理函数。但是有一个问题:

接收到这三个信号中的任意信号,都会执行处理函数中内容,假如有这样的情形:

1 程序接受到了 SIGINT 信号,程序收到中断后扎内核,被调度后,从内核态切换到用户态,发现 收到了 SIGINT信号,于是开始执行处理函数,但是只执行了 fclose(fb); 这一句,就再次被打断,进程内核态,等待调度。

2 在等待调度的时候,程序又收到了 SIGQUIT 信号,等到程序被调度,从内核态切换到用户态时候,发现收到了 SIGQUIT信号,于是再次 执行 信号处理函数,再次执行到 fclose(fb);。于是fb 被两次 fclose(),会发生段错误。发生了重入!!

用 sigaction() 实现 多信号共用一个处理函数。

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <syslog.h>
#include <signal.h>

#define FILENAME "/tmp/out"

static FILE* fp;

static int craetdeamon(void)
{
	pid_t pid;
	int fd;	

	pid = fork();
	if(pid < 0)
	{
		perror("fork()");
		return -1;
	}

	if(pid > 0)
	{
		printf("%d\n",getpid());
		exit(0);
	}

	fd = open("/dev/null",O_RDWR);
	if(fd < 0)
	{	
		perror("open()");
		return -1;
	}

	dup2(fd,0);
	dup2(fd,1);
	dup2(fd,2);	
	if(fd > 2)
	{
		close(fd);
	}
	setsid();return
	chdir("/");
	return 0;

}

static void exitdeamon(int s)
{
	fclose(fp);
	closelog();
}



int main(int argc,char* argv[])
{
	int i;
	struct sigaction sa;


	sa.sa_handler = exitdeamon;
	sigemptyset(&sa.sa_mask);
	sigaddset(&sa.sa_mask,SIGQUIT);
	sigaddset(&sa.sa_mask,SIGTERM);
	sigaddset(&sa.sa_mask,SIGINT);

	sigaction(SIGINT,&sa,NULL);
	sigaction(SIGTERM,&sa,NULL);
	sigaction(SIGQUIT,&sa,NULL);

	openlog("craetdeamon",LOG_PID,LOG_DAEMON);

	if(craetdeamon())
	{
		syslog(LOG_ERR,"craetdeamon failed!");
		exit(1);
	}else{
		syslog(LOG_INFO,"craetdeamon successded!");
	}


	fp = fopen(FILENAME,"w");
	if(fp == NULL)
	{
		syslog(LOG_ERR,"fopen %s failed!",FILENAME);
		exit(1);
	}

	syslog(LOG_INFO,"fopen %s successede!",FILENAME);

	for(i = 0; ;i++)
	{
	fprintf(fp,"%d\n",i);
		fflush(fp);
		syslog(LOG_DEBUG,"%d is printed!",i);
		sleep(1);
	}

	exit(0);

}

不会发生重入现象

signal()缺陷2:不能识别信号来源

回顾之前的漏桶实例:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>

#define BUFSIZE 10

static volatile int loop = 0;

static void alrm_handler(int s)
{
	alarm(1);//重新定时
	loop = 1;
}

int main(int argc,char *argv[])
{
	int sfd,dfd=1;
	char buf[BUFSIZE];
	int len,ret,pos;


	if(argc < 2)
	{
		fprintf(stderr,"Usage:%s <src_file> <dest_file>\n",argv[0]);
		exit(1);
	}

	signal(SIGALRM,alrm_handler);
	alarm(1);

	do
	{
		sfd = open(argv[1],O_RDONLY);
		if(sfd < 0)
		{
			if(errno != EINTR)//防止是 信号打断阻塞的系统调用
			{
				perror("open()");
				exit(1);	
			}
		}
	}while(sfd < 0);

	while(1)
	{

//休眠挂起 直到收到信号,重新开始执行while(!loop)循环,实现一秒一输出
// 这里也可以 不用pause(),while()后 执行空,但是这样 CPU 占用率会很高,一秒钟会在这里执行循环上亿次,所以用pause()替换,直接休眠等待信号来唤醒
/*		
while(!loop)
			;
*/
		while(!loop)
			pause();
		loop = 0;

		while((len = read(sfd,buf,BUFSIZE)) < 0)
		{	
if(errno == EINTR)//防止是 信号打断阻塞的系统调用
				continue;
			perror("read()");
			break;
		}

		if(len == 0)
			break;

		//确保写进去 len 个字节
		pos = 0;
		while(len > 0)
		{
			ret = write(dfd,buf+pos,len);
			if(ret < 0)
			{
				if(errno == EINTR) //防止是 信号打断阻塞的系统调用
					continue;
				perror("write()");
				exit(1);

			}
			pos += ret;
			len -= ret;

		}

	}

	close(sfd);

}

在终端1 运行
mhr@ubuntu:~/Desktop/xitongbiancheng/parallel/signal/test$ ./a.out /etc/services

在终端2 运行
mhr@ubuntu:~/Desktop/xitongbiancheng/parallel/signal/test$ while true ; do kill -ALRM 10405 ;done

会发现 程序会瞬间执行完成。即 从其他终端 以用户的角度 向指定进程 不停的发送 ALRM 信号,导致流控失效。问题在于 signal() 并不会检查区分 信号的来源,属性信息,只要来了该信号,就会响应动作。但是实际上程序利用 alarm来发送信号,实际上 alarm信号是从 kernel 发送过来的,而刚刚的实验信号,是从 user 发送的。所以需要指定只响应从某处来的 信号,即指定信号来源,指定从 kernel来的信号,signal() 无法完成该动作,sigaction()可以。

实例 待补充。。。。。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ma浩然

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值