Signals and Signal Sets

Table of contents

1. Signal basis

1. Basic concepts

2. The purpose of the signal is to communicate

3. Who handles the signal and how to handle it

4. The signal is asynchronous

5. The nature of the signal

2. Classification of signals

1. Reliable signals and unreliable signals

2. Real-time signal and non-real-time signal

3. The processing of the signal by the process

signal() function

code writing

verify 

sigaction() function

struct sigaction structure

code writing

verify

4. Signal set

1. API of sigset_t signal set

① Initialize the signal set

② Add/delete signals to the signal set

③ Test whether the signal is in the signal concentration

Other functions used 

④ strsignal () function to obtain description information

⑤raise() function

⑥ pause () function

 2. Signal mask (blocking signal transmission)

Code writing to verify the role of the signal mask

verify

sa_mask for code writing

verify

sigsuspend() for code writing

code writing

verify

5. Real-time signal 

sigpending() function

send live signal

sigqueue() function

Write code

receive signal

send signal

verify


1. Signal basis

1. Basic concepts

        A signal is a notification mechanism to a process when an event occurs, and it can also be called a software interrupt

2. The purpose of the signal is to communicate

        A process with the appropriate privileges can send a signal to another process, and this use of signals can be used as a synchronization technique and even as a primitive form of interprocess communication (IPC). "Who" can the signal be sent by? The following situations can generate signals:
⚫ An exception occurs in the hardware, that is, the hardware detects an error condition and notifies the kernel, and then the kernel sends a corresponding signal to the relevant process

⚫ is used to enter a special character that can generate a signal under the terminal. For example, pressing the CTRL + C key combination on the terminal can generate an interrupt signal (SIGINT), through which the process running in the foreground can be terminated

⚫ A process calls the kill() system call to send an arbitrary signal to another process or process group. Of course, there are restrictions on this. The owner of the process receiving the signal and the process sending the signal must be the same, or the owner of the process sending the signal is the root superuser

⚫ Users can send signals to other processes through the kill command. For example, execute "kill -9 xxx" in the terminal to kill the process whose PID is xxx

⚫ A software event has occurred, i.e. when it is detected that a certain software condition has occurred. The timer set by the process has timed out, the CPU time of the process execution has exceeded, a child process of the process has exited, etc.

3. Who handles the signal and how to handle it

        Signals are usually sent to the corresponding process. When the signal arrives, the process needs to take corresponding measures. Usually, the process will perform one of the following operations depending on the specific signal: ⚫ Ignore the signal
. That is to say, when the signal arrives at the process, the process will ignore it and ignore it directly, as if the signal has not been issued, and the signal will not have any impact on the process. It should be noted that the SIGKILL signal and the SIGSTOP signal cannot be ignored

⚫ Capture signal. When the signal reaches the process, execute the pre-bound user-defined signal processing function

⚫ Execute the system default operation. The process does not handle the signal event, but is handed over to the system for processing. It should be noted that for most signals, the default processing method of the system is to terminate the process

4. The signal is asynchronous

        The event that generates a signal occurs randomly to the process, the process cannot predict the exact time when the event occurs, and the process cannot determine whether a signal has been generated by simply testing a variable or using a system call

5. The nature of the signal

        Signals are defined in the <signum.h> header file, and each signal starts with SIGxxx. The signal is essentially a digital number of int type. The kernel defines a unique integer number for each signal, which starts from the number 1 and expands sequentially. And each signal has its corresponding name (in fact, it is a macro), and the signal name and signal number are in one-to-one correspondence

2. Classification of signals

1. Reliable signals and unreliable signals

        Use "kill -l" command under Linux system to view all signals. The problem of unreliable signals under Linux mainly refers to the possible loss of signals. Under Linux, signals whose signal value is less than SIGRTMIN(34) are unreliable signals, which is the source of "unreliable signals"


        Reliable signals support queuing and will not be lost. At the same time, a new version of signal sending and binding has appeared, the signal sending function sigqueue() and the signal binding function sigaction()         

        There are two signals, named SIGUSR1 and SIGUSR2, which are classified as "user-defined signals". "User-defined" here means that these two signals are not defined by the operating system or the standard library as signals for a specific purpose, but are reserved for applications or users to define and use them.

2. Real-time signal and non-real-time signal

        Real-time signals and non-real-time signals are actually classified from the time relationship, which correspond to reliable signals and unreliable signals. Non-real-time signals do not support queuing and are unreliable signals; real-time signals support queuing and are all unreliable signals. Reliable signal. Real-time signals ensure that multiple signals sent can be received. Real-time signals are part of the POSIX standard and can be used in application processes.
Generally, non-real-time signals (unreliable signals) are called standard signals

3. The processing of the signal by the process

        The Linux system provides two functions of the system call signal() and sigaction() to set the processing mode of the signal

signal() function

typedef void (*sig_t)(int);
sig_t signal(int signum, sig_t handler);

signum: Specify the signal that needs to be set. You can use the signal name (macro) or the digital number of the signal. It is recommended to use the signal name. handler
 : A function pointer of type sig_t, pointing to the signal processing function corresponding to the signal. Execute the processing function; the parameter handler can be set as a user-defined function or as SIG_IGN or SIG_DFL, SIG_IGN indicates that the process needs to ignore the signal, and SIG_DFL indicates that it is set as the system default operation Return value: The return value of this function is
also A function pointer of type sig_t. If successful, the return value points to the previous signal processing function; if an error occurs, it returns SIG_ERR and errno is set.

code writing

 Lines 5-8 define a signal processing function and print

Lines 12-13, use a variable of type sig_t to receive the pointer returned by the signal function, and the SIGINT signal is a terminal interrupt character, usually a combination of CTRL + C

Lines 14-18, judging the return status of the function

Line 19, get the pid of this process

Line 20, infinite loop waiting for signal

        Bind the SIGINT (2) signal to a user-defined processing function sig_handler(int sig) through the signal() function. When the process receives the SIGINT signal, it will execute this function and then run the printf print statement. After running the program,
the program will be stuck in the for infinite loop. At this time, press the interrupt character CTRL + C on the terminal, and the system will send a SIGINT signal to each process in the foreground process group, and our test program will receive this Signal

verify 

        Run the program, then press ctrl+c

 Sending the "kill -9 id number" command with a new terminal will kill the program

 Note: Ordinary users can only kill the user's own process, and have no permission to kill other user's processes

sigaction() function

        It is recommended that you use the sigaction() function. Although the signal() function is simple and easy to use, sigaction() is more complicated, but in return, sigaction() is also more flexible and portable.

        sigaction() allows the signal processing function to be obtained separately instead of setting, and various properties can also be set to exercise more precise control over the behavior when calling the signal processing function. The prototype is as follows

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

 signum: The signal that needs to be set, any signal except SIGKILL signal and SIGSTOP signal.
act: The act parameter is a struct sigaction type pointer, pointing to a struct sigaction data structure, which describes the processing method of the signal. If the parameter act is not NULL, it means that a new processing method needs to be set for the signal;
if If it is NULL, it means that there is no need to change the current processing method of the signal
oldact: The oldact parameter is also a struct sigaction type pointer, pointing to a struct sigaction data structure. If the parameter oldact is not NULL, information such as the processing method before the signal will be returned through the parameter oldact; if such information is not intended to be obtained, then the parameter can be set to NULL return
value: return 0 if successful; return -1 if failed , and set errno.

struct sigaction structure

struct sigaction {
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void);
};

 sa_handler: Specifies the signal processing function, which is the same as the handler parameter of the signal() function

sa_sigaction: It is also used to specify the signal processing function. This is an alternative signal processing function. It provides more parameters, and more information can be obtained through this function. These signals are obtained through the siginfo_t parameter. sa_handler and sa_sigaction are mutually exclusive It cannot be set at the same time. For standard signals, it is enough to use sa_handler, which can be selected through the SA_SIGINFO flag

sa_mask: The parameter sa_mask defines a set of signals. Before the process executes the signal processing function defined by sa_handler, this set of signals will be added to the signal mask field of the process, and the signal will be restored after the process executes the processing function mask to remove this set of signals from the signal mask field

        When the process is executing the signal processing function, it may receive the same signal or other signals, thereby interrupting the execution of the current signal processing function. Usually we do not want to be interrupted by another signal during the execution of the signal processing function, so how do it? Then it is realized through the signal mask. If the process receives these signals in the signal mask, the signal will be blocked and cannot be processed temporarily until these signals are removed from the signal mask of the process.

        When the signal processing function is called, the process will automatically add the currently processed signal to the signal mask field, which ensures that when a given signal is processed, if this signal occurs again, it will be blocked. If the user also needs to block other signals, it can be done by setting the parameter sa_mask. The signal mask can avoid the competition state (also called race condition) between some signals.

        sa_restorer: This member is obsolete and should not be used any more.

        sa_flags: The parameter sa_flags specifies a set of flags that are used to control the processing of signals

code writing

         Similar to the signal() function, first instantiate the struct sigaction structure as sig and initialize it to 0 to prevent the variable from containing garbage data. It is sig.sa_flagsa set of flags used to modify the behavior of the signal handler. 0Indicates that all flags are set to their default values, i.e. no additional options are used when processing the signal

verify

 The effect is the same as the signal() function

4. Signal set

        A data type that can represent multiple signals (a set of signals) --- signal set (signal set, signal set is actually a sigset_t type data structure

typedef struct
{
unsigned long int __val[_SIGSET_NWORDS];
} sigset_t;

        Use this structure to represent a set of signals, add multiple signals to the data structure, and the Linux system has an API for operating the sigset_t signal set

1. API of sigset_t signal set

① Initialize the signal set

        sigemptyset() and sigfillset() are used to initialize signal sets. sigemptyset() initializes the signal set so that it does not contain any signals; and the sigfillset() function initializes the signal set so that it contains all signals (including all real-time signals). The function prototype is as follows

int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);

 set: Points to the signal set variable that needs to be initialized.
Return value: return 0 if successful; return -1 if failed, and set errno

② Add/delete signals to the signal set

        Use the sigaddset() and sigdelset() functions to add or remove a signal to the signal set respectively. The function prototype is as follows

int sigaddset(sigset_t *set, int sign);
int sigdelset(sigset_t *set, int sign);

 set: Points to the signal set.
signum: The signal that needs to be added/removed.
Return value: return 0 if successful; return -1 if failed, and set errno

③ Test whether the signal is in the signal concentration

        Use the sigismember() function to test whether a certain signal is in the specified signal set. The function prototype is as follows

int sigismember(const sigset_t *set, int sign);

 set: Specifies the signal set.
signum: The signal to be tested.
Return value: If the signal signum is in the signal set, return 1;

                Returns 0 if not in signal set; returns -1 and sets errno on failure.

Other functions used 

④ strsignal () function to obtain description information

char *strsignal(int sig);

         Calling the strsignal() function will obtain the description information corresponding to the signal specified by the parameter sig, and return a pointer to the description information string; the function will check the parameter sig, and if the incoming sig is invalid, it will return "Unknown signal" information

⑤raise() function

A process needs to send a signal to itself, the raise() function can be used to achieve this requirement

int raise(int sig);

sig: The signal to be sent.
Return value: Returns 0 on success; returns a non-zero value on failure

⑥ pause () function

int pause(void);

         The pause() system call can cause the process to pause and go to sleep until the process catches a signal. Only when the signal processing function is executed and returned from it, pause() returns. In this case, pause() Return -1 and set errno to EINTR

 2. Signal mask (blocking signal transmission)

        The kernel maintains a signal mask (actually a signal set) for each process, that is, a set of signals. When a process receives a signal defined in the signal mask, the signal will be blocked and cannot be passed to the process for processing, then the kernel will block it until the signal is removed from the signal mask, the kernel will Pass the signal to the process to be handled

        There are usually several ways to add a signal to the signal mask:
⚫ When the application calls the signal() or sigaction() function to set the processing method for a certain signal, the process will automatically add the signal to the signal mask , which ensures that when a given signal is processed, if the signal occurs again, it will be blocked; of course, for sigaction(), it depends on whether the sigaction() function has set the SA_NODEFER flag; when the signal After the processing function returns, the signal will be automatically removed from the signal mask⚫
When using the sigaction() function to set the processing method for the signal, you can specify an additional set of signals, which will be automatically added when the signal processing function is called to the signal mask, and after the signal processing function returns, remove this group of signals from the signal mask; set it through the sa_mask parameter of the struct sigaction structure

        In addition to the above two methods, you can also use the sigprocmask() system call to explicitly add/remove signals to the signal mask at any time. The prototype is as follows

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

 how: The parameter how specifies some behaviors when calling the function.

        ⚫ SIG_BLOCK: Add all signals in the signal set pointed to by the parameter set to the signal mask of the process. In other words, set the signal mask to the union of the current value and set.
        ⚫ SIG_UNBLOCK: Remove all signals in the signal set pointed to by parameter set from the process signal mask.
        ⚫ SIG_SETMASK: The process signal mask is directly set to the signal set pointed to by the parameter set

set: Add or remove all signals in the signal set pointed to by the parameter set to the signal mask; if the parameter set is NULL, It means that no changes are required to the current signal mask.
oldset: If the parameter oldset is not NULL, before adding a new signal to the signal mask, get the current signal mask of the process and store it in the signal set specified by oldset; if it is NULL, it means not to get the current signal mask Code
Return value: If successful, return 0; if failed, return -1 and set errno

Code writing to verify the role of the signal mask

         A processing function sig_handler is registered for the SIGINT signal, and it will be executed when the process receives the signal
; then call the sigprocmask function to add the SIGINT signal to the signal mask, and then call raise(SIGINT) to send a SIGINT signal to itself, if The signal mask does not take effect, which means that the SIGINT signal will not be blocked, then the sig_handler function should be executed immediately after calling raise(SIGINT), thereby printing out the string information of "executing signal processing function..."; if
        set If the signal mask takes effect, the signal processing function will not be executed immediately, but will be executed after 2 seconds, because the program uses sleep(2) to sleep for 2 seconds before moving the SIGINT signal from the signal mask Remove, so the process will handle the signal, and receiving the signal before removing will block it

verify

 After sleep, the 27 lines of code are unblocked, and the signal processing function can be executed

sa_mask for code writing

        Members of the struct sigaction structure sa_maskrepresent a set of signal sets that need to be blocked during the execution of the signal processing function. When handling a signal, if sa_maskthe signals set in are present, they are added to the process's signal mask to prevent interrupting the signal handler.

        If sa_maskthe signals in are blocked, then the handlers for those signals will not be executed while this handler is running. Therefore, blocking certain signals ensures that while the signal handler is running, it will not be disturbed by other signals.

        In the example below, SIGUSR1the signal handler blocks the signal while it is running SIGUSR2to ensure that the handler execution is not SIGUSR2interrupted by another signal

 Line 20, define the struct sigaction structure variable sig

Line 21, initialize the signal set so that it does not contain any signals

Lines 22-23, using the default value, bind the signal processing function

Lines 25-29, set the acquisition signal to SIGUSR1

Lines 6-15, the signal processing function, adding the blocking mask is the SIGUSR2 signal, and SIGUSR2 is not allowed to interrupt when processing the signal, so when the signal processing function is executed after receiving the SIGUSR1 signal, the SIGUSR2 signal will be blocked, and the SIGUSR1 signal is processed directly After the program is completed, SIGUSR2 will no longer be blocked and can be sent and received.

verify

         It can be seen that another terminal is used in the upper left corner. When the execution starts, two signals are quickly sent out, but only signal 1 is executed, and signal 2 can only be run after the completion of signal 1, and it will be terminated by default after running. program

sigsuspend() for code writing

        Encapsulate the two actions of restoring the signal mask and suspending the process with pause() into an atomic operation, which is exactly the
purpose of the sigsuspend() system call. The prototype of the sigsuspend() function is as follows

int sigsuspend(const sigset_t *mask);

mask: The parameter mask points to a signal set.
Return value: sigsuspend() always returns -1, and sets errno to indicate an error (usually EINTR), indicating that it is interrupted by a signal. If the call fails, set errno to EFAULT. The sigsuspend() function will set the signal pointed to by the parameter
        mask Set to replace the signal mask of the process, that is, set the signal mask of the process to the signal set pointed to by the parameter mask, and then suspend the process until the captured signal is awakened (if the captured signal is a member of the mask signal set, Will not wake up, continue to suspend), and return from the signal processing function, once returning from the signal processing function, sigsuspend() will restore the signal mask of the process to the value before the call

code writing

 

         Initialize three signal sets: new_mask: signal mask, used to add signals that need to be blocked (here SIGINT);         old_mask: old signal mask, save the original signal mask for later restoration; wait_mask: wait signal set, initially empty, Later, sigsuspend()the function is used to hang up and wait for the signal; SIGINTthe processing function of the signal is set tosig_handler

        Set the received signal to SIGINT, add a mask in the new signal set, and save the original signal mask in the old signal set before adding the mask. In practical applications, you can put the code that needs to be protected at the position of line 30 . In this way, sigprocmask()after the call, these codes will not be interrupted by the signal, ensuring the integrity of the data.

        Called sigsuspend()to block the process until a signal is received SIGINT(emitted when Ctrl-C is typed). Doing so protects some critical code from being interrupted when it is not expected. When SIGINTthe signal is received, the process will wake up, execute the subsequent code, and then exit.

verify

5. Real-time signal 

        If the process is currently executing a signal processing function and receives a new signal during signal processing, if the signal is a member of the signal mask, the kernel will block it and add the signal to the process's waiting signal set (waiting to be processing, signals in the waiting state), in order to determine which signals are in the waiting state in the process, you can use the sigpending() function to obtain

sigpending() function

int sigpending(sigset_t *set);

set: The signal in the waiting state will be stored in the signal set pointed to by the parameter set.
Return value: Return 0 if successful; return -1 and set errno if failed. 

send live signal

The Linux kernel defines 31 different real-time signals, and the signal number ranges from 34 to 64. Compared with standard signals, real-time signals have the following advantages: ⚫ The signal range of real-time signals has been expanded, which can be applied to the purpose of application
customization , while the standard signals only provide two signals for application custom use: SIGUSR1 and SIGUSR2.
⚫ The kernel adopts queue management for real-time signals. If a real-time signal is sent to another process multiple times, the signal will be delivered multiple times. Conversely, if a process is waiting for a standard signal, the signal will only be delivered once if the signal is sent to the process again.
⚫ When sending a real-time signal, you can specify accompanying data (an integer data or a pointer value) for the signal, which can be obtained by the process receiving the signal in its signal processing function.
⚫ The delivery sequence of different real-time signals is guaranteed. If several different real-time signals are waiting, the signal with the lowest number will be delivered first. In other words, the smaller the signal number, the higher its priority. If multiple signals of the same type are queued, the delivery order of the signals (and accompanying data) is consistent with the order in which the signals were sent.

The use of real-time signals in the application requires the following two requirements:
⚫ The sending process uses the sigqueue() system call to send real-time signals and accompanying data to another process.
⚫ The process of receiving real-time signals needs to create a signal processing function for the signal, use the sigaction function to create a processing function for the signal, and add SA_SIGINFO, so that the signal processing function can receive real-time signals and accompanying data, that is, use the sa_sigaction pointer to point to The processing function instead of sa_handler, of course, allows the application to use sa_handler, but in this way, the accompanying data of the real-time signal cannot be obtained

sigqueue() function

int sigqueue(pid_t pid, int sig, const union sigval value);

 pid: Specify the pid corresponding to the process receiving the signal, and send the signal to the process.
sig: Specifies the signal to be sent. The parameter sig can also be set to 0 to check whether the process specified by the parameter pid exists.
value: The parameter value specifies the accompanying data of the signal, union sigval data type

Return value: If successful, it will return 0; if it fails, it will return -1 and set errno.

The union sigval data type (union) is as follows:

typedef union sigval
{
int sival_int;
void *sival_ptr;
} sigval_t;

The accompanying data carried can either specify an integer data or a pointer

Write code

receive signal

sigactionA processing function for real-time signal is registered          through the function sig_handler, and the processing method SA_SIGINFO,is set to use sa_sigaction as the signal processing function. The program obtains the real-time signal number to be processed through the command line parameter. After entering an infinite loop, the program keeps calling sleepthe function to sleep. When the program receives the bound real-time signal, it will call the registered processing function, and print out the signal number and accompanying data. Since the program is in an infinite loop, the program needs to be terminated manually.

send signal

         Send the specified signal to the specified process by calling sigqueuethe function, and attach an integer data. Among them, the program obtains the target process ID and signal value of the signal to be sent through the command line parameters, stores the data in the sig_valvariable, and uses sigqueuethe function to send the specified signal and the accompanying data to the target process. After sending successfully, the program prints "Signal sent successfully!" and exits.

verify

         Run the receiving code first, set the received signal to 34, and then run the sending code in the terminal. The sent object is the process of 37137, and the signal is 34. After seeing that the sending is successful, the receiving side will print the received signal and its data

Guess you like

Origin blog.csdn.net/weixin_46829095/article/details/129643934