Linux Chapter [6]: Process Waiting

Table of contents

1. First introduction to fork function

1. Concept

2. The parent and child processes share all the code before and after the fork, but the child process can only execute the code after the fork.

3. What did the operating system do after the fork?

Processes are independent, code and data must be independent

copy on write

Common usage of fork

Reasons why the fork call failed

4.What does the child process retain from the parent process after Fork?

5.fork and exec system calls

2. Process termination

1. Common process exits

2. Correct understanding of process termination

(1) Process exit code

 echo $? View process exit code

(2) Common practices regarding termination-exit()

(3)exit和_exit

(4) Regarding termination, what does the kernel do?

3. Process waiting

1. Why do we need to wait for the process?

①In order to solve the problem of memory leakage of zombie process

②In order to obtain the exit status of the child process

2.wait与waitpid

waitpid:

The second parameter status:

(1) The next lowest 8 bits of the lower 16 bits (the second lowest 8 bits) are the exit code

(2) The lower 8 bits are the termination signal

3. Parent process non-blocking wait (WNOHANG)

(1) Example of parent process based on non-blocking polling waiting:

(2) The parent process is based on non-blocking polling waiting. The parent process also has examples of tasks.


1. First introduction to fork function

1. Concept

The fork function is a very important function in Linux . It creates a new process from an existing process. The new process is the child process, and the original process is the parent process.
#include <unistd.h>
pid_t fork(void);
Return value: 0 is returned in the child process , the parent process returns the child process id , and -1 is returned in case of error.
The process calls fork. After control is transferred to the fork code in the kernel, the kernel does:
        ①Allocate new memory blocks and kernel data structures to the child process
        ②Copy some data structure contents of the parent process to the child process
        ③Add the child process to the system process list (link into the run queue)
        ④fork returns and starts scheduler scheduling
When a process calls fork , there are two processes with the same binary code. And they all run to the same place. But each process will be able to start their own journey, see the following procedure

2. The parent and child processes share all the code before and after the fork, but the child process can only execute the code after the fork.

Before fork, the parent process executes independently. After fork, the two execution streams of father and son execute separately.

So after the fork, is only the code after the fork shared by the father and son processes? ?
After the fork, the father and son share all the code, but the code before the fork is also shared by the father and son, but the child process can only execute the
child process after the fork. The subsequent code of ! = All the shared code, except that the child process can only start execution from here! !

why:

There is a program counter in the CPU called eip. The purpose of eip is to save the next instruction of the currently executing instruction! The
eip program counter will be copied to the child process, and the child process will start executing from the code pointed to by the eip!!

If I want the child process to get the code before the fork, I can let the child process change the eip in the CPU to the main function entry to execute the code before the fork.

3. What did the operating system do after the fork?

Process = the process data structure of the kernel + the code and data of the process.
Create the kernel data structure of the child process (struct task_ struct + struct mm_ struct + page table) + the code inherits from the parent process, and the data is shared or copied in a copy-on-write manner. independent!
 

Processes are independent, code and data must be independent

Data is independent through copy-on-write.
Code is read-only and cannot be modified.

copy on write

Usually, the code of the father and the son is shared. When the father and the son are not writing, the data is also shared. When either party tries to write, they each have a copy in the form of copy-on-write.

Common usage of fork

A parent process wants to duplicate itself so that the parent and child processes execute different code segments at the same time. For example, the parent process waits for client requests and generates child
process to handle the request.
A process wants to execute a different program. For example, after the child process returns from fork , it calls the exec function.

Reasons why the fork call failed

There are too many processes in the system
The number of processes for the actual user exceeds the limit

4.What does the child process retain from the parent process after Fork?

A.Environment variables

B. File locks, pending alarms and pending signals of the parent process

C.Current working directory

D.Process number

The function of the fork function is to create a new child process by copying the parent process.

  • Option A is correct: environment variables will be inherited from the parent process by default and are the same as the parent process.
  • Option B is wrong: signal-related information is independent and will not be copied.
  • Option C is correct: the working path is also the same
  • Option D is wrong: Each process has its own independent identifier

According to the analysis of understanding, the correct options are options A and C.

5.fork and exec system calls

  • The process generated by fork is an identical copy of the current process (the fork call creates a child process by copying the parent process, and the child process runs exactly the same code and data as the parent process)
  • The working principles of the fork system call and the clone system call are basically the same (fork creates a child process by calling clone in the kernel)
  • exec is a program replacement function and does not create a process itself.
  • The function of the clone function is to create a pcb, fork to create the process and the subsequent creation thread to essentially call the clone function internally. The exec function itself does not create the process, but replaces the program, so the working mechanism is not the same.

2. Process termination

1. Common process exits

________________________________
Common process exits: |         
1. The code is finished and the result is correct | 
2. The code is finished and the result is incorrect | 
3. The code is not finished and the program is abnormal | 
———————————————— ——

2. Correct understanding of process termination

In C/C++, why does the main function return 0;?
a.Return 0, to whom
b.Why is it 0? Are other values ​​ok?


Return 0 means the process code has finished running and the result is correct.
Return non-zero: the result is incorrect.

Return in the main function represents the end of the process. Other non-main function return represents the end of the function call

(1) Process exit code

After the code is run and the result is correct, there is nothing to say, just exit(0)/return 0. The return code is 0; if the code is finished and the result is incorrect, then what we want to know most is the reason for the failure! So: non-zero
identifies different Reason! For example, the
X in exit(13) return

 echo $? View process exit code

 echo $?: In bash, when the latest execution is completed, the exit code of the corresponding process

Explain here: the first echo $? prints the process exit code 123, the second echo $? prints the process exit code of the last echo $?, because the last echo $? was executed successfully, so the process exit code is 0,.

Generally speaking, how should I set the non-zero value of failure? And the meaning of the default expression?
You can customize it, or you can use the system-defined sterror.
Error code exit code can correspond to different error causes, making it easy to locate the problem!

(2) Common practices regarding termination-exit()

1. Return in the main function represents the end of the process. The return of other non-main functions represents the end of the function call.
2. In the main function/non-main function anywhere in your own code, calling exit() will cause the process to exit. The X in exit(X) is the exit code.

(3)exit和_exit

exit terminates the process and refreshes
the buffer_exit, which is a system call that directly terminates the process without any refresh operation.

To terminate the process, it is recommended to exit or return in the main function.

Will print: hello bit, that is, refresh the buffer

If it is _exit(0), nothing will be printed, i.e. the buffer will not be flushed

(4) Regarding termination, what does the kernel do?

Process = kernel structure + process code and data.
Process code and data will definitely be released, but task/struct && mm_ struct: The operating system may not release the kernel data structure of the process
because the object is created again: 1. Open up space 2. Initialization It all takes time.
Linux will maintain a linked list of abandoned data structures called obj. If a process is released, the data structure of the corresponding process will be maintained in this linked list. This linked list does not release space, it is just set to invalid. You can use it when needed to save development. Time (such a linked list is also called the kernel's data structure buffer pool, and the operating system is called: slab dispatcher)

3. Process waiting

1. Why do we need to wait for the process?

①In order to solve the problem of memory leakage of zombie process

If the child process exits and the parent process is ignored, it may cause a 'zombie process' problem, which in turn may cause a memory leak. Once the process becomes a zombie, it will be invulnerable, and the "kill without blinking" kill -9 will be unable to do anything.

②In order to obtain the exit status of the child process

We need to know how well the tasks assigned by the parent process to the child process are completed. For example, if the subprocess is completed, the result is correct or incorrect,
Or whether to exit normally.

2.wait与waitpid

The wait() solution can solve the problem of recycling the z state of the child process and letting the child process enter X ten. wait is waiting for any child process to exit.

  wait demo:

waitpid:

The return value of pid_t (int):
>0: Waiting for the child process to succeed, the return value is the pid of the child process
<0: Waiting failed  
=0: Waiting to succeed, but the child process did not exit     

The first parameter pid:
>0: It means which child process to wait for to exit, such as pid=1234, specify wait
-1: wait for any process to exit (usually the last process to exit)

The third parameter option:

0: Indicates blocking waiting (that is, the parent process waits for the child process to die, and then recycles the child process after it dies)

When options is set to WNOHANG, the function is non-blocking (Wait No Hang is blocked), and when no child process exits, waitpid returns 0

The second parameter status:

int* status: is an integer, an output parameter : the parent process calls waitpid, and you can get the exit code of the child process through status (specific process: the child process changes to Z state after exiting - the child process code is released, but the child process is maintained. The process control block task_struct of the process; at this time, the two int exit codes int exit_ code and exit signal exit_ signal in the process control block task_struct of the child process will be filled, and then the parent process will get these two values ​​​​and put them into status . Therefore, the output parameters must be defined first and then the address waitpid(id, &status, 0) is taken when passing the parameters, or if nullptr is passed, no exit code is required)

You only need to worry about changing the lower 16 bits of the integer!

(1) The next lowest 8 bits of the lower 16 bits (the second lowest 8 bits) are the exit code

Proof: Let the child process sleep for 5 seconds, then exit, and set the exit code to 0. While the child process sleeps for 5 seconds, the parent process uses wait/waitpid to set it to the blocking state. The return value is known:

The return value of pid_t (int):
>0: Waiting for the child process to succeed, the return value is the pid of the child process
<0: Waiting failed        
=0: Waiting to succeed, but the child process did not exit 

Use ret to receive the return value. When the reception is successful, print ret (ret here is the pid of the child process),

And print (status>>8) &0xFF, status>>8 starts with the next lowest 8 bits, and the upper 0xFF (8 1s) is the exit code. We will find that the exit code in status does record the exit code of the child process.

(2) The lower 8 bits are the termination signal

(Kill -l can check the exit signal, the exception is because the signal is received, status&0x7F can print the termination signal)

 status&0x7F: The lower 8 bits of status are combined with the upper 111 1111 to get the termination signal

No signal 0:

another receives

WIFEXITED(status): True if it is the status returned by the normal termination of the child process. (Check whether the process exited normally)
WEXITSTATUS(status): If WIFEXITED is non-zero, extract the child process exit code. (View the exit code of the process)

 

 waitpid():
blocking wait and non-blocking wait

When we call certain functions, because the condition is not ready, we need to block and wait. The essence is: the current
process becomes blocked by itself, and will be awakened when the condition is ready! (The condition here may be any software and hardware conditions!)

3. Parent process non-blocking wait (WNOHANG)

waitpid(-1,&status, WNOHANG);
WNOHANG The parent process is waiting for non-blocking (Wait No Hang)

Return value: =0, waiting is successful, but the child process does not exit; waiting for success returns the child process pid, waiting for failure returns -1.

(1) Example of parent process based on non-blocking polling waiting:

waitpid(-1,&status, WNOHANG); Each time waitpid is executed in the while loop, the child process is detected once. If the child process exits, the child process pid is returned successfully; if the child process does not exit, it is a non-blocking wait. Wait for success and return 0;

 

(2) The parent process is based on non-blocking polling waiting. The parent process also has examples of tasks.

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

typedef void (*handler_t)();

//方法集
std::vector<handler_t> handlers;

void fun1()
{
    printf("hello, 我是方法1\n");
}
void fun2()
{
    printf("hello, 我是方法2\n");
}

void Load()
{
    //加载方法
    handlers.push_back(fun1);
    handlers.push_back(fun2);
}

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //子进程
        while(1)
        {
            printf("我是子进程, 我的PID: %d, 我的PPID:%d\n", getpid(), getppid());
            sleep(3);
        }

        exit(104);
    }
    else if(id >0)
    {
        //父进程
        // 基于非阻塞的轮询等待方案
        int status = 0;
        while(1)
        {
            pid_t ret = waitpid(-1, &status, WNOHANG);
            if(ret > 0)
            {
                printf("等待成功, %d, exit sig: %d, exit code: %d\n", ret, status&0x7F, (status>>8)&0xFF);
                break;
            }
            else if(ret == 0)
            {
                //等待成功了,但是子进程没有退出
                printf("子进程好了没,奥, 还没,那么我父进程就做其他事情啦...\n");
                if(handlers.empty())    
                    Load();                添加任务
                for(auto f : handlers)
                {
                    f(); //回调处理对应的任务,即执行任务
                }
                sleep(1);
            }
            else{
                //出错了,暂时不处理
            }
        }
}

 

Guess you like

Origin blog.csdn.net/zhang_si_hang/article/details/126589679