Process Signals (Linux)

Getting Started with Signals

Signaling is divided into four phases:

  1. preparation
  2. produce
  3. keep
  4. deal with

signal around

Use a simple chestnut to explain the four stages of the signal:
When we cross the road and encounter traffic lights, first of all, we can recognize the traffic lights (except for color blindness). Recognition includes two important factors: recognition, and the ability to produce corresponding behaviors ; But why do we know traffic lights? It must be educated by someone, perhaps learned in school, or educated by family members. This is actually the preparatory stage of the signal; when the light turns green, we don’t have to cross the road immediately. If we have More important things to deal with, we will choose to wait for the next time, changing the light is actually generating a signal, choosing to wait for the next time is the processing of the signal, there is still a signal preservation between signal generation and processing, that is, the signal needs to be processed Remember; the signal processing methods can also be divided into three types: default action, which is to stop at a red light and go at a green light; custom action, such as waiting for a few seconds after turning the green light before crossing the road; ignore, but not crossing the road

process signal

Migrating the above concepts to the process
First, we have a consensus: the signal is sent to the process by the operating system; the process recognizes the signal and will take corresponding actions; it is not that the process will process the signal immediately as soon as the signal is generated. Therefore, the process itself must have the ability to save signals; there are three ways for a process to process signals: default, custom, ignore, and signal processing is also called signal capture

signal set, only [1,31]common signals are learned
insert image description here

Here is another concept to understand: since the process can save the signal, where should it be saved? ? ?
In fact, it is not difficult to imagine that the signal is stored in PCB, and there is an attribute in the structure that is used to store
insert image description here
the bit position of the signal, which represents the signal number; the content of the bit represents whether the signal has been received, 0 means no, 1 means Yes; so the essence of sending a signal is to modify PCBthe signal PCBbitmap. Since is the data structure object maintained by the kernel, and the operating system is the manager of the process, no matter what signal is sent, it is essentially a signal sent by the operating system to the process. System calls related to sending signals and processing signals must be provided

generate signal

The terminal key generates a signal

For example, observe the signal in the process

int main()
{
    
    
    while(true)
    {
    
    
        cout<<"我是一个进程"<<endl;
        sleep(2);
    }
}

insert image description here

The hotkey ctrl+ccan terminate the process, the essence is that the operating system interprets it as signal No. 2SIGINT

insert image description here

introduce a function

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
  1. signum: signal number
  2. handler: function pointer, custom processing of the signal

Modify the chestnut above

void handler(int signo)
{
    
    
    cout<<"进程捕捉到一个信号,信号编号:"<<signo<<endl;
}

int main()
{
    
    
    signal(2,handler);
    while(true)
    {
    
    
        cout<<"我是一个进程"<<getpid()<<endl;
        sleep(2);
    }
}

insert image description here

Although signalthe function , the process still prints four times normally, because the signal is not caught handler, the function is not called; when the hotkey is input, the signal is caught, because the processing method is customized, so the process does not exit directly

Call a system function to signal the target process

kill

int kill(pid_t pid, int sig);
  1. pid: of the running processpid
  2. sig: sigSend a signal to the target process; can be any signal

Examples are as follows:
mysignal.cpp

void Usage(const string&proc)
{
    
    
    cout<<"\nUsage "<<proc<<" pid signo\n"<<endl;
}
int main(int argc,char*argv[])
{
    
    
    if(argc!=3)
    {
    
    
        Usage(argv[0]);
        exit(1);
    }
    pid_t pid=atoi(argv[1]);
    int signo=atoi(argv[2]);
    int n=kill(pid,signo);
    if(n!=0)
    {
    
    
        perror("kill");
    }
    return 0;
}

insert image description here

insert image description here

Signal 19 can stop the process

raise

int raise(int sig);

Send the signal sigdirectly to the process (self)

int main()
{
    
    
    int cnt=0;
    while(cnt<=10)
    {
    
    
        printf("cnt:%d,pid:%d\n",cnt++,getpid());
        sleep(1);
        if(cnt>=5)
        {
    
    
            raise(9);
        }
    }
    return 0;
}

insert image description here

insert image description here

Signal No. 9 directly kills the process

abort

void abort(void);

Send a specified signal to the process (self) to terminate the process

int main()
{
    
    
    int cnt=0;
    while(cnt<=10)
    {
    
    
        printf("cnt:%d,pid:%d\n",cnt++,getpid());
        sleep(1);
        if(cnt>=5)
        {
    
    
            abort();
        }
    }
    return 0;
}

insert image description here

insert image description here

The designated signal sent is actually signal No. 6

hardware exception signal

The generation of the signal does not have to be explicitly sent by the user

Observe the following code:

int main()
{
    
    
    while(true)
    {
    
    
        cout<<"我正在运行中..."<<endl;
        sleep(1);
        int a=10;
        a/=0;
    }
}

insert image description here

The result of the program running is overflow. Why does it a/=0terminate the process? The process termination is a signal sent by the operating system. How is it done?

insert image description here

Perform calculations in the CPU and store the calculation results in registers. Since the a/=0calculation results overflow, the status register will record the overflow flag as 1. Since the operating system manages software and hardware resources, CPU operation exceptions will be known to it. , and then send signal No. 8 to the process to let the process terminate

Verify as follows:

void catchSIG(int signo)
{
    
    
    cout<<"获取一个信号,信号编号是: "<<signo<<endl;
}

int main()
{
    
    
    signal(SIGFPE,catchSIG);
    while(true)
    {
    
    
        cout<<"我正在运行中..."<<endl;
        sleep(1);
        int a=10;
        a/=0;
    }
}

insert image description here

The signal is caught, but the process does not exit directly; receiving the signal does not necessarily make the process exit, since the process has not exited, it may be acquired again

There is only one register inside the CPU, and the memory in the register saves the context of the process; when the process is switched, the status register will be saved and restored, and every time it is restored, the operating system will recognize the status of the status register inside the CPU If the overflow flag is 1, signal No. 8 will be sent to the process

Signals generated by software conditions

In the pipeline in the previous chapter, if the reading end of the pipeline is closed, the process will receive signal 13 and end the process. This signal is generated under software conditions; here is another signal generated under software conditions

unsigned int alarm(unsigned int seconds);

Calling alarmthe function can set an alarm clock, that is, tell the kernel secondsto send a signal to the current process in seconds SIGALRM, and the default processing is to terminate the current process

Code:

int main()
{
    
    
    int cnt=0;
    alarm(1);
    while(true)
    {
    
    
        cout<<"cnt: "<<cnt++<<endl;
    }
}

insert image description here

Why is it said alarmthat a function is information generated under software conditions? ? ?

The alarm clock is actually implemented by software. Any process can alarmset an alarm clock in the kernel through a system call. There may be many alarm clocks in the operating system. In order to manage these alarm clocks, the operating system needs to describe and then organize

describe first
insert image description here

reorganize

insert image description here

The operating system periodically checks these alarms; if they time out, the operating system sends a signal SIGALARMto the process, which alarm.pcalls

Finally, there is one more point, the core dump when the process exits
Observe the code

int main()
{
    
    
    int a[10];
    a[1000]=10;
    return 0;
}

insert image description here
insert image description here

A segfault occurs in the program, and the process coreexits , and there is a signal above termit exits, which is called a normal exit; corewhen exiting, the program will do some other operations

coreWhen exiting from the cloud server , no obvious phenomenon can be seen, and it is closed by defaultcore file
insert image description here

You can open the cloud server by yourselfcore file

insert image description here

run the program again

insert image description here

A new content appears after the segment fault core dumped, which is the core storage: when the process is abnormal, the operating system will dump the valid data of the process at the corresponding time to the disk. After running, there will be an extra file core.2545. problematic processpid

The meaning of the core dump is for debugging, and it is more convenient for users to check the cause of the process crash.
Let’s practice it:
insert image description here

Summarize

  1. All the above signal generation is performed by the operating system, because the operating system manages hardware and software resources
  2. Signal processing is not immediate, but when appropriate
  3. The signal is not processed immediately, the signal will be temporarily stored in the PCB
  4. Before the process receives the signal, it already knows what to do
  5. The operating system sends a signal to the process, which is actually to modify the bitmap in the PCB

blocking signal

Signal Other related common concepts

  1. The actual execution of the signal processing action is called signal delivery
  2. The state between signal generation and delivery is called signal pending
  3. A process can choose to block a signal
  4. After the blocked signal is generated, it will be kept in the pending state until the process unblocks the signal before performing the delivery action
  5. Blocking and ignoring are different. As long as the signal is blocked, it will not be delivered. Ignoring is essentially a processing action selected after delivery.

Representation in the kernel

insert image description here

Each process has two flag bits representing blocking and pending respectively, and a function pointer representing processing action; when a signal is generated, the kernel sets the pending flag of the signal in the process control block, and the signal is not cleared until the signal is delivered flag; if a signal is not generated, it does not prevent it from being blocked first

sigset_t

sigset_tEach signal has only one bit, which is either 0 or 1. It does not record how many times the signal has been generated, and the same is true for the blocking flag; both pending and blocked can be stored in the same data type, called a signal set, which can sigset_tbe Indicates the valid or invalid state of each signal; blocking signal is also called the signal mask word of the current process, and masking is blocking rather than ignoring

Signal Set Manipulation Functions

sigset_tThe type uses one bit for each signal to indicate whether it is valid or invalid, and how to store these bits inside the type depends on the system implementation

int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set,int signo);
int sigdelset(sigset_t *set,int signo);
int sigismember(const sigset_t *set,int signo);
  1. The function sigemptyinitializes setthe signal set pointed to by and clears the bits corresponding to all the signals, indicating that the signal set does not include any valid signals
  2. The function sigfillsetinitializes setthe signal set pointed to, so that the bit positions corresponding to all signals in it are 1, indicating that the effective signals of this signal set include all signals supported by the system
  3. Function sigaddsetto add a signal to the signal set; function sigdelsetto delete a signal from the signal set; function sigismemberto determine whether a certain signal is in the signal set

sigprocmask

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

If oldsetit is a non-null pointer, read the current signal mask of the process and oldsetsend it through the parameter. If it setis a non-null pointer, change the current signal mask of the process. The parameter howindicates how to change it; if oldsetboth setare non-null pointers, the original signal The mask word is backed up to oldset, and then the current signal mask word is changed according to setthe and parametershow

SIG_BLOCK set contains all signals to be added to the signal set
SIG_UNBLOCK set contains all signals to be removed from the signal set
SIG_SETMASK Set the current signal mask word to the value pointed to by set

sigpending

int sigpending(sigset_t *set);

Read the pending signal set of the current process and setsend it out through parameters

By default, all signals are not blocked. If the signal is blocked, the signal will not be delivered.
Code implementation: block signal 2

#include<iostream>
#include<signal.h>
#include<unistd.h>
#define BLOCK_SIGNAL 2
#define MAX_SIGNUM 31
using namespace std;
void show_pending(const sigset_t& pending)
{
    
    
    for(int signo=MAX_SIGNUM;signo>=1;signo--)
    {
    
    
        if(sigismember(&pending,signo))
        {
    
    
            cout<<"1";
        }
        cout<<"0";
    }
    cout<<"\n";
}

int main()
{
    
    
    //1.屏蔽指定信号
    sigset_t block,oldblock,pending;
    //1.1初始化
    sigemptyset(&block);
    sigemptyset(&oldblock);
    sigemptyset(&pending);
    //1.2添加要屏蔽的信息
    sigaddset(&block,BLOCK_SIGNAL);
    //1.3开始屏蔽
    sigprocmask(SIG_SETMASK,&block,&oldblock);
    
    //2.遍历打印pending信号集
    while(true)
    {
    
    
        //2.1初始化
        sigemptyset(&pending);
        //2.2获取
        sigpending(&pending);
        //2.3打印
        show_pending(pending);
        //间隔一秒
        sleep(1);
    }
    return 0;
}

insert image description here

capture signal

How the kernel implements signal capture

Processes in the operating system exist in two states: user mode and kernel mode; user mode generally accesses operating system resources and hardware resources. To achieve this purpose, the system call interface provided by the system must be used, and the identity of the system call must be It is the kernel, why can users access the system call interface? ? ?

There are many registers in the CPU, which are divided into: visible registers and invisible registers. As long as they are strongly related to the process, they store the context data of the process; the register named CR3 stores the running level of the current process: 0 means Kernel mode, 3 means user mode, at the starting position of the system call interface, there is an int 80 assembly, which will modify the user mode to the kernel mode, so that the system call interface can be accessed as the kernel mode

What about the specific process of the process accessing the system call interface as the kernel? ? ?
I have learned in the previous process space that the process space includes user space and kernel space, and the system call interface is related to this kernel space: each process has its own process space, which is exclusive to user space, and there is only one copy of kernel space, that is, It is said that all processes share the same kernel space; when a process accesses an interface, it only needs to jump in the process space, which is similar to loading a dynamic library into the process space

Illustration:

insert image description here

When booting, the operating system will be loaded from the disk into the kernel area in the memory. When the process accesses the system call as the kernel state, it will jump to the kernel space in the process space and map it to the memory through the kernel-level page table. Operating system Complete the corresponding call, and then jump back to user space

When the signal is generated, it will not be processed immediately, but will be processed by the operating system at an appropriate time. This appropriate time is when returning from the kernel state to the user state; therefore, the process first enters the kernel state when switching processes or system calls , to perform signal detection in kernel mode, that is, two flag bits ( pending/block) and function pointer ( handler*) in the process: if the signal is pending and not blocked, check whether the function pointer has a corresponding custom processing method, if so, Change the identity of the process kernel state to user identity to complete the corresponding processing method, and then restore to the kernel identity, and complete the remaining system calls. After the system call ends, finally change the identity to user state and continue to execute subsequent codes

Illustration:

insert image description here

It should be noted that the user mode code cannot be executed as the kernel mode, because the operating system does not trust anyone to avoid malicious damage

sigaction

int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
  1. signum: signal number to be captured
  2. act: structure pointer
    insert image description here

which contains processing methods sa_handlerand signal setssa_mask

For example, use this function to capture the No. 2 signal, take a break for 20 seconds after capturing the signal, send the No. 2 signal to the process multiple times, and observe the running results of the process

void Count(int cnt)
{
    
    
    while(cnt)
    {
    
    
        printf("cnt:%2d\r",cnt);
        fflush(stdout);
        cnt--;
        sleep(1);
    }
    printf("\n");
}

void handler(int signo)
{
    
    
    cout<<"get a signo"<<signo<<"正在处理..."<<endl;
    Count(20);
}

int main()
{
    
    
    struct sigaction act,oldact;
    act.sa_handler=handler;
    act.sa_flags=0;
    sigemptyset(&act.sa_mask);
    sigaction(SIGINT,&act,&oldact);
    while(true)
    sleep(1);
    return 0;
}

insert image description here
insert image description here

Although the same signal is sent to the process multiple times, the process only catches it twice, because when the process is delivering a certain signal, the same type of signal cannot be reached, and the system will automatically add the current signal to the mask In the word, pendingchange the position of the signal in the bitmap to 0, and send the same signal again, and the pendingposition of the signal in the bitmap will be changed to 1; when the process completes the delivery of the signal, the system will automatically release the signal Shield, so the system will immediately deliver pendingthe signal in the bitmap, that is, capture the second signal

When we are processing signal No. 2, we also want to block signal No. 3. At this time, we only need to add signal No. 3 to the sa_masksignal set.

void Count(int cnt)
{
    
    
    while(cnt)
    {
    
    
        printf("cnt:%2d\r",cnt);
        fflush(stdout);
        cnt--;
        sleep(1);
    }
    printf("\n");
}

void handler(int signo)
{
    
    
    cout<<"get a signo"<<signo<<"正在处理..."<<endl;
    Count(20);
}

int main()
{
    
    
    struct sigaction act,oldact;
    act.sa_handler=handler;
    act.sa_flags=0;
    sigemptyset(&act.sa_mask);
    //添加3号信号
    sigaddset(&act.sa_mask,3);
    sigaction(SIGINT,&act,&oldact);
    while(true)
    sleep(1);
    return 0;
}

insert image description here
insert image description here

The difference from the above is that the process here exits directly at the end. In fact, it is because after the No. 2 signal is blocked, the No. 2 signal is executed again, and finally the No. 3 signal is executed to end the process.

The principle of process signal processing is to process signals of the same type serially, recursion is not allowed

Guess you like

Origin blog.csdn.net/qq_68006585/article/details/130574157