Linux | Process termination and process waiting

Table of contents

Preface

1. Process termination

1. Several possibilities for process termination 

2、exit 与 _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


Preface

        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 idea about 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 Exit code, we use this to determine whether the result is correct. The exit code has a corresponding explanation. We can print out the meaning of the exit code through the strerror function; this is to distinguish case one and case two. Method, for case 3, the program crashes, our software virtual studio will usually pop up a pop-up window to tell you where the program crashed. The most common ones are division-by-zero errors, null pointer dereferences, etc., which will cause the program to crash. Crash; let’s print the exit code; 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 first few items, among which the first item 0 means success and the result is correct;

2、exit 与 _exit

        We said before that in the main function, we can let the process exit through the return statement 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 above exit function exit can be replaced by _exit to achieve the same function, 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 running 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;

        Second, 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调用失败
        exit(1);
    }
    else if(id == 0)
    {
        // 子进程
        int cnt = 5;
        while(cnt--)
        {
            printf("我是子进程,我的pid:%d,ppid:%d\n", getpid(), getppid());
            sleep(1);
        }
        exit(14); // 退出码
    }
    else  
    {
        // 父进程
        sleep(7);
        wait(NULL); // 进程等待
        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 && 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 query result through the man manual;

Parameter pid:

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

 

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 as a whole, but must use it separately by bit;

Case 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) & 0xFF, or we can use the macro function WEXITSTATUS to obtain it, through the macro function WIFEXITED to get whether the process exited normally, if it exits normally, it returns true, otherwise it returns false; 

Case 2:The signal terminates and exits (abnormal exit)

        Here we use the following bits 0 to 6 to indicate signal termination. We can use status & 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;

Code 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调用失败
        exit(1);
    }
    else if(id == 0)
    {
        // 子进程
        int cnt = 5;
        while(cnt--)
        {
            printf("我是子进程,我的pid:%d,ppid:%d\n", getpid(), getppid());
            sleep(1);
        }
        exit(14); // 退出码
    }
    else  
    {
        // 父进程
        sleep(7);
        int status = 0;
        // 进程等待
        int ret = waitpid(id, &status, 0);  // 此时与我们的wait函数功能相同
        if(WIFEXITED(status))
        {
            printf("子进程正常退出,退出码为%d\n", WEXITSTATUS(status));
        }
        else
        {
            printf("子进程异常退出,收到信号%d\n", (status) & 0x7F);
        }
        sleep(2);
    }
    return 0;
}

        The output of the code is as follows;

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

        The running results are as follows;

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

        We will 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调用失败
        exit(1);
    }
    else if(id == 0)
    {
        // 子进程
        int cnt = 5;
        // 除零错误展示
        // int a = 10/ 0;
        while(cnt--)
        {
            printf("我是子进程,我的pid:%d,ppid:%d\n", getpid(), getppid());
            sleep(1);
        }
        exit(14); // 退出码
    }
    else  
    {
        // 父进程
        int status = 0;
        // 进程等待
        while(true)
        {
            int ret = waitpid(id, &status, WNOHANG); 
            if(ret == -1)
            {
                printf("waitpid调用失败\n");
                exit(-1);
            }
            else if(ret == 0)
            {
                printf("子进程还未退出,我再干点别的\n");
                sleep(1);
            }
            else
            {
                printf("等待成功\n");
                break;
            }
        }
        
        if(WIFEXITED(status))
        {
            printf("子进程正常退出,退出码为%d\n", WEXITSTATUS(status));
        }
        else
        {
            printf("子进程异常退出,收到信号%d\n", (status) & 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 download a copy of the source code from the official website. 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;

Guess you like

Origin blog.csdn.net/Nice_W/article/details/134068513