Linux signal (signal kill alarm raise abort settimer sigaction SIGCHLD recycles child process)

1. Basic concepts of signals:

1. Signal mechanism

When process A sends a signal to process B, once process B receives the signal, it will stop the executing process and process the signal. After processing the signal, it will continue to return to execute the previous process. It can be seen that the priority of the signal is relatively high. .

2. Signal status

Signals have three states: generated, pending, and delivered. Signals can be generated by pressing the keys ctrl + \, ctrl + c…, etc., or through system calls (kill raise abort will be discussed later). Pending literally means that it has not been executed, that is, it has not been executed. The meaning of processing is in the intermediate state between production and delivery. Delivery means delivery and has reached the process and has been processed.

3. Signal processing methods

Execute the default processing action (mostly termination), ignore the signal (throw it away without processing), capture the signal (register a custom signal processing function)

4. Four elements of signals

The number of the signal, the name of the signal, the default processing action of the signal, and the conditions for generating the signal (these can be viewed in man 7 signal)

5. Pending signal set and blocking signal set

The blocking signal collection stores signals blocked by the current process. If the current process receives blocking signals, these signals need to be blocked and not processed. If a signal is not processed for some reason after it is generated, then the set of such signals is called a pending signal set. The signal remains pending until the block is unblocked. If the signal is unblocked from the blocked signal set, the signal is processed and removed from the pending signal set.

2. Signal-related functions

1.signal function registers custom signal processing

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

The prototype of the signal function is as above. Its first parameter is the value of the signal to be registered, which is usually passed in the corresponding macro. The second parameter is a callback function, which is a function written by yourself to process the signal. Let’s look at a simple example of the signal function.

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

void handler(int signo)
{
    printf("signo=[%d]\\
",signo);
}
int main()
{
    signal(SIGPIPE,handler);
    int fd[2];
    int pip=pipe(fd);
    if(pip<0)
    {
        perror("pipe error\\
");
        return -1;
    }
    close(fd[0]);
    while(1)
    {
        write(fd[1],"hello",sizeof("hello"));
    }
    return 0;
}

This signal registration code has been briefly introduced in the communication between processes last time. Here, signal is used to capture the pipe rupture signal. We close the read end of the pipe and let it keep writing, which will cause the pipe to rupture, and then signal The function will capture this signal. The integer value of this signal is 13. Use SIGPIPE when using it.

This will always be printed on the screen because the loop writing is set up and execution will continue after the pipe bursts. Here you need to pay attention to how the callback function is written. The callback function has one parameter.

2.kill function

The function of the kill function is to send a signal to the specified process.

The first parameter is the pid of the process to be killed. You can check the manual for details. The most commonly used parameter is the pid of the process to be killed. The second parameter is the number of the signal.

You can use the kill function to send a termination signal SIGKILL to the process itself.

If the following sentence is not executed, it means that the process has received the signal and ended its operation.

When using the kill function, you need to pay attention to the different effects of passing in different parameters. Some parameters need to be used with caution. If you are not careful, the entire console program may be closed.

3.abort and raise functions

The abort function sends the SIGABRT signal to the current process and terminates the current process.

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

void handler(int signo)
{
    printf("signo=[%d]\\
",signo);
}
int main()
{
    signal(SIGABRT,handler);
    abort();
    printf("----\\
");
    return 0;
}

We register a signal capture function to capture the SIGABRT signal, and after the abort function is executed, the subsequent printf will not be executed. This abort function is equivalent to kill(getpid(), SIGABRT);

The raise function sends a specified signal to the current process, which is equivalent to kill(getpid(),signo); it returns 0 on success and a non-0 value on failure.

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

void handler(int signo)
{
    printf("signo=[%d]\\
",signo);
}
int main()
{
    signal(SIGPIPE,handler);
    int ret=raise(SIGPIPE);
    printf("----\\
");
    return 0;
}

Register a signal capture function as above to capture the current signal, and we can send a SIGPIPE signal to the current process at will.

4.alarm function

The alarm function is used to set a timer, which can be understood as setting an alarm clock. After specifying seconds, the kernel will send a SIGALRM signal to the current process. After the process receives the signal, it will terminate by default. Each process has only one timer. The function returns 0 or how many seconds are left. The first call to this function returns 0, and the second call returns the remaining seconds.

int alarm (int seconds);

Let’s look at a piece of test code:

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

void handler(int signo)
{
    printf("signo=[%d]\\
",signo);
}
int main()
{
    signal(SIGALRM,handler);
    int ret=alarm(9);
    printf("%d\\
",ret);
    sleep(2);
    ret=alarm(1);
    printf("%d\\
",ret);
    //alarm(0);//The sentence after canceling the clock will not be output
    //printf("%d\\
",ret);
    sleep(10);
    return 0;
}

Let me explain the function of the last sleep. If there is no sleep, the program will be launched directly after execution and the timer will not be triggered yet. Here we set a 9s timer. The first return value should be 0, so the first printed value should be 0. The second return value is how long the timer is left. It sleeps for two seconds. , so the output here is 7.

The answer is what we imagined. Note that alarm(0) means canceling the clock. If the clock is canceled, the SIGALRM signal will not be issued, so it will not be blocked. Here’s an interesting test to see how many times your computer can count in one second.

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

int main()
{
    int i=0;
    alarm(1);
    while(1)
    {
        printf("%d\\
",i + + );
    }
    return 0;
}

It seems that the faster the computer operates, the more numbers it can count in one second.

5.setitimer function

The function prototype is as follows:

int setitimer(int which, const struct itimerval *new_value,
                     struct itimerval *old_value);

The first parameter which indicates specifying the timing method. There are three methods:

ITIMER_REAL calculates the natural time and returns the SIGALRM signal at the end.

ITIMER_VIRTUAL virtual space timing, only calculates the time the process occupies the CPU, and returns the SIGVTALRM signal.

ITIMER_PROF runs the timing, calculates the time it takes to occupy the CPU and execute system calls, and returns the SIGPROF signal at the end.

The second parameter new_value is a structure:

struct itimerval {
               struct timeval it_interval; /* next value */
               struct timeval it_value; /* current value */
           };

           struct timeval {
               time_t tv_sec; /* seconds */
               suseconds_t tv_usec; /* microseconds */
           };

it_interval represents the alarm clock departure period it_value represents the departure time it_sec seconds it_usec microseconds

Just pass NULL as the last parameter. Here is an example to illustrate how to use the setitimer function:

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

void handler(int signo)
{
    printf("HELLO WORLD %d\\
",signo);
}
int main()
{
    signal(SIGALRM,handler);
    struct itimerval tm;
    tm.it_interval.tv_sec=1;//Set the period
    tm.it_interval.tv_usec=0;

    tm.it_value.tv_sec=2;
    tm.it_value.tv_usec=0;

    setitimer(ITIMER_REAL, & tm,NULL);
    while(1)
    {
        sleep(1);//Prevent the above from printing too fast
    }
    return 0;
}

Above we set up a timer with a period of one. Starting from the second second, HELLO WORLD will be printed every second. If you don’t remember the structure, you can check the manual.

6. The relationship and related functions between pending signal sets and blocked signal sets

As mentioned above, the blocking signal set is the set of signals that the current process wants to block, and the pending signal set is the set of signals that the current process has not yet processed. This collection is stored in the PCB of the process. Let’s use the SIGINT signal just now to explain these two signal sets: when a process receives the SIGINT signal, the pending signals of this process will first be concentrated, indicating that the signal is in a pending state, and the pending signals at that time will be concentrated at this location. Set to 1. When this signal needs to be processed, it will first check whether the corresponding position in the blocked signal set is 1. If it is 1, then the signal will be blocked and the signal will not be processed, so it is pending. The position corresponding to the signal set will also remain at 1. If it is not 1, then the current signal will be processed, the default processing action will be performed, and the position corresponding to the pending signal set will be set to 0. When the SIGINT signal is unblocked from the blocked signal set, it will be processed.

Let’s take a look at the signal set-related functions: First, let’s learn about the creation of a blocking signal set–sigset_t set This creates a blocking signal set.

int sigemptyset(sigset_t *set) sets all positions in the set to 0; initialize
int sigfillset(sigset_t *set) sets all positions in the set to 1
int sigaddset(sigset_t *set, int sognum) adds a signal to the signal set
int sigdelset(sigset_t *set,int signum) deletes a signal from the set
int sigmismember(sigset_t *set,int signum) Check whether a signal is in the set
int sigprocmask(int how,const sigset_t *set,*oldset) sets the blocking signal set
int sigpending(sigset_t *set) reads the pending signal set of the current process

The last how in the blocking signal set function can be passed in SIG_BLOCK to set a certain signal to blocking, SIG_UNBLOCK to unblock a certain signal, and SIG_SETMASK to set it directly, including the functions of the above two. See the examples below for specific usage methods. . Let’s look at an example below. We add the SIGINT signal to the blocking signal signal set, and then use ctrl + c on the keyboard to generate the SIGINT signal to determine whether the signal is in the pending signal set. If it is, it will output 1, otherwise it will output 0. The program is as follows:

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

void handler(int signo)
{
    printf("signo=[%d]\\
",signo);
}
int main()
{
    signal(SIGINT,handler);
    sigset_t set;
    sigemptyset( & amp;set);
    
    sigaddset(&set,SIGINT);//Add signal to set
    sigprocmask(SIG_BLOCK, & amp;set,NULL);//Add the signal to the blocking signal set
    
    sigset_t pending;
    sigset_t oldset;
    int i=0;
    int j=0;
    while(1)
    {
        sigemptyset( & amp;pending);
        sigemptyset( & amp;oldset);
        sigpending( & amp;pending);//Get the signals in the pending signal set
        for(i=1;i<32;i + + )
        {
            if(sigismember( & amp;pending,i)==1)//Determine whether the signal is within that range
            {
                printf("1");
            }
            else
            {
                printf("0");
            }
        }
        printf("\\
");
        //Loop ten times to unblock and execute the signal processing function
        if(j + + ==0)
        {
            //sigprocmask(SIG_UNBLOCK, & amp;set,NULL);
            sigprocmask(SIG_SETMASK, & amp;oldset,NULL);//Unblock
        }
        else
        {
            sigprocmask(SIG_BLOCK, & set,NULL);
        }
        sleep(1);
    
    }
    return 0;
}

After adding it to the blocked signal set earlier, it will be unblocked every ten times in the loop. After being unblocked, the signal processing function we registered will be processed.

Observe the operation As a result, we can find that although we input the signal many times before, the signal will only be processed once, which proves that the signal does not support queuing.

7.sigaction function

The functions of sigaction and signal are similar. They both register signal processing functions. You can regard sigaction as an upgraded version of signal. The function prototype of sigaction is as follows:

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

The first parameter is the signal you want to register, which is usually passed in the corresponding macro. The second parameter is an incoming parameter, a structure:

struct sigaction {
               void (*sa_handler)(int);//Signal processing function
               void (*sa_sigaction)(int, siginfo_t *, void *);//Basically not used
               sigset_t sa_mask;//The function that needs to be blocked during the execution of the signal processing function is regarded as a blocking signal set
               int sa_flags;//usually 0, indicating the default flag
               void (*sa_restorer)(void);//This parameter is almost never used
           };

Let’s look at an example to see the specific usage of sigaction:

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

void handler(int signo)
{
    printf("signo=[%d]\\
",signo);
    sleep(3);
}
int main()
{
    struct sigaction act;
    act.sa_handler=handler;
    sigemptyset( & amp;act.sa_mask);//Set the signal to empty without blocking it
    sigaddset( & amp;act.sa_mask,SIGQUIT);
    act.sa_flags=0;
    sigaction(SIGINT, &act,NULL);
    signal(SIGQUIT,handler);
    while(1)
    {
        sleep(1);
    }

    return 0;
}

For the sa_mask member of the sigaction structure, if you do not need to block the signal, just call the sigemptyset function directly to initialize it to all 0s. If you want to block the signal, then you need to call the sigaddset function to add it to the blocking signal set. In this example, we set the SIGQUIT signal to blocking in the act attribute. Then only when the SIGINT signal processing function is executed, if the SIGQUIT signal is received, it will be blocked. When the SIGINT signal processing function is completed, it will be SIGQUIT unblocks and executes its corresponding signal processing function.

It is worth noting that during sleep(3), both signals are generated many times at the same time, but will only be executed once. This also proves that the above signal does not support the idea of queuing. At the same time, it should be noted here that the program here needs to be killed by re-opening a window and using the kill command.

8..SIGCHLD signal recycling child process

Result: Each process will send the SIGCHLD signal to its parent process when it finishes running.

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

void sighandler(int signo)
{
    printf("signo=%d\\
",signo);
}
int main()
{
    pid_t pid=fork();
    signal(SIGCHLD,sighandler);
    if(pid<0)
    {
        perror("fork error\\
");
        return -1;
    }
    else if(pid==0)
    {
        printf("child process pid=%d,ppid=%d\\
",getpid(),getppid());
        while(1)
        {sleep(1);}
    }
    else if(pid>0)
    {
        printf("father process pid=%d,ppid=%d\\
",getpid(),getppid());
        while(1)
        {sleep(1);}
    }

    return 0;
}

Borrow the code from the previous parent-child process and register a processing function for the SIGCHLD signal. Then we manually kill the child process and observe the capture of the SIGCHLD signal.

9.SIGCHILD recycles multiple child processes

Here we use the SIGCHLD signal to recycle the child process. First, let’s take a look at the conditions for generating the SIGCHLD signal. When a process ends or receives the SIGSTOP SIGCONT signal, a SIGCHLD signal will be sent to its parent process. The parent process will receive the SIGCHLD signal. After this signal, the child process begins to be recycled. Let’s look at an example of using the SIGCHILD signal to recycle child processes. First, three child processes are created in a loop in the same way as before. Note that there must be a break in the child process to prevent the child process from being created repeatedly. Later, a signal processing function must be registered in the parent process. , use the sigaction function, and use the waitpid function in the signal processing function to recycle the child process. Of course, there will be many situations in this process that will generate zombie processes. First, if your signal processing function has not completed registration, the three child processes will has exited. At this time, the parent process has not completed the recycling of the child process, and three zombie processes will be generated:

Run the program and observe the running status of the process and you will find three sub-processes:

For this situation, we can set the SIGCHILD signal to blocking before the signal registration function is completed, and then unblock it after completing the registration of the signal processing function; of course, there are also problems in the signal processing function, because the signal is not It supports queuing. If you send multiple signals continuously, the signal will only be processed once. Therefore, we need to recycle all the sub-processes when receiving a signal. We need to set up a recycling sub-process in the signal processing function; The above two points are small details that are easily overlooked and should be noted! Here is the full code:

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<sys/wait.h>

void handler(int signo)
{
    while(1)
    {
        pid_t pid=waitpid(-1,NULL,WNOHANG);
        if(pid>0)
        {
            printf("Recycled %d\\
",pid);
        }
        else if(pid==0)
        {
            continue;
        }
        else if(pid==-1)
        {
            printf("recycle over\\
");
            break;
        }
    }
}
int main()
{
    sigset_t set;
    sigemptyset( & amp;set);
    sigaddset( & amp;set,SIGCHLD);
    sigprocmask(SIG_BLOCK, & set,NULL);
    int i=0;
    for(i=0;i<3;i + + )
    {
        pid_t pid=fork();
        if(pid<0)
        {
            perror("fork errror\\
");
            return -1;
        }
        else if(pid==0)//child process
        {
            printf("child process pid=[%d],ppid=[%d]\\
",getpid(),getppid());
            break;//When it is detected that it is a child process, it will jump out, and the child process will not create a grandson process in a loop.
        }
        else if(pid>0)//parent process
        {
            printf("father process pid=[%d],ppid=[%d]\\
",getpid(),getppid());
        }

    }
    if(i==0)//first child process
    {
        printf("child 1 :pid = [%d],ppid = [%d]\\
",getpid(),getppid());
    }
    if(i==1)//The second child process
    {
        printf("child 2 :pid = [%d],ppid = [%d]\\
",getpid(),getppid());
    }
    if(i==2)//The third sub-process
    {
        printf("child 3 :pid = [%d],ppid = [%d]\\
",getpid(),getppid());
    }
    if(i==3)//Recycle the child process in the parent process
    {
        printf("father :pid = [%d],ppid = [%d]\\
",getpid(),getppid());
        struct sigaction act;
        act.sa_handler=handler;
        sigemptyset( & amp;act.sa_mask);
        act.sa_flags=0;
        sleep(5);
        sigaction(SIGCHLD, & amp;act,NULL);
        sigprocmask(SIG_UNBLOCK, & amp;set,NULL);
        while(1)
        {
            sleep(1);
        }
    }
    return 0;
}

It will be much simpler to look at this code after clarifying the entire processing logic. It also includes the use of the main waitpid function and the settings of its related parameters.

Observation and Discovery , all child processes have been recycled, and no zombie processes have been generated. Finally, note that SIGKILL and SIGSTOP cannot be captured!