[Linux] Principle and implementation of signal shielding and signal capture (with diagrams and code implementation)

This article may be a bit long. If you have already understood the first two parts, you can skip to the third part – the implementation of signal shielding.

For those who don’t know much about it, I hope you can calm down and read it. I believe you will gain a lot. So without further ado, let’s get started! ! !

Table of Contents

How does a signal pass through a process?

Three major behaviors and five actions of signals

Implementation of signal shielding

Special data types and functions related to signal masking

Specific steps to achieve signal shielding

Implementation of signal capture

Special data types and functions related to signal capture

Specific steps to implement signal capture


To understand how to implement signal shielding and signal capture, we must first understand two knowledge points

  1. How signals are delivered within a process
  2. Three major behaviors and five actions of signals

To understand these two knowledge points, we must first understand some basic concepts

  1. There are two signal sets in the PCB, namely the pending signal set and the masked word
  2. Both signal sets are composed of several bits. The signal set has as many signal bits as the system supports. Each signal goes to its corresponding position.
  3. The bit codes of the signal bits are 0 or 1 respectively. Each signal bit is like a door. 1 means that passage is prohibited and 0 means that passage is allowed. By default, the bit code of each signal bit is 0, which means the door is open.
  4. The signal bit code of the pending signal set is set by the system, and the signal bit code of the masking word can be set by the user to block certain specific signals.

After understanding these basic concepts, we can understand the signal transmission process in the process. Taking signal No. 2 – SIGINT as an example, the specific situation is shown in the figure below

The process of signal transmission in the process

From the above figure, we can draw some interesting information, that is,Unix classic signals (signals 1-31) do not support queuing. At most two signals can be processed at the same time, and the remaining signals will be discarded directly

But it should be noted that custom signals (signals 34-64) support queuing, and a custom signal queue is used to help signals be queued. Why are there such differences between these two types of signals? This involves the functions of these two types of signals.

We know that signals No. 1-31 are mostly used to kill or suspend processes, and you only need to kill or suspend the process once. If you support queuing, it will become a signal to kill once. A signal to kill again, and then continue to kill. Why, killing once is not enough, why do you have to whip the corpse? This is why Unix classic signals do not support queuing.

Why are signals 34-64 queued up? For example, I believe everyone has used a remote control to change channels. The remote control also uses signals to complete tasks. The signals here involve custom signals. If you press the channel change button ten times in a row, will the TV only change one channel for you? Taiwan? This is why custom signals support queuing. Their job is mainly to bind tasks or events, so they need to support queuing.

Three major behaviors and five actions of signals

The three major behaviors and five actions are actually the detailed contents of the handler we are talking about, as shown in the figure below

The so-called three major behaviors are: SIG_DFL-default behavior, SIG_IGN-ignore behavior, SIG_ACTION-capture behavior

By default, if we do not do special processing, the signal will execute the default behavior. The five actions are the five default processing actions in the default behavior.

I believe everyone can see this from the picture above. Here we talk about capturing behavior.

In fact, it is very simple. The capture function is implemented by the user. We can implement the functions we want to complete in this function.

When the PCB of the process receives the corresponding signal, the corresponding capture function will be triggered, which will invalidate the signal and complete the functions we require.

This leads to the main topic of this blog, three ways to make the signal invalid:

  1. Signal ignore, block the signal from the set of pending signals or let the signal enter the ignore behavior
  2. Signal masking, delaying the delivery of signals, blocking them between the pending signal set and the masked word
  3. Signal capture, let the signal enter the capture behavior and make it execute the user-defined capture function

But it should be noted that not all signals can be disabled, because once a process error occurs, or a virus appears, the system may have discovered the problem, but because the signals are blocked, the corresponding process or virus cannot be killed.

Sothe operating system retains two advanced operation signals, which are not delivered in the process according to the above process and cannot be invalidated. These two signals are signal No. 9-SIGKILL and signal No. 19-SIGSTOP. One is used to kill the process, and one is used to suspend the process.

Implementation of signal shielding

Let me first tell you that the header files of the special data types and related functions required below are #include

#include: Signal header file, defining signal symbol constants, signal structures and signal operation function prototypes.

Next, let’s take a look at the special data types and related functions needed to implement signal shielding.

To implement signal shielding, a special data type is required, called the signal set type, which is sigset_t. Here I define a variable set of the signal set type, which is sigset_t set;

Define another variable int signo; signo–signal number

The following are related functions commonly used to implement signal shielding

Note ①: Note that the signo here should pass in the signal name instead of the signal number. For example, we want to block the SIGINT signal, but the signal number of the same signal in different systems may be different. Maybe the signal number of SIGINT in system 1 is 2. The signal number of SIGINT in system 2 is 3, so when we pass it in, we directly pass in the signal name instead of the signal number.

Note ②: Introduction to parameters of sigprocmask function

Function Function Return value
int sigemptyset( & amp;set); Initialize the signal set and set the bit codes of all signal bits to 0, which is the allowed state

Returns 0 on success, -1 on failure, and sets errno to return the reason for failure.

int sigfillset( & amp;set); Initialize the signal set, set the bit codes of all signal bits to 1, that is, the pass-through state returns 0 on success, -1 on failure, and sets errno to return failure Reason
int sigdelset( & amp;set , int signo); Set the bit code of the signal bit with the serial number signo in the signal set to 0, which is the allowed state (please see the note ① below) Returns 0 if successful, and returns if failed -1
int sigaddset( & amp;set , int signo); Set the bit code of the signal bit with serial number signo in the signal set to 1, which is the forbidden state Returns 0 if successful, and -1 if failed
int sigismember( & amp;set , int signo); Get the signal set sequence number signo The bit code of the signal bit Returns the bit code of the corresponding signal bit, that is, 0 or 1
int sigprocmask (replacement method, new signal set, whether to transmit the original mask word) (see note ② for parameter introduction) Use new signal Set to replace the old masked word and get the new masked word returns 0 if the replacement is successful, and -1 if it fails
Parameters Variable type Explanation
Replacement method ——

There are three replacement methods:

SIG_SETMASK (overwrite the new signal set directly onto the original mask word),

SIG_BLOCK (Bitwise OR operation of the new signal set and the bit code of the corresponding signal bit of the original mask word)

SIG_UNBLOCK (after inverting the bit code of each signal bit in the new signal set, and performing a bitwise AND operation with the original mask word), among these three replacement methods, the most commonly used is SIG_SETMASK

New signal set sigset_t* New signal set set by the user, used to replace the old blocking words to block certain specific signals
Whether to transmit the original masked word sigset_t* Passing NULL means not to transmit it. The original masked word, passing in a variable of type sigset_t* means passing out the original masked word

After understanding this, we can implement signal shielding

Specific steps to implement signal shielding

Let’s complete a function – let the process shield the SIGINT signal, as shown below

Code implementation

//sig_shield.c

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

int main(void)
{
    sigset_t newset;//Initialize a signal set
    sigemptyset(&newset);//Set all signal bit codes to 0
    sigaddset(& amp;newset, SIGINT);//Shield the SIGINT signal and set the bit code of its corresponding signal bit to 1
    int bitcode = sigismember( & amp;newset , SIGINT);//Use the sigismember function to check whether the bitcode is set to 1
    if(bitcode == 1){
        printf("New signal set set successfully!\
");
    }
    else{
        printf("New signal set setting failed!\
");
        exit(1);
    }
    sigprocmask(SIG_SETMASK, & newset, NULL);//Replace the mask word with the new signal set to get the new mask word, and do not transmit the old mask word
    //Write an infinite loop, let the process sleep for one second at a time, and test whether CTRL + C can kill the process in the terminal interface
    int count = 0;
    while(1){
        sleep(1);
        count + + ;
        printf(" count = %d\
" , count);
    }
    exit(0);
}

Illustration

We can clearly find that the SIGINT signal sent by CTRL + C is blocked, and the SIGQUIT signal sent by CTRL + \ successfully kills the process. Above, simple signal shielding is achieved. Isn’t it very easy?

Next, let’s learn about the relevant knowledge and specific implementation of signal capture.

Implementation of signal capture

In the previous explanation, we mentioned that there are three behaviors of signals, namely: SIG_DFL (default behavior), SIG_IGN (ignore behavior), SIG_ACTION (capture behavior)

Under normal circumstances, signals follow the default behavior. If we want to achieve signal capture, it means that we have to select the signal behavior and let it enter the signal behavior. To enter the capture behavior, it means that the signal must enter the handler, which also This means that the signal must first pass through the pending signal set and the mask word and become a deliverable state before it can enter the processing flow.

This involves a structure: called struct sigaction. Let’s introduce the members of this structure.

Members Variable type Function Explanation
sa_handler

Function pointer type, parameter type is int

void (*sa_handler)(int)

Behavior selection Three parameters can be passed in – SIG_DFL (default behavior ), SIG_IGN (ignore behavior), the address of the capture function. It should be noted that the capture function must be written as [void function name (int)] to match the variable type of sa_handler
sa_flags int Specify any special processing for the signal Generally, 0 is passed, indicating the default option. You can search for more parameters yourself, so I won’t introduce too much here
sa_mask sigset_t As a temporary mask word, it prevents multiple similar signals from entering the handler at the same time. Initialization is required before use sa_mask is a signal set. When a signal enters the sa_handler function through the mask word, the corresponding signal in the signal set is added to the signal of the process. In the mask, when the sa_handler function is executed, the mask word is reset to its original value. This ensures that when a certain signal is being processed, if other signals of the same type are passed in again, they will be blocked

Here we define two variables, namely struct sigaction act; and struct sigaction old_act;

Then we need to understand a function that is used to change the behavior of the signal. This function has the same name as the above structure and is called the sigaction function.

Header file #include
Function Replace the behavior of the target signal with the behavior required by the user, and the original behavior can be transmitted
Syntax int sigaction(int signo, struct sigaction *act, struct sigaction *old_act);
Return value Returns 0 on success, -1 on failure

After understanding this, we can implement signal capture

Specific steps to implement signal capture

Code implementation

//Sig_Catch.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <signal.h>

//This n is not filled in by us, but the signal serial number filled in by the system.
void sig_do(int n){
    printf("Successfully captured %d signal\
",n);
}

int main()
{
    struct sigaction act, old_act;//Create two sigaction type structures
    act.sa_handler = sig_do;//Change the signal from default behavior to capture behavior
    act.sa_flags = 0; //Default option, no special processing
    sigemptyset( & amp;act.sa_mask);//Initialize sa_mask in the structure
    sigaction(SIGINT, & amp;act, & amp;old_act);//Replace the behavior of signal SIGINT with the capture behavior and pass out the original behavior
    //Write an infinite loop, sleep for one second each time, and see if CTRL + C can kill the process
    while(1){
sleep(1);
    }
    return 0;
}

Illustration

The above is the entire content of this blog. If you don’t understand anything, you can leave me a message in the comment area and I will help you answer it to the best of my ability.

Today’s learning record ends here. See you in the next article, ByeBye!