C + + Advanced Learning -- signal processing under multithreading / multiprocessing

Posted by benyamin on Mon, 21 Feb 2022 15:49:09 +0100

catalogue

1, Introduction

2, Use of signals in multi process

------>2.1 signal processing flow
------>2.2 installation registration
------>2.3 signal set operation function
------>2.4. Set signal shielding bit function
------>2.5. Query the shelved (pending) signal function
------>2.6 summary

3, Use of signals under multithreading

------>3.1 common interfaces of multithreaded pthread
------>3.2 signals under multi process and multi thread
------>3.3 relevant interfaces used by signals in threads
------>3.4 signal usage demo in multithreading
------------>3.4.1 sigwait blocking example
------------>3.4.2 example of signal communication in multithreading
------------>3.4.3 multi thread synchronous receiving signal

1, Introduction

This article introduces the use of signals in multi process and multi thread in detail

2, Use of signals in multi process

Signals are associated with certain processes. In other words, a process can decide which signals are processed in the process. For example, a process can ignore some signals and only process others; In addition, a process can choose how to process signals. In short, these are always associated with specific processes.

Signal processing flow

For the signal processed by the application program, the life cycle of the signal should go through four stages: signal installation and registration, signal set operation, signal transmission and signal processing.
1. Signal installation registration refers to the installation of the processing method of this signal in the application program.
2. The function of signal set operation is to signal mask one or more specified signals. This stage is not required for some applications.
3. Signal transmission refers to the transmission signal, which can be transmitted by hardware (such as pressing Ctrl-C on the terminal) and software (such as through kill function).
4. Signal processing refers to the processing of the receiving signal process by the operating system. The processing method is to first check whether the signal set operation function shields the signal. If not, the operating system will complete the processing of the process according to the processing function registered in the signal installation function.

Installation registration

First of all, the corresponding relationship between the signal and the process should be established, which is the installation registration of the signal.

Linux mainly has two functions to realize the installation and registration of signals: signal and signal action. Signal is implemented on the basis of system call and is a library function. It has only two parameters and does not support signal transmission information. It is mainly used for the installation of the first 32 non real-time signals; Sigaction is a relatively new function (implemented by two system calls: sys_signal and sys_rt_sigaction). It has three parameters and supports signal transmission information. It is mainly used in conjunction with sigqueue system call. Of course, sigaction also supports the installation of non real-time signals. The advantage of sigaction over signal is mainly reflected in the support of signals with parameters.

In addition, sigaction() is the signal interface of POSIX, and signal() is the signal interface of standard C (this interface should be used if the program must run on a non POSIX system)

signal

In the signal function, there are two formal parameters, representing the signal number value to be processed and the pointer of the signal processing function. It is mainly used for the processing of the first 32 non real-time signals and does not support the transmission of signal information. But because it is easy to use and easy to understand, it is used by programmers on many occasions.

For Unix system, when signal function is used, the user-defined signal processing function fails after one execution, and the processing of the signal returns to the default processing mode. The following is an example:
For example, a program uses the signal(SIGQUIT, my_func) function call, where my_ Func is a custom function. When the application process receives the SIGQUIT signal, it will jump to the custom signal processing function_ Execute at func and register the function my after execution_ Func fails, and the processing of SIGQUIT signal returns to the default processing mode of the operating system. When the application process receives the SIGQUIT signal again, it will process according to the default processing mode of the operating system (that is, it will no longer execute my_func processing function).

In the Linux system, the signal function has been rewritten and encapsulated by the sigaction function, there is no such problem.

#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 causes the calling process (or thread) to sleep until it receives a signal to terminate the process, or receives the signal and returns from the signal capture function
        pause();
        pause();
        return 0;
}
sigaction

The signalaction function is used to query and set the signal processing mode. It is used to replace the early signal function.

#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; 		//Can be set to SA_SIGINFO, the signal processing function is SA with three parameters_ 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;
}

Signal set operation function

Sometimes it is necessary to treat multiple signals as a set, so the signal set is generated. The signal set is used to describe the set of a class of signals. The signals supported by Linux can appear in the signal set in whole or in part. The signal set operation function is most commonly used for signal shielding. For example, sometimes you want a process to execute correctly without the process being affected by some signals. At this time, you need to use the signal set operation function to shield these signals.

The signal set operation functions are divided into three categories according to the function and use order: creating the signal set function, setting the signal shielding bit function and querying the shelved (pending) signal function.
The create signal set function simply creates a set of signals
Set the signal shielding bit function to shield the signal in the specified signal set
The query shelved signal function is used to query the current "pending" signal set.

The signal set function group cannot complete the installation and registration of signals. The installation and registration of signals needs to be completed through the sigaction function or signal function.

Querying the shelved signal is a subsequent step of signal processing, but it is not necessary. Because sometimes the process requires to block some signals in a certain period of time, the program will unblock the signal after completing a specific work. The blocked signals in this period of time are called "pending" signals. These signals have been generated but have not been processed. The sigpending function is used to detect these "pending" signals of the process and further decide what to do with them (including not).

Create signal set function

There are five functions to create a signal set:
1. sigemptyset: initialization signal set is empty.
2. sigfillset: add all signals to the set. The signal set will contain 64 signals supported by Linux.
3. sigaddset: add the specified signal to the signal set.
4. sigdelset: delete the specified signal from the signal set.
5. sigismember: query whether the specified signal is in the signal set.

Set signal mask bit function

Each process has a signal set that describes which signals will be blocked when delivered to the process. All signals in the signal set will be blocked after being delivered to the process. Call the function sigprocmask to set whether the signal in the signal set is blocked or not.

Query pending signal function

The sigpending function is used to query for "pending" signals

summary

How to use the signal set operation function
The use method and sequence of signal set operation functions are as follows:
① Use the signal or sigaction function to install and register the signal processing.
② Use sigemptyset and other functions to define the signal set to complete the definition of the signal set.
③ Use the sigprocmask function to set the signal mask bit.
④ Using the sigpending function to detect pending signals is not a required step.

3, Use of signals under multithreading

1. Common interfaces of multithreaded pthread

Before talking about multithreading, let's review the interfaces commonly used in pthread

    pthread_create                       Create a thread    
    pthread_exit                           Exit thread                
    pthread_self                           Get thread ID                
    pthread_equal                        Check two threads ID Are they equal                        
    pthread_join                           Wait for the thread to exit            
    pthread_detach                      Set thread state to detached state                        
    pthread_cancel                      Thread cancellation                                    
    pthread_cleanup_push          Thread exit, Register cleanup function                            
    pthread_cleanup_pop            Thread exit, Execute cleanup function     

2. Signals under multi process and multi thread

Using signaling mechanism in Linux multithreading is fundamentally different from using signaling mechanism in process, which can be said to be completely different.
In the process environment, the signal processing is to register the signal processing function first. When the signal occurs asynchronously, the processing function is called to process the signal. It is completely asynchronous (we have no idea that the signal will arrive at the execution point of the process!).
However, the realization of signal processing function has many limitations; For example, some functions can not be called in the signal processing function. For another example, some functions such as read and recv will be interrupted by asynchronous signals when they are called. Therefore, we must deal with the interruption of these functions due to signals when they are called (judge whether enno is equal to EINTR when the function returns).

However, the principle of signal processing in multithreading is completely different. Its basic principle is to convert the asynchronous processing of signals into synchronous processing, that is, one thread is specially used to "synchronously wait" for the arrival of signals, while other threads can not be interrupted by the signal at all. This simplifies signal processing in a multithreaded environment to a considerable extent. And it can ensure that other threads are not affected by the signal. In this way, we can fully predict the signal, because it is no longer asynchronous, but synchronous (we fully know which execution point in which thread the signal will arrive and be processed!). The synchronous programming mode is always simpler than the asynchronous programming mode. In fact, one of the advantages of multithreading over multiprocessing is that multithreading can convert asynchronous things in the process into synchronous ones.

3. Related interfaces used by signals in threads

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

From the above description of man sigwait, we know that sigwait is synchronous waiting for the arrival of the signal, rather than asynchronous waiting for the arrival of the signal as in the process. Sigwait function uses a signal set as its parameter, and returns the signal value when any signal in the set occurs, unblocks, and then carries out some corresponding processing for the signal.

In multithreaded code, functions such as sigwait or sigwaitinfo or sigtimedwait are always used to process signals. Instead of functions such as signal or sigaction. Because calling functions such as signal or sigaction in a thread will change the signal processing functions in all threads. Instead of just changing the signal processing function of the thread calling signal/sigaction.

2,pthread_sigmask function

Each thread has its own signal mask set (signal mask), and pthread can be used_ Sigmask function to mask the response processing of a thread to some signals, leaving only the thread that needs to process the signal to process the specified signal. The implementation method is to use the inheritance relationship of thread signal mask set (after setting sigmask in the main process, the thread created by the main process will inherit the mask of the main process)
After using sigmask, the process / thread will not respond to this signal, that is, it will not process asynchronously, and can only use sigwait for synchronous processing

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

In a multithreaded program, a thread can use pthread_kill sends a signal to a specified thread (including itself) in the same process. Note that in multithreading, the kill function is generally not used to send signals, because kill sends signals to the process. As a result, the running thread will process the signal. If the thread does not register the signal processing function, the whole process will exit.

#include <signal.h>
int pthread_kill(pthread_tthread, intsig);
4. The signal of calling sigwait synchronous wait must be masked in the calling thread

It is best to be shielded in all threads, so as to ensure that the signal will never be sent to any other thread except those calling sigwait. This is achieved by using the inheritance relationship of signal mask.

Signal usage demo in multithreading

1. sigwait blocking example
#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);	//It returns 0 normally and positive error number in case of failure
            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. Example of signal communication in multithreading
#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;
 
}

Operation results

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

It can be seen that threads can normally respond to the registered SIGALRM signal and respond asynchronously, while maskedthreads cannot respond. In this case, if sigwait is added

3. Multi thread synchronous receiving signal
#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;
 
}

Operation results

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

Compared with the above results, this signal can also be received in Masked thread

It can be seen from the above that the signal processing mechanism in the thread is different from that in the process, or it is completely different. Generally, signals are used in multiple threads, or a thread class is started to process signals separately, mainly using sigwait to synchronously wait for signals; The signal mechanism in the process is not so complex. In the process, you only need to register a function and wait for signal processing quietly. However, the methods used in the process can also be used in the thread.

Topics: C++