Linux system programming 51 signals-sigaction(), different from signal()

Defect of signal() 1: When multiple signals share a signal processing function, reentrance may occur, resulting in segmentation faults. So we hope to block other signals when responding to one signal. sigaction() can make up

Defect of signal() 2: Cannot specify the source of the received signal, sigaction() can make up for it


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);
       };

Choose one of the two, and the signal response processing can be shared by multiple signals to avoid re-entry. The difference is that the latter can specify the signal source.

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 specifies the source of the signal:

       SI_USER
              kill(2).

       SI_KERNEL
              Sent by the kernel.
       ...

Defects of signal() 1 Test: When multiple signals share a signal processing function, reentrance may occur

Take the previous daemon as an example:

#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);
}

The program is not perfect, because the daemon process has been separated from the controlling terminal and conforms to the characteristics of the daemon process. The program can only terminate abnormally. That is, kill(), then make the following modifications:

Add signal processing, accept specific signals, terminate the daemon, pseudo code:

#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);

}

The original intention of the program is that the daemon process terminates the daemon process when it receives any of the three signals, SIGINT, SIGQUIT, and SIGTERM, and multiple signals share a signal processing function. But there is a problem:

When receiving any of these three signals, the content in the processing function will be executed, if there is such a situation:

1 The program receives the SIGINT signal. After receiving the interrupt, the program pierces the kernel. After being scheduled, it switches from the kernel mode to the user mode. When it finds that the SIGINT signal is received, it starts to execute the processing function, but only executes fclose(fb); this sentence , It is interrupted again, the process is kernel mode, waiting for scheduling.

2 While waiting for the scheduling, the program received the SIGQUIT signal again. When the program was scheduled and switched from the kernel mode to the user mode, it was found that the SIGQUIT signal was received, so the signal processing function was executed again and executed again to fclose(fb);. So fb is fclose() twice, a segfault will occur. Re-entry happened! !

Use sigaction() to share a processing function for multiple signals.

#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);

}

No reentrancy


signal() defect 2: Cannot identify the source of the signal

Recall the previous leaky bucket example:

#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);

}

Run
mhr@ubuntu:~/Desktop/xitongbiancheng/parallel/signal/test$ ./a.out /etc/services in terminal 1

Run
mhr@ubuntu:~/Desktop/xitongbiancheng/parallel/signal/test$ while true in terminal 2 ; do kill -ALRM 10405 ;done

You will find that the program will be executed instantly. That is, other terminals continuously send ALRM signals to the specified process from the user's perspective, resulting in flow control failure. The problem is that signal() does not check the source and attribute information of the distinguished signal. As long as the signal comes, it will respond to the action. But in fact, the program uses alarm to send signals. In fact, the alarm signal is sent from the kernel, while the experimental signal just now is sent from the user. Therefore, you need to specify that you only respond to signals from somewhere, that is, specify the source of the signal, specify the signal from the kernel, signal() cannot complete the action, sigaction() can.

Examples need to be added. . . . .

Guess you like

Origin blog.csdn.net/LinuxArmbiggod/article/details/114145285