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
- How signals are delivered within a process
- Three major behaviors and five actions of signals
To understand these two knowledge points, we must first understand some basic concepts
- There are two signal sets in the PCB, namely the pending signal set and the masked word
- 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.
- 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.
- 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:
- Signal ignore, block the signal from the set of pending signals or let the signal enter the ignore behavior
- Signal masking, delaying the delivery of signals, blocking them between the pending signal set and the masked word
- 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
Next, let’s take a look at the special data types and related functions needed to implement signal shielding.
Special data types and functions related to 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
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.
Special data types and functions related to signal capture
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!