Linux系统编程51 信号- sigaction(),区别于 signal()

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

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


NAME
       sigaction, rt_sigaction - examine and change a signal action

SYNOPSIS
       #include <signal.h>
       
对 signum 信号,定义新的响应act,并且保存该信号旧的响应oldact
       int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);

The sigaction structure is defined as something like:

       struct sigaction {
           void     (*sa_handler)(int); //响应函数 多信号共用,避免重入现象
           void     (*sa_sigaction)(int, siginfo_t *, void *);//三参响应函数,sa_sigaction同样可以多信号共用,siginfo_t 信号来源识别
           sigset_t   sa_mask; //需要阻塞的其他信号集合
           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()可以。

实例 待补充。。。。。

猜你喜欢

转载自blog.csdn.net/LinuxArmbiggod/article/details/114145285