Deep understanding of Linux signals

Signal generation

Concept of signal

  • Signaling is a way of asynchronous notification of events between processes, which belongs to soft interrupt

Generation method:

  1. Generate a signal to the process through the terminal keyboard. If the process can terminate the program by pressing "ctrl + c" when running, it is actually sending No. 2 signal to the program.
  2. Generate signals through system calls. For example, when a process is running, use the kill command to "kill" it, that is, use the system call function to send signal No. 9 to the process.
  3. Generate signals through software. For example, in a pipeline, if the reader closes the file descriptor, the writer will receive the pipeline signal when writing, and then close the process.
  4. If the hardware generates signals, such as operation errors (divide by 0, etc.), the OS will send corresponding signals to the process.

Other concepts

  • The actual signal processing action is called signal delivery
  • The state between the generation and delivery of a signal is called signal pending.
  • The process can choose to Block a signal.
  • When the blocked signal is generated, it will remain in the pending state until the process unblocks the signal
  • Note that blocking and ignoring are different. As long as the signal is blocked, it will not be delivered, and ignoring is an optional processing action after delivery

View signal

kill -l


There are 62 signals in total (32,33 do not exist). This paper only discusses the processing of the first 32 signals

Signal processing

Let's look at such an example

When the program executes the for loop, the array has actually crossed the boundary, and the OS has also sent a signal to the program (a segment error is printed later). However, the program does not immediately terminate the program, but continues to execute the code downward, print "hello", and then process the signal.
Therefore, we draw a conclusion that the signal may be generated at any time when the process is executing, but the process does not necessarily process the signal directly when the signal is generated, so the process must save the received signal.
Signals are saved as bitmaps in the process. There are three bitmaps in the process, namely, appending table, block table and handler table; Appending table. Each table has 32 bits in total, and each bit corresponds to a signal. The corresponding position in the appending table indicates whether the process receives the corresponding signal, the corresponding position in the block table indicates whether the process blocks the signal (receives the signal but does not process it), and the corresponding position in the handler table indicates the processing method of the signal.
There are three processing methods for a signal:
SIG_DFL: default processing method
SIG_IGN: ignore this signal
Function pointers: Custom signal processing

Line 1: the process does not receive signal 1, signal 1 is not blocked, and the processing method of signal 1 is the default processing method.
Line 2: the process receives signal 2. Signal 2 is blocked, so signal 2 will not be processed temporarily (pending). Once the blocking is removed, the processing method of signal 2 is ignored.
The third line: the process does not receive signal 3, signal 2 is not blocked, and the processing method of signal 3 is user-defined.

signal control

We know the storage mode and processing mode of the signal in the process, so we can try to control the blocking state of the signal and the processing mode of the signal.

Blocking state of control signal

The blocking state table of the signal is the block table in the process. We can create a table of the same type, and then assign the table we created to the table in the process through the system call function, and then we can control the blocking state of the process signal.
The type of block is "sigset_t"
This type is not a built-in type and cannot be operated directly, so it should be operated through functions.

#include <signal.h>
int sigemptyset(sigset_t *set);
//Set each position of set to 0
int sigfillset(sigset_t *set);
//Set each position of set to 1
int sigaddset (sigset_t *set, int signo);
//Set the position corresponding to the signo signal to 1
int sigdelset(sigset_t *set, int signo);
//Set the position corresponding to the signo signal to 0
int sigismember(const sigset_t *set, int signo)
//Judge whether the corresponding position of signo signal in set is 1

The above functions only modify the table created by ourselves, and do not make any changes to the actual block table in the process!

Now that we have a custom table, we can use this table to modify the block table in the process.
Modify the block table and use the sigprocmask function

The first parameter: how to modify the block table
Assume that the original table is mask
SIG_BLOCK: add the signal we want to block on the basis of the original table, which is equivalent to mask|set
SIG_UNBLOCK: remove the signal we want to cancel, which is equivalent to mask&~set
SIG_SETMASK: set the original table to set, which is equivalent to mask=set

Second parameter:
We have set up a new table

Third parameter:
Output type parameter, which can bring out the original table in the process, and set it to NULL if you don't care;

The above is to set the blocking state of the signal, and the following is to accept a function to obtain the attachment table of the current process, that is, to obtain the signal received by the current process

Parameter: output type parameter to obtain the appending table of the current process.

Here is an example

#include <stdio.h>    
#include <unistd.h>    
#include <signal.h>    
    
void show(sigset_t* set){    
  int i = 1;    
  for(; i < 32; i++){    
    if(sigismember(set, i)){//If the corresponding signal is received, output 1, otherwise output 0                                                                                               
      printf("1");    
    }    
    
    else{    
      printf("0");    
    }    
  }    
  printf("\n");    
}    
    
int main(){    
  sigset_t set,oset;    
  sigemptyset(&set);//Set all positions of set to 0 first    
  sigaddset(&set, 2);//Set the corresponding position of No. 2 signal to 1    
  sigprocmask(SIG_BLOCK, &set, &oset);//Set the in-process block table. The No. 2 signal of the current process is blocked    
    
  while(1){    
    sigpending(&set);//Get current process appending table    
    show(&set);//Output attaching table    
    sleep(1);    
  }    
  return 0;    
}

The process blocks signal 2 (ctrl + c is the signal 2 sent to the process), so the process sending signal 2 will not exit during the execution of the process. We show the signals received by the current process by printing the appending table in a loop.

Signal capture

Signal capture is to customize the signal processing method when the process generates a signal.
Customize the signal processing method and use the signal() function.

First parameter: the signal to customize the processing method
The second parameter: function pointer, which executes the function when the signal is received.

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

void show(sigset_t* set){
  int i = 1;
  for(; i < 32; i++){
    if(sigismember(set, i)){//If the corresponding signal is received, output 1, otherwise output 0
      printf("1");
    }

    else{
      printf("0");
    }
  }
  printf("\n");
}

void hander(int sig){
  printf("get a sig:%d\n",sig);
}

int main(){
  sigset_t set,oset;
  sigemptyset(&set);//Set all positions of set to 0 first
  sigaddset(&set, 2);//Set the corresponding position of No. 2 signal to 1
  sigprocmask(SIG_BLOCK, &set, &oset);//Set the in-process block table. The No. 2 signal of the current process is blocked

  signal(2, hander);//Custom signal processing mode 2

  int count = 0;
  while(count < 20){
    sigpending(&set);//Get the attachment table of the current process
    show(&set);//Output attaching table
    sleep(1);                                                                                                                                                  
    if(count == 10){
      sigprocmask(SIG_SETMASK, &oset, NULL);//After 10 seconds, No. 2 signal is no longer blocked. Process No. 2 signal
    }
    count++;
  }
    return 0;
}


At the beginning of the current process, the processing method of signal 2 is customized, and signal 2 is blocked. The process will not make any processing after receiving signal 2. The blocking of signal 2 will be cancelled in 10 seconds. The process processes signal 2, and signal 2 has been customized, so the program will not exit until the end of the cycle.

As mentioned earlier, the process does not necessarily process the signal immediately after the signal is generated (even if the signal is not blocked). When does the process process process process the signal? In fact, the process of execution is divided into two states: user state and kernel state. User state can execute user level code. When the process is abnormal or interrupted, or when using system call functions, it will switch from user state to kernel state. The kernel state executes the kernel level code and returns to the user state after execution. Whenever switching from the kernel state to the user state, the system will detect the signal table of the current process and process the signal.

Keywords: C Linux MySQL Operating System

Added by noiseusse on Thu, 03 Mar 2022 16:32:48 +0200