With regard to the reentrant nature of code, design developers generally consider thread safety only, and the security of asynchronous signal processing functions is often ignored. This article first describes how to write a secure asynchronous signal processing function, and then illustrates how to build the model in multithreaded applications so that asynchronous signals are handled synchronously in the specified thread.
Write a secure signal processing function in the application
In developing multithreaded applications, developers generally consider thread safety, which is used pthread_mutex
to protect global variables. If the signal is used in the application, and the signal is generated not because the program is running wrong, but the program logic needs, such as SIGUSR1, sigrtmin, etc., the signal is processed after the application will run normally. When writing such signal processing functions, application-level developers tend to ignore the context of signal processing function execution, and do not consider some rules for writing secure signal processing functions. This paper first introduces some rules to be considered when writing signal processing functions, and then illustrates how to build models in multithreaded applications so that asynchronous signals generated by the program logic need to be processed synchronously in the specified thread.
Threads and Signals
In a Linux multithreaded application, each thread can pthread_sigmask()
set the signal mask for this thread by calling. In general, blocked signals will not interrupt the execution of this thread unless the signal is generated because the program is running in an error such as SIGSEGV, and the signal SIGKILL and SIGSTOP that cannot be ignored is not blocked.
When a thread invokes the creation of a pthread_create()
new thread, the signal mask for this thread is inherited by the newly created thread.
The POSIX.1 standard defines the interface for a series of threading functions, the POSIX threads (Pthreads). The Linux C Library provides two implementations of threading: Linuxthreads and NPTL (Native POSIX Threads Library). Linuxthreads is obsolete, some implementations of functions do not follow the POSIX.1 specification. NPTL relies on the Linux 2.6 kernel for more POSIX compliance. 1 specification, but it is not fully followed.
With NPTL-based line libraries, each thread in a multithreaded application has its own unique thread ID and shares the same process ID. An application can send a signal to a process by calling it, and kill(getpid(),signo)
if the currently executing thread in the process does not block the signal, it will be interrupted and the line number handler will execute in the context of the thread. The application can also send pthread_kill(pthread_t thread, int sig)
a signal to the specified thread by calling, and the line number handler executes in the context of the specified thread.
With Linuxthreads-based line libraries, each thread in a multithreaded application has its own unique process ID, and getpid()
calls in different threads return different values, so it is not possible kill(getpid(),signo)
to send the signal to the entire process by calling it.
The synchronous processing of asynchronous signals in a specified thread, described below, is based on the Linux C library using NPTL. Please refer to the "Linux threading model Comparison: Linuxthreads and NPTL" and "pthreads (7)-Linux man page" to learn more about the threading model of Linux and the support of different versions of Linux C libraries for NPTL.
Writing a secure asynchronous signal processing function
The signal can be generated by:
- The user terminates the program from the control terminal, such as CTRK + C to generate SIGINT;
- When the program runs in error, the hardware generates a signal, such as accessing an illegal address to produce SIGSEGV;
- Program run logic needs, such as call
kill
, raise
generate signal.
because the signal is an asynchronous event, the context in which the signal processing function executes is indeterminate, such as when a thread may be interrupted by a signal when it calls a library function, and the library function returns in advance and executes the signal processing function instead . For the third kind of signal generation, the signal is generated, processed, the application will not terminate, or will continue to function normally, in writing such signal processing functions, especially need to be careful, so as not to disrupt the normal operation of the application.
There are several rules for writing secure signal processing functions:
- The signal processing function performs only simple operations such as setting an external variable, and other complex operations are left outside the signal processing function.
errno
is thread-safe, that is, each thread has its own errno
, but not asynchronous, signal security. If the signal processing function is more complex and calls a errno
library function that may change the value, you must consider recovering the value of the interrupted thread at the beginning of the signal processing function, when it is saved and ended errno
;
- The signal processing function can only call the C library functions that are reentrant, such as the inability to call
malloc(),free()
and standard I/O library functions;
- Signal processing function If you need access to a global variable, you must declare it to avoid compiler inappropriate optimizations when defining this global variable
volatile,
.
From the point of view of the entire Linux application, because of the asynchronous signal used in the application, some library functions in the program may be interrupted by an asynchronous signal at the time of invocation, at which point the errno
library function calls must be considered for error recovery after signal interruption, such as the read operation in socket programming:
Rlen = recv (sock_fd, buf, Len, msg_waitall); if ((Rlen = =-1) && (errno = = eintr)) { //This kind of error are recoverable, we can set the offset change ' Rlen ' as 0 and continue to recv }
Back to top of page
Processes asynchronous signals synchronously in a specified thread
As mentioned above, not only is writing a secure asynchronous signal processing function itself has a lot of rules, and other places in the application when calling a library function that can be interrupted, you need to consider the error recovery processing after being interrupted. This complicates the programming of the program, and fortunately, the POSIX.1 specification defines sigwait()、 sigwaitinfo()
and pthread_sigmask()
interfaces that can be implemented:
- Processing asynchronous signals in a synchronous manner;
- Processes the signal in the specified thread.
This model, which processes the signals synchronously in the specified thread, avoids the uncertainty and potential danger that the program will run due to the processing of the asynchronous signal.
Sigwait
sigwait()
Provides a mechanism to wait for a signal to come in and take a signal out of the signal queue in a serial manner. sigwait(
waits only for the set of signals specified in the function parameter, that is, if the newly generated signal is not within the specified set of signals, sigwait()
continue waiting. For a stable and reliable program, we generally have some questions:
- Can multiple identical signals be queued in the signal queue?
- If there are multiple signals waiting in the signal queue, is there a priority rule in signal processing?
- Is there a difference between real-time and non-real-time signals in processing?
The author has written a short test procedure to test sigwait
some rules during signal processing.
Listing 1. Sigwait_test.c
#include <signal.h> #include <errno.h> #include <pthread.h> #include <unistd.h> #include < Sys/types.h>void sig_handler (int signum) {printf ("Receive signal. %d\n ", Signum);} void* Sigmgr_thread () {sigset_t waitset, oset; int sig; int RC; pthread_t ppid = Pthread_self (); Pthread_detach (PPID); Sigemptyset (&waitset); Sigaddset (&waitset, sigrtmin); Sigaddset (&waitset, sigrtmin+2); Sigaddset (&waitset, Sigrtmax); Sigaddset (&waitset, SIGUSR1); Sigaddset (&waitset, SIGUSR2); while (1) {rc = Sigwait (&waitset, &sig); if (rc! =-1) {Sig_handler (SIG); } else {printf ("sigwaitinfo () returned err:%d; %s\n ", errno, Strerror (errno)); }}}int Main () {sigset_t bset, oset; int i; pid_t pid = Getpid (); pthread_t Ppid; Sigemptyset (&bset); Sigaddset (&bset, sigrtmin); Sigaddset (&bset, sigrtmin+2); Sigaddset (&bset, Sigrtmax); Sigaddset (&bset, SIGUSR1); Sigaddset (&bset, SIGUSR2); if (Pthread_sigmask (Sig_block, &bset, &oset)! = 0) printf ("!! Set pthread Mask failed\n "); Kill (PID, Sigrtmax); Kill (PID, Sigrtmax); Kill (PID, sigrtmin+2); Kill (PID, sigrtmin); Kill (PID, sigrtmin+2); Kill (PID, sigrtmin); Kill (PID, SIGUSR2); Kill (PID, SIGUSR2); Kill (PID, SIGUSR1); Kill (PID, SIGUSR1); Create the dedicated thread Sigmgr_thread () which would handle signals synchronously pthread_create (&ppid, NULL, Sigmgr_thread, NULL); Sleep (10); Exit (0);}
The results of the program compilation run in RHEL4 are as follows:
Figure 1. Sigwait test Program Execution results
The following rules are found from the above test program:
- For non-real-time signals, the same signal cannot be queued in the signal queue, and for real-time signals, the same signal can be queued in the signal queue.
- If there are multiple real-time and non-real-time signals queued in the signal queue, the real-time signal is not taken out before the non-real-time signal, the small number of signals will be taken out first: if SIGUSR1 (10) will precede SIGUSR2, Sigrtmin (34) will precede Sigrtmax (64), The non-real-time signal is removed before the real-time signal because its signal is small.
sigwaitinfo()
Functions sigtimedwait()
Similar to functions are also provided sigwait()
.
Signal processing model in multi-threaded application of Linux
In a Linux-based multithreaded application, the call is processed using a synchronous model for signals generated as a result of program logic needs sigwait()
. The program flow is as follows:
- The main thread sets a signal mask that blocks the signal that you want to synchronize, and the signal mask of the main thread is inherited by the thread it creates;
- The main thread creates a signal processing thread, and the signal processing thread sets the signal set to
sigwait()
the first parameter to be processed synchronously.
- The main thread creates the worker thread.
Figure 2. Example of a model code that synchronously processes asynchronous signals in a specified thread
The following is a complete program that processes asynchronous signals synchronously in a specified thread.
The main thread sets the signal mask to block SIGUSR1 and sigrtmin two signals and then creates a signal processing thread sigmgr_thread()
and five worker threads worker_thread()
. The main thread calls the process every 10 seconds kill()
to send SIGUSR1 and sigtrmin signals. The signal processing thread sigmgr_thread()
calls the signal processing function when the signal is received sig_handler()
.
Program Compile:gcc -o signal_sync signal_sync.c -lpthread
Program execution:./signal_sync
From the program execution output, you can see that all signals emitted by the main thread are received by the specified signal processing thread and processed in a synchronous manner.
Listing 2. Signal_sync.c
#include <signal.h> #include <errno.h> #include <pthread.h> #include <unistd.h> #include < sys/types.h> void Sig_handler (int signum) {static int j = 0; static int k = 0; pthread_t sig_ppid = Pthread_self (); Used to show which thread the signal was handled in. if (Signum = = SIGUSR1) {printf ("thread%d, receive SIGUSR1 No.%d\n", Sig_ppid, J); j + +; Sigrtmin should not being considered constants from Userland,//there was compile error when use switch case} else I F (Signum = = sigrtmin) {printf ("thread%d, receive Sigrtmin No.%d\n", Sig_ppid, K); k++; }}void* Worker_thread () {pthread_t ppid = pthread_self (); Pthread_detach (PPID); while (1) {printf ("I ' m thread%d, I ' M alive\n", ppid); Sleep (10); }}void* Sigmgr_thread () {sigset_t waitset, oset; siginfo_t info; int RC; pthread_t ppid = Pthread_self (); Pthread_detach (PPID); Sigemptyset (&waitset); Sigaddset (&waitset, sigrtmin); Sigaddset (&waitset, SIGUSR1); while (1) {rc = Sigwaitinfo (&waitset, &info); if (rc! =-1) {printf ("sigwaitinfo () Fetch the signal-%d\n", RC); Sig_handler (Info.si_signo); } else {printf ("sigwaitinfo () returned err:%d; %s\n ", errno, Strerror (errno)); }}}int Main () {sigset_t bset, oset; int i; pid_t pid = Getpid (); pthread_t Ppid; Block Sigrtmin and SIGUSR1 which'll be handled in//dedicated thread sigmgr_thread ()//newly created threads Would inherit the Pthread mask from its creator sigemptyset (&bset); Sigaddset (&bset, sigrtmin); Sigaddset (&bset, SIGUSR1); if (Pthread_sigmask (Sig_block, &bset, &oset)! = 0) printf ("!! Set pthread Mask failed\n "); Create the dedicated thread Sigmgr_thread () which'll handle//SIGUSR1 and Sigrtmin synchronously pThread_create (&ppid, NULL, sigmgr_thread, NULL); Create 5 worker threads, which would inherit the thread mask of//the creator main thread for (i = 0; i < 5; I + +) {pthread_create (&ppid, NULL, worker_thread, NULL); }//Send out of SIGUSR1 and sigrtmin signals for (i = 0; i <; i++) {Kill (PID, SIGUSR1); printf ("Main thread, send SIGUSR1 No.%d\n", i); Kill (PID, sigrtmin); printf ("Main thread, send Sigrtmin No.%d\n", i); Sleep (10); } exit (0);}
Precautions
In a multi-threaded application based on Linux, it is possible to consider using a synchronous model for the signal generated by the logic of the program, and for signals such as SIGSEGV, which will cause the program to terminate, the signal processing function must be used and registered in accordance with the traditional asynchronous method signal()
sigaction()
. These two signal processing models are available in a Linux application, depending on the signal being processed:
- Do not block two signals SIGSTOP and SIGKILL that cannot be ignored for processing in the signal mask of a thread.
- Do not block SIGFPE, Sigill, SIGSEGV, sigbus in the signal mask of the thread.
- Ensure that
sigwait()
the waiting signal set has been blocked by all threads in the process.
- When the main thread or other worker thread generates a signal, it must be called to send the signal to the
kill()
entire process instead pthread_kill()
of sending a particular worker thread, or the signal processing thread cannot receive the signal.
- Because of the use of serial processing of the arrival of the signal, in
sigwait()
order to avoid the signal processing lag, or the non-real-time signal is lost, the code to process each signal should be as concise and fast as possible to avoid the call will create blocking library functions.
Summary
In developing a Linux multithreaded application, the program will continue to function normally after signal processing if the signal is needed for the program logic to be introduced. In this context, if the signal is processed asynchronously, it is necessary to consider the security of the asynchronous signal processing function in writing the signal processing function, while some library functions in the program may be interrupted by the signal, and the error will be returned, and the processing of eintr should be considered. On the other hand, you can also consider using the synchronization model described above to process the signal, simplifying the writing of the signal processing function, and avoiding the risk caused by the uncertainty of the execution context of the signal processing function.
Synchronous processing of Linux asynchronous signals