C++进阶学习--多线程/多进程下的信号处理

目录

一、引言

二、多进程下信号的使用

------> 2.1、信号的处理流程
------> 2.2、安装登记
------> 2.3、信号集操作函数
------> 2.4、设置信号屏蔽位函数
------> 2.5、查询被搁置(未决)信号函数
------> 2.6、总结

三、多线程下信号的使用

------> 3.1、多线程pthread常用接口
------> 3.2、多进程和多线程下的信号
------> 3.3、线程中信号使用的相关接口
------> 3.4、多线程中信号使用demo
------------> 3.4.1、sigwait阻塞示例
------------> 3.4.2、信号在多线程中通讯实例
------------> 3.4.3、多线程同步接收信号

一、引言

本篇文章来详细介绍下在多进程,多线程下信号的使用

二、多进程下信号的使用

信号是与一定的进程相联系的。也就是说,一个进程可以决定在进程中对哪些信号进行什 么样的处理。例如,一个进程可以忽略某些信号而只处理其他一些信号;另外,一个进程还可以选择如何处理信号。总之,这些总与特定的进程相联系的。

信号的处理流程

对 于应用程序自行处理的信号来说,信号的生命周期要经过信号的安装登记、信号集操作、信号的发送和信号的处理四个阶段。
1、信号的安装登记指的是在应用程序中, 安装对此信号的处理方法。
2、信号集操作的作用是用于对指定的一个或多个信号进行信号屏蔽,此阶段对有些应用程序来说并不需要。
3、信号的发送指的是发送信号,可 以通过硬件(如在终端上按下Ctrl-C)发送的信号和软件(如通过kill函数)发送的信号。
4、信号的处理指的是操作系统对接收信号进程的处理,处理方法 是先检查信号集操作函数是否对此信号进行屏蔽,如果没有屏蔽,操作系统将按信号安装函数中登记注册的处理函数完成对此进程的处理。

安装登记

首先要建立其信号和进程的对应关系,这就是信号的安装登记。

Linux 主要有两个函数实现信号的安装登记:signal和sigaction。其中signal在系统调用的基础上实现,是库函数。它只有两个参数,不支持信号 传递信息,主要是用于前32个非实时信号的安装;而sigaction是较新的函数(由两个系统调用实现:sys_signal以及 sys_rt_sigaction),有三个参数,支持信号传递信息,主要用来与sigqueue系统调用配合使用。当然,sigaction同样支持非实时信号的安装,sigaction优于signal主要体现在支持信号带有参数。

另外,sigaction()是POSIX的信号接口,而signal()是标准C的信号接口(如果程序必须在非POSIX系统上运行,那么就应该使用这个接口)

signal

在signal函数中,有两个形参,分别代表需要处理的信号编号值和处理信号函数的指针。它主要是用于前32种非实时信号的处理,不支持信号的传递信息。但是由于使用简单,易于理解,因此在许多场合被程序员使用。

对 于Unix系统来说,使用signal函数时,自定义处理信号函数执行一次后失效,对该信号的处理回到默认处理方式。 下面以一个例子进行说明:
例如一程序 中使用signal(SIGQUIT, my_func)函数调用,其中my_func是自定义函数。应用进程收到SIGQUIT信号时,会跳转到自定义处理信号函数my_func处执行,执行 后信号注册函数my_func失效,对SIGQUIT信号的处理回到操作系统的默认处理方式,当应用进程再次收到SIGQUIT信号时,会按操作系统默认 的处理方式进行处理(即不再执行my_func处理函数)。

而在Linux系统中,signal函数已被改写,由sigaction函数封装实现,则不存 在上述问题。

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

void my_func(int sig_no)
{
        if(sig_no == SIGINT)
                printf("I have get SIGINT\n");
        else if(sig_no == SIGQUIT)
                printf("I have get SIGQUIT\n");
}

int main()
{
        signal(SIGINT,my_func);
        signal(SIGQUIT,my_func);
        pause();		//pause使调用进程(或线程)休眠,直到接受到终止进程的信号,或者接收到信号并从信号捕获函数中返回时,pause才返回
        pause();
        pause();
        return 0;
}
sigaction

sigaction函数用来查询和设置信号处理方式,它是用来替换早期的signal函数。

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

void my_func(int sig_no)
{
	if(sig_no == SIGINT)
		printf("I have get SIGINT\n");
	else if(sig_no == SIGQUIT)
		printf("I have get SIGQUIT\n");
}

int main()
{
	struct sigaction act; 
//	signal(SIGINT,my_func);
//	signal(SIGQUIT,my_func);

	sigemptyset(&act.sa_mask); 
    	act.sa_flags=NULL; 		//可以设置为SA_SIGINFO,信号处理函数是带有三个参数的sa_sigaction
    	act.sa_sigaction=my_func; 

    	if(sigaction(SIGQUIT,&act,NULL) < 0) 
    	{ 
        	perror("install sigal error"); 
        	return -1 ;
    	}
    	if(sigaction(SIGINT,&act,NULL) < 0) 
    	{ 
        	perror("install2 sigal error"); 
        	return -1 ;
    	}
	pause();
	pause();
	pause();
	return 0;
}

信号集操作函数

由 于有时需要把多个信号当作一个集合进行处理,这样信号集就产生了,信号集用来描述一类信号的集合,Linux所支持的信号可以全部或部分的出现在信号集 中。信号集操作函数最常用的地方就是用于信号屏蔽。比如有时候希望某个进程正确执行,而不想进程受到一些信号的影响,此时就需要用到信号集操作函数完成对 这些信号的屏蔽。

信号集操作函数按照功能和使用顺序分为三类,分别为创建信号集函数,设置信号屏蔽位函数和查询被搁置(未决)的信号函数。
创建信号集函数只是创建一个信号的集合
设置信号屏蔽位函数对指定信号集中的信号进行屏蔽
查询被搁置的信号函数是用来查询当前“未决”的信号集。

信号集函数组并不能完成信号的安装登记 工作,信号的安装登记需要通过sigaction函数或signal函数来完成。

查询被搁置的信号是信号处理的后续步骤,但不是必需的。由于有时进程在某时间段内要求阻塞一些信号,程序完成特定工作后解除对该信号阻塞,这个时间段内被阻 塞的信号称为“未决”信号。这些信号已经产生,但没有被处理,sigpending函数用来检测进程的这些“未决”信号,并进一步决定对它们做何种处理 (包括不处理)。

创建信号集函数

创建信号集函数有如下5个:
1、 sigemptyset:初始化信号集合为空。
2、 sigfillset:把所有信号加入到集合中,信号集中将包含Linux支持的64种信号。
3、sigaddset:将指定信号加入到信号集合中去。
4、sigdelset:将指定信号从信号集中删去。
5、sigismember:查询指定信号是否在信号集合之中。

设置信号屏蔽位函数

每个进程都有一个用来描述哪些信号递送到进程时将被阻塞的信号集,该信号集中的所有信号在递送到进程后都将被阻塞。调用函数sigprocmask可设定信号集内的信号阻塞或不阻塞。

查询被搁置(未决)信号函数

sigpending函数用来查询“未决”信号

总结

对信号集操作函数的使用方法
对信号集操作函数的使用方法和顺序如下:
① 使用signal或sigaction函数安装和登记信号的处理。
② 使用sigemptyset等定义信号集函数完成对信号集的定义。
③ 使用sigprocmask函数设置信号屏蔽位。
④ 使用sigpending函数检测未决信号,非必需步骤。

三、多线程下信号的使用

1、多线程pthread常用接口

在讲多线程前,先来回顾下pthread中常用的接口

    pthread_create                       创建一个线程    
    pthread_exit                           退出线程                
    pthread_self                           获取线程ID                
    pthread_equal                        检查两个线程ID是否相等                        
    pthread_join                           等待线程退出            
    pthread_detach                      设置线程状态为分离状态                        
    pthread_cancel                      线程的取消                                    
    pthread_cleanup_push          线程退出, 注册清理函数                            
    pthread_cleanup_pop            线程退出, 执行清理函数     

2、多进程和多线程下的信号

在Linux的多线程中使用信号机制,与在进程中使用信号机制有着根本的区别,可以说是完全不同。
在进程环境中,对信号的处理是,先注册信号处理函数,当信号异步发生时,调用处理函数来处理信号。它完全是异步的(我们完全不知到信号会在进程的那个执行点到来!)。
然而信号处理函数的实现,有着许多的限制;比如有一些函数不能在信号处理函数中调用;再比如一些函数read、recv等调用时会被异步的信号给中断(interrupt),因此我们必须对在这些函数在调用时因为信号而中断的情况进行处理(判断函数返回时 enno 是否等于 EINTR)。

但是在多线程中处理信号的原则却完全不同,它的基本原则是:将对信号的异步处理,转换成同步处理,也就是说用一个线程专门的来“同步等待”信号的到来,而其它的线程可以完全不被该信号中断/打断(interrupt)。这样就在相当程度上简化了在多线程环境中对信号的处理。而且可以保证其它的线程不受信号的影响。这样我们对信号就可以完全预测,因为它不再是异步的,而是同步的(我们完全知道信号会在哪个线程中的哪个执行点到来而被处理!)。而同步的编程模式总是比异步的编程模式简单。其实多线程相比于多进程的其中一个优点就是:多线程可以将进程中异步的东西转换成同步的来处理。

3、线程中信号使用的相关接口

1、sigwait函数
sigwait - wait for a signal
 
#include <signal.h>
int sigwait(const sigset_t *set, int *sig);

从上面的man sigwait的描述中,我们知道:sigwait是同步的等待信号的到来,而不是像进程中那样是异步的等待信号的到来。sigwait函数使用一个信号集作为他的参数,并且在集合中的任一个信号发生时返回该信号值,解除阻塞,然后可以针对该信号进行一些相应的处理。

在多线程代码中,总是使用sigwait或者sigwaitinfo或者sigtimedwait等函数来处理信号。而不是signal或者sigaction等函数。因为在一个线程中调用signal或者sigaction等函数会改变所有线程中的信号处理函数。而不是仅仅改变调用signal/sigaction的那个线程的信号处理函数。

2、pthread_sigmask函数

每个线程均有自己的信号屏蔽集(信号掩码),可以使用pthread_sigmask函数来屏蔽某个线程对某些信号的响应处理,仅留下需要处理该信号的线程来处理指定的信号。实现方式是:利用线程信号屏蔽集的继承关系(在主进程中对sigmask进行设置后,主进程创建出来的线程将继承主进程的掩码)
在使用sigmask后,该进程/线程不会在响应这个信号,也就是不会再异步处理,只能使用sigwait进行同步处理

#include <signal.h>
int pthread_sigmask(inthow, const sigset_t *set, sigset_t *oldset);
3、pthread_kill函数

在多线程程序中,一个线程可以使用pthread_kill对同一个进程中指定的线程(包括自己)发送信号。注意在多线程中一般不使用kill函数发送信号,因为kill是对进程发送信号,结果是:正在运行的线程会处理该信号,如果该线程没有注册信号处理函数,那么会导致整个进程退出。

#include <signal.h>
int pthread_kill(pthread_tthread, intsig);
4、调用sigwait同步等待的信号必须在调用线程中被屏蔽

最好在所有的线程中被屏蔽,这样可以保证信号绝不会被送到除了调用sigwait的任何其它线程,这是通过利用信号掩码的继承关系来达到的。

多线程中信号使用demo

1、sigwait阻塞示例
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
 
/* Simpleerror handling functions*/
 
#define handle_error_en(en, msg)\
        do { errno= en; perror(msg);exit(EXIT_FAILURE);}while(0)
 
static void * sig_thread(void*arg)
{
      sigset_t *set=(sigset_t*) arg;
      int s, sig;
 
      for (;;){
            s = sigwait(set,&sig);	//正常返回0,失败时返回正错误号
            if (s != 0)
                  handle_error_en(s,"sigwait");
            printf("Signal handling thread got signal %d\n", sig);
      }
}
 
int main(int argc, char*argv[])
{
      pthread_t thread;
      sigset_t set;
      int s;
 
      /* 
         Block SIGINT; other threads created by main() will inherit
         a copy of the signal mask. 
       */
      sigemptyset(&set);
      sigaddset(&set, SIGQUIT);
      sigaddset(&set, SIGALRM);
      s = pthread_sigmask(SIG_BLOCK,&set,NULL);
      if (s!= 0)
            handle_error_en(s,"pthread_sigmask");
      s = pthread_create(&thread,NULL,&sig_thread,(void*)&set);
      if (s!= 0)
            handle_error_en(s,"pthread_create");
      /* 
        Main thread carries on to create other threads and/ordo
        other work 
       */
      pause();/* Dummy pause so we can test program*/
      return 0;
}
2、信号在多线程中通讯实例
#include <pthread.h>
#include <stdio.h>
#include <sys/signal.h>
#define NUMTHREADS 3
void sighand(int signo);
void *threadfunc(void *parm)
{
    pthread_t             tid = pthread_self();
    int                   rc;
    printf("Thread %lu entered\n", tid);
    rc = sleep(6);
    printf("Thread %lu did not get expected results! rc=%d\n", tid, rc);
    return NULL;
}
void *threadmasked(void *parm)
{
    pthread_t             tid = pthread_self();
    sigset_t              mask;
    int                   rc;
    printf("Masked thread %lu entered\n", tid);
    sigfillset(&mask); /* Mask all allowed signals */
//    sigemptyset(&mask);
    rc = pthread_sigmask(SIG_BLOCK, &mask, NULL);
    if (rc != 0)
    {
        printf("%d, %s\n", rc, strerror(rc));
        return NULL;
    }
    rc = sleep(5);
    if (rc != 0)
    {
        printf("Masked thread %lu did not get expected results! ""rc=%d \n",tid, rc);
        return NULL;
    }
//    sigwait(&mask,&rc);
    printf("Masked thread %lu completed masked work\n",tid);
    return NULL;
}
int main(int argc, char **argv)
{
    int                     rc;
    int                     i;
    struct sigaction        actions;
    pthread_t               threads[NUMTHREADS];
    pthread_t               maskedthreads[NUMTHREADS];
    printf("Enter Testcase - %s\n", argv[0]);
    printf("Set up the alarm handler for the process\n");
    memset(&actions, 0, sizeof(actions));
    sigemptyset(&actions.sa_mask);
    actions.sa_flags = 0;
    actions.sa_handler = sighand;
    rc = sigaction(SIGALRM,&actions,NULL);
    printf("Create masked and unmasked threads\n");
    for(i=0; i<NUMTHREADS; ++i)
    {
        rc = pthread_create(&threads[i], NULL, threadfunc, NULL);
        if (rc != 0)
        {
            printf("%d, %s\n", rc, strerror(rc));
            return -1;
        }
        rc = pthread_create(&maskedthreads[i], NULL, threadmasked, NULL);
        if (rc != 0)
        {
            printf("%d, %s\n", rc, strerror(rc));
            return -1;
        }
    }
    sleep(5);
    printf("Send a signal to masked and unmasked threads\n");
    for(i=0; i<NUMTHREADS; ++i)
    {
        rc = pthread_kill(threads[i], SIGALRM);
        rc = pthread_kill(maskedthreads[i], SIGALRM);
    }
    printf("Wait for masked and unmasked threads to complete\n");
    for(i=0; i<NUMTHREADS; ++i) {
        rc = pthread_join(threads[i], NULL);
        rc = pthread_join(maskedthreads[i], NULL);
    }
    printf("Main completed\n");
    return 0;
}
void sighand(int signo)
{
    pthread_t             tid = pthread_self();
    printf("Thread %lu in signal handler\n",tid);
    return;
 
}

运行结果

Set up the alarm handler for the process
Create masked and unmasked threads
Thread 139911426180864 entered
Masked thread 139911417788160 entered
Thread 139911409395456 entered
Masked thread 139911401002752 entered
Thread 139911392610048 entered
Masked thread 139911384217344 entered
Send a signal to masked and unmasked threads
Masked thread 139911401002752 completed masked work
Masked thread 139911384217344 completed masked work
Masked thread 139911417788160 completed masked work
Thread 139911409395456 in signal handler
Thread 139911409395456 did not get expected results! rc=0
Thread 139911392610048 in signal handler
Thread 139911392610048 did not get expected results! rc=0
Thread 139911426180864 in signal handler
Thread 139911426180864 did not get expected results! rc=0
Wait for masked and unmasked threads to complete
Main completed

可以看到,threads中能正常响应注册的SIGALRM信号,异步响应,而maskedthreads不能响应,这时如果加上sigwait

3、多线程同步接收信号
#include <pthread.h>
#include <stdio.h>
#include <sys/signal.h>
#define NUMTHREADS 3
void sighand(int signo);
void *threadfunc(void *parm)
{
    pthread_t             tid = pthread_self();
    int                   rc;
    printf("Thread %lu entered\n", tid);
    rc = sleep(6);
    printf("Thread %lu did not get expected results! rc=%d\n", tid, rc);
    return NULL;
}
void *threadmasked(void *parm)
{
    pthread_t             tid = pthread_self();
    sigset_t              mask;
    int                   rc,sig;
    sigset_t 		  *set = (sigset_t*) parm;
    printf("Masked thread %lu entered\n", tid);
    sigfillset(&mask); /* Mask all allowed signals */
//    sigemptyset(&mask);
    rc = pthread_sigmask(SIG_BLOCK, &mask, NULL);
    if (rc != 0)
    {
        printf("%d, %s\n", rc, strerror(rc));
        return NULL;
    }
    rc = sleep(5);
    if (rc != 0)
    {
        printf("Masked thread %lu did not get expected results! ""rc=%d \n",tid, rc);
        return NULL;
    }
            rc = sigwait(set,&sig);
           if (rc != 0)
	         printf("sigwait error %d\n",rc);
	   else
                 printf("Signal handling thread got signal %d\n", sig);
    printf("Masked thread %lu completed masked work\n",tid);
    return NULL;
}
int main(int argc, char **argv)
{
    int                     rc;
    int                     i;
    sigset_t 		    set;
    struct sigaction        actions;
    pthread_t               threads[NUMTHREADS];
    pthread_t               maskedthreads[NUMTHREADS];

    sigemptyset(&set);
    sigaddset(&set, SIGALRM);
    printf("Enter Testcase - %s\n", argv[0]);
    printf("Set up the alarm handler for the process\n");
    memset(&actions, 0, sizeof(actions));
    sigemptyset(&actions.sa_mask);
    actions.sa_flags = 0;
    actions.sa_handler = sighand;
    rc = sigaction(SIGALRM,&actions,NULL);
    printf("Create masked and unmasked threads\n");
    for(i=0; i<NUMTHREADS; ++i)
    {
        rc = pthread_create(&threads[i], NULL, threadfunc, NULL);
        if (rc != 0)
        {
            printf("%d, %s\n", rc, strerror(rc));
            return -1;
        }
        rc = pthread_create(&maskedthreads[i], NULL, threadmasked, (void*)&set);
        if (rc != 0)
        {
            printf("%d, %s\n", rc, strerror(rc));
            return -1;
        }
    }
    sleep(5);
    printf("Send a signal to masked and unmasked threads\n");
    for(i=0; i<NUMTHREADS; ++i)
    {
        rc = pthread_kill(threads[i], SIGALRM);
        rc = pthread_kill(maskedthreads[i], SIGALRM);
    }
    printf("Wait for masked and unmasked threads to complete\n");
    for(i=0; i<NUMTHREADS; ++i) {
        rc = pthread_join(threads[i], NULL);
        rc = pthread_join(maskedthreads[i], NULL);
    }
    printf("Main completed\n");
    return 0;
}
void sighand(int signo)
{
    pthread_t             tid = pthread_self();
    printf("Thread %lu in signal handler\n",tid);
    return;
 
}

运行结果

Enter Testcase - ./a.out
Set up the alarm handler for the process
Create masked and unmasked threads
Masked thread 140198030653184 entered
Thread 140198039045888 entered
Thread 140198022260480 entered
Thread 140198005475072 entered
Masked thread 140198013867776 entered
Masked thread 140197997082368 entered
Send a signal to masked and unmasked threads
Wait for masked and unmasked threads to complete
Signal handling thread got signal 14
Masked thread 140198013867776 completed masked work
Thread 140198005475072 in signal handler
Thread 140198005475072 did not get expected results! rc=0
Thread 140198039045888 in signal handler
Thread 140198039045888 did not get expected results! rc=0
Thread 140198022260480 in signal handler
Thread 140198022260480 did not get expected results! rc=0
Signal handling thread got signal 14
Masked thread 140198030653184 completed masked work
Signal handling thread got signal 14
Masked thread 140197997082368 completed masked work
Main completed

对比上面的结果,再Masked thread中也能接收到此信号

由上面可知,线程中的信号处理机制和进程中的信号处理机制是不同的,或者说是完全不同的,一般在多线程中使用信号的,或单独启动一个线程类处理信号,主要就是使用sigwait同步等待信号;而在进程中的信号机制没有这么复杂,进程中只需要注册一个函数,就静静的等待信号处理。不过在进程中使用的方法也是可以在线程中使用的。

上一篇:4.5 rust the Reference Counted Smart Pointer


下一篇:centos7之添加开机启动服务/脚本