工作点滴(五)Linux多线程编程中的信号问题

时间:2022-07-25 18:31:11

0 引言

由于工作保密原因,只简单介绍下背景:

本系统中有一个进程A,A中又分别生成5个线程分别为a,b,c,d和e。

其中,

a是主线程(也可以叫做进程A),采用epoll(select)通过socket负责和其他进程间的通信。

b是计时器线程,通过alarm调用产生信号中断来进行计时。(主要是通过类似如下代码进行)

 signal(SIGALRM,timer_handle);

    if(setitimer(ITIMER_REAL,&gTimerVal,NULL) == -1)
        printf("setitimer error! ");


c,d和e是其他工作线程。

1 问题产生

如果初始化的时候将b线程(计时器线程)打开。

在a线程的socket的通信中就会产生“Interrupted system call”(中文的意思:系统调用被中断)。

如果关闭b线程,该bug就不会产生。

2 分析问题

通过研究epoll及signal原理,发现是由于多线程调用的信号问题产生。

3 解决问题

通过在a线程中设置中断屏蔽即可解决,同时不影响其他线程接受signal。

代码如下:

        
        ...

        struct sigaction action;  
	bzero(&action,sizeof(action));
	action.sa_sigaction = siga_handler;  
	sigset_t set;  
	sigemptyset(&set)  
	sigaddset(&set,SIGALRM); 
	//sigprocmask(SIG_BLOCK,&set,NULL);
	pthread_sigmask(SIG_BLOCK,&set,NULL);
	action.sa_mask = set;  
	action.sa_flags = SA_SIGINFO;  
	// if(sigaction(SIGINT,&action,NULL)==-1)perror("error"),exit(-1); 
	if(sigaction(SIGALRM,&action,NULL)==-1)perror("error"),exit(-1);
         ...

4 总结

4.1 Linux 多线程应用中,每个线程可以通过调用 pthread_sigmask() 设置本线程的信号掩码。一般情况下,被阻塞的信号将不能中断此线程的执行,除非此信号的产生是因为程序运行出错如 SIGSEGV;另外不能被忽略处理的信号 SIGKILL 和 SIGSTOP 也无法被阻塞。

4.2 当一个线程调用 pthread_create() 创建新的线程时,此线程的信号掩码会被新创建的线程继承。

4.3 信号安装最好采用sigaction方式,sigaction,是为替代signal 来设计的较稳定的信号处理,signal的使用比较简单。signal(signalNO,signalproc);

不能完成的任务是:1.不知道信号产生的原因;

                                    2.处理信号中不能阻塞其他的信号

而signaction,则可以设置比较多的消息。尤其是在信号处理函数过程中接受信号,进行何种处理。

sigaction函数用于改变进程接收到特定信号后的行为。

4.4 sigprocmask函数只能用于单线程,在多线程中使用pthread_sigmask函数。

4.5 信号是发给进程的特殊消息,其典型特性是具有异步性。

4.6 信号集代表多个信号的集合,其类型是sigset_t。

4.7 每个进程都有一个信号掩码(或称为信号屏蔽字),其中定义了当前进程要求阻塞的信号集。

4.8 所谓阻塞,指Linux内核不向进程交付在掩码中的所有信号。于是进程可以通过修改信号掩码来暂时阻塞特定信号的交付,被阻塞的信号不会影响进程的行为直到该信号被真正交付。

4.9 忽略信号不同于阻塞信号,忽略信号是指Linux内核已经向应用程序交付了产生的信号,只是应用程序直接丢弃了该信号而已。

5 程序实例

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <pthread.h>

struct sigaction act;

void catchctrlc(int signo)
{
	char msg[]="Ctrl-C entered!\n";
	char errmsg[]="Failed to restore SIGINT\n";
	int msglen = sizeof(msg);
	int emsglen = sizeof(errmsg);

	act.sa_handler = SIG_DFL;
	write(STDERR_FILENO,msg,msglen);

	if(sigaction(SIGINT,&act,NULL) == -1)
		write(STDERR_FILENO,errmsg,emsglen);
}

int main()
{
	act.sa_handler = catchctrlc;
	act.sa_flags = 0;

	if((sigemptyset(&act.sa_mask) == -1) ||
		(sigaction(SIGINT,&act,NULL) == -1))
		perror("Failed to set SIGINT to handle Ctrl-c");

	for(;;)
		;
	return 0;
}

运行结果:

lab@ubuntu:~/ctest/signal$ ./cc //运行程序
                                //回车
^CCtrl-C entered!               //第一次按Ctrl+c
                              //回车
^C                             //第二次按Ctrl+c
lab@ubuntu:~/ctest/signal$