Linux | Process termination and process waiting

Table of Contents

Preface

1. Process termination

1. Several possibilities for process termination

2. exit and _exit

2. Process waiting

1. Why should the process wait?

2. How to wait for the process

(1) wait function

(2) waitpid function

3. Deeply understand process waiting again


Foreword

When we introduced the process earlier, we said that the child process exits. The parent process does not recycle resources from the child process, and the child process will enter the zombie state. For the operating system, this is a resource leak, and it is also a resource leak at the operating system level, unless the parent process The process exits, otherwise the child process will always be in the zombie state. This chapter introduces how the parent process recycles the child process;

1. Process Termination

1. Several possibilities for process termination

Before introducing the recycling of child processes, we must have a certain understanding of process termination; the so-called process termination refers to the exit of the process. Here we must first wait until the process exits to have the following three possibilities;

1. The program ends normally and the result is correct;

2. The program ends normally and the result is incorrect;

3. The program crashes and the result is not important;

Maybe we didn’t have any concept of process exit before. How do we distinguish the above three situations? For example, when we used to run our C/C++ programs on virtual studio, we would always write a main function, and generally we would write a return 0 at the end of the main function; in fact, this 0 is the exit code We use this to determine whether the result is correct. The exit code has a corresponding interpretation. We can print out the meaning of the exit code through the strerror function; this is the method to distinguish between case one and case two. For case three, the program crashes. , our software virtual studio will usually pop up a pop-up window to tell you what caused the program to crash. The most common ones are division-by-zero errors, null pointer dereferences, etc., which will cause the program to crash; let’s print the exit code below; The following code;

We compile and run the above program and the results are as follows;

We found that the error code information can be edited up to number 133. We are also familiar with the previous items, among which the first item 0 means success and the result is correct;

2. exit and _exit

We said before that in the main function, we can use the return statement to exit the process and return the return value; so what if we are not in the main function? So do we have to return to the main function and then call the return statement? That would be too troublesome. In fact, we can also terminate the process through the exit function and _exit function; the following code;

We compile the code and the results are as follows; here is a command echo $?; you can view the return value of a recently run program. We find that when we enter a value other than -1, the return value is 0, which is in the main function return statement, and when we enter -1, the return value is 14, which is the return value when we call the exit function;

The same function can be achieved by replacing the above exit function exit with _exit, so what is the difference? Look at the following code;

When we use the exit function, the running results are as follows;

When we use the _exit function, the results are as follows;

We can no longer see you can see me; this is because our printed content is still in the C language buffer, and our _exit is a system call, and the buffer cannot be refreshed before exiting; and our exit is C language library function will refresh our buffer;

Supplement: The buffer refresh mechanism of C language is line refresh. Because there is no newline character when printing, it will print when we use the exit function, but not when using the _exit function;

2. Process waiting

1. Why should the process wait

First, this reason has been explained earlier. When the child process exits, the parent process does not recycle resources from the child process. The child process will always be in a zombie state, and this state will cause system resource leakage. At this time We need to “collect the corpse” of the child process by waiting for the process;

Secondly, if we let the sub-process complete the task, do we need the sub-process to complete it? For certain times, there is such a demand, so another function of process waiting is to obtain the completion status of the sub-process task;

2. How to wait for the process

(1) wait function

Regarding how to wait for a process, we usually implement it through the system calls wait and waitpid; we first look at the function declaration of wait;

This function has only one parameter, which is an output parameter. The so-called output parameter means that we pass a pointer, and the function will assign the value executed by this pointer and return it to us; this output parameter is the exit code and termination signal of the child process. , here we temporarily set it to NULL, and wait until waitpid is introduced before introducing it;

The return value of this function, if the call is successful, returns the child process pid, if it fails, it returns -1. The error code is set, we write the following code; check whether the parent process has recycled the child process;

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

int main()
{
    int id = fork();
    if(id == -1)
    {
        // fork call failed
        exit(1);
    }
    else if(id == 0)
    {
        // child process
        int cnt = 5;
        while(cnt--)
        {
            printf("I am a child process, my pid:%d,ppid:%d\\
", getpid(), getppid());
            sleep(1);
        }
        exit(14); // exit code
    }
    else
    {
        // parent process
        sleep(7);
        wait(NULL); // process waits
        sleep(2);
    }
    return 0;
}

We then enter the following script commands on the command line to monitor these two processes;

while :; do ps -axj | head -1 & amp; & amp; ps -axj | grep test | grep -v grep; sleep 1; echo “————— —-“; done

We found that the first few seconds were indeed running, and then the child process was in a zombie state for two seconds in the middle, because the parent process slept for two seconds more than the child process, as we expected; then only the parent process was left; the child process was successfully replaced by the parent process Recycle;

(2) waitpid function

The following is the result of our query through the man manual;

Parameter pid:

pid Function
pid < -1 Waiting for any child process whose process group number is the absolute value of pid.
pid = -1 Wait for any child process, At this time, the waitpid() function degenerates into an ordinary wait() function.
pid = 0 Waiting process group number and current Any child process of the same process, that is, any process in the same process group as the process calling the waitpid() function.
pid > 0 Waiting for the process number pid child process.

Note: The process group number is not mentioned here for the time being. We don’t use it much. The most commonly used ones are 2 and 4;

Parameter status:

This parameter is an output parameter, the same as our wait function; regarding the use of this parameter, we cannot use the int value in it as a whole, but must use it separately by bit;

Situation 1:Exit normally

At this time, we use the 7th to 15th bits as the exit code; we can obtain this exit code through (status >> 8) & amp; 0xFF, or we can use the macro function WEXITSTATUS to obtain it, and use the macro function WIFEXITED To get whether the process exited normally, if it exits normally, it returns true, otherwise it returns false;

Case 2: Signal termination exit (abnormal exit)

Here we use the following bits 0 to 6 to indicate signal termination. We can use status & amp; 0x7F to get this termination signal. As for the core dump flag bit here, we will not explain it yet, this is another topic;

Parameter options:

By default, this parameter is filled with 0, which means blocking waiting. If WNOHANG is filled in, it means non-blocking waiting;

Return value:

The return value of waitpid is slightly more complicated than wait. There are three situations;

1. Return normally, then return the child process pid;

2. If WNOHANG is set and the child process has not exited, 0 will be returned. If the child process exits, the child process pid will be returned;

3. If the call fails, -1 is returned and the error code is set;

Coding Practice:

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

int main()
{
    int id = fork();
    if(id == -1)
    {
        // fork call failed
        exit(1);
    }
    else if(id == 0)
    {
        // child process
        int cnt = 5;
        while(cnt--)
        {
            printf("I am a child process, my pid:%d,ppid:%d\\
", getpid(), getppid());
            sleep(1);
        }
        exit(14); // exit code
    }
    else
    {
        // parent process
        sleep(7);
        int status = 0;
        // process waits
        int ret = waitpid(id, & status, 0); // This function has the same function as our wait function
        if(WIFEXITED(status))
        {
            printf("The child process exited normally, the exit code is %d\\
", WEXITSTATUS(status));
        }
        else
        {
            printf("The child process exited abnormally and received signal %d\\
", (status) & amp; 0x7F);
        }
        sleep(2);
    }
    return 0;
}

The code output is as follows;

If we add a divide-by-zero error to the code;

The running results are as follows;

We then check the signal through kill -l; signal No. 8 is our floating point calculation problem;

Let’s change the code again and change our waitpid to non-blocking waiting, as follows;

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

int main()
{
    int id = fork();
    if(id == -1)
    {
        // fork call failed
        exit(1);
    }
    else if(id == 0)
    {
        // child process
        int cnt = 5;
        // Divide by zero error display
        // int a = 10/ 0;
        while(cnt--)
        {
            printf("I am a child process, my pid:%d,ppid:%d\\
", getpid(), getppid());
            sleep(1);
        }
        exit(14); // exit code
    }
    else
    {
        // parent process
        int status = 0;
        // process waits
        while(true)
        {
            int ret = waitpid(id, & status, WNOHANG);
            if(ret == -1)
            {
                printf("waitpid call failed\\
");
                exit(-1);
            }
            else if(ret == 0)
            {
                printf("The child process has not exited yet, I will do something else\\
");
                sleep(1);
            }
            else
            {
                printf("Waiting for success\\
");
                break;
            }
        }
        
        if(WIFEXITED(status))
        {
            printf("The child process exited normally, the exit code is %d\\
", WEXITSTATUS(status));
        }
        else
        {
            printf("The child process exited abnormally and received signal %d\\
", (status) & amp; 0x7F);
        }
        sleep(2);
    }
    return 0;
}

The running results are as follows. At this time, our parent process does not need to block and wait for the child process to end. The parent process only needs to recycle the child process through polling;

3. Deeply understand process waiting again

The above content is the practical part of process waiting. Now we return to the theoretical part again. I have the following questions;

Question 1: Can we obtain the exit code of the child process through a global variable?

No, although the parent process and the child process share a piece of code, they each have their own process address space. When we use a global variable, when the child process writes to this global variable, copy-on-write will occur, so the exit code cannot be obtained. , which is also the independence of the process;

Question 2: Since the process is independent, how do wait and waitpid obtain the exit code of the child process?

Our wait and waitpid are system calls. Since they are system calls, they are of course part of the operating system. When we recycle the child process, we are actually destroying the process PCB and other kernel data, and there is an exit in the PCB (task_struct) Code and exit signal fields, the following is a screenshot of the Linux source code;

We did find these fields in task_struct. If you are interested, you can go to the official website to download a copy of the source code. The task_strcut structure is in include/linux/sche.h;

Back to the topic, since we have these fields in our task_struct, can we obtain the information of these fields when our parent process recycles the child process? The answer is of course yes. Our wait and waitpid are system calls and are of course qualified to obtain these fields. Our parent process can also get the exit code of the child process through these two system calls;

The knowledge points of the article match the official knowledge files, and you can further learn relevant knowledge. CS entry skill treeLinux introductionFirst introduction to Linux 37704 people are learning the system