Linux process control (2) --- process waiting

Table of contents

what is process waiting

Why do process wait?

wait()

waitpid()

Use of status★

options★

Question: Since the process is independent, isn't the exit code of the process also the data of the child process, why should the parent process get it? What exactly does wait/waitpid do?

what is process waiting

Process waiting is a mechanism by which a parent process waits for its child process to terminate . In a multi-process system, a parent process creates a child process and may need to wait for the child process to finish executing in order to get the exit status of the child process or perform other operations.

It should be noted that: the parent process waiting for the child process to terminate is a blocking operation, that is, the parent process will suspend its own execution until the child process terminates.

Why do process wait?

1. Recycle zombie process

2. Get the exit status of the child process.

Here is a detailed explanation of the above two sentences:

1. As mentioned before, if the child process exits, if the parent process is ignored, it may cause the problem of a 'zombie process', which in turn will cause a memory leak.
In addition, once a process becomes a zombie, it is invulnerable, and kill -9 is powerless, because no one can kill a dead process.
2. Finally, we need to know how the tasks assigned by the parent process to the child process are completed. For example, whether the child process finishes running, whether the result is correct or not, or whether it exits normally.
Conclusion: The parent process reclaims the resources of the child process and obtains the exit information of the child process by means of process waiting

Next we're going to create a zombie state.

Enter the following code:

1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<sys/types.h>
  5 int main()
  6 {
  7   pid_t id = fork();
  8   if(id < 0)
  9   {
 10     perror("fork");
 11     exit(1);//标识进程运行完毕,结果不正确
 12   }
 13   else if(id == 0)
 14   {
 15     //子进程
 16     int cnt = 5;
 17     while(cnt)
 18     {
 19       printf("cnt: %d, 我是子进程,pid: %d,ppid: %d\n",cnt,getpid(),getppid());
 20       sleep(1);
 21       cnt--;
 22     }
 23     exit(0);
 24 
 25   }
 26   else
 27   {
 28     while(1)
 29     {
 30       printf("我是父进程,pid: %d,ppid: %d\n",getpid(),getppid());
 31       sleep(1);
 32     }
 33   }
 34 
 35   return 0;
 36 }     

In this way, the child process loops 5 times and exits, while the parent process keeps looping and cannot exit, resulting in the situation that the child process cannot be recycled.

Then exit vim, make compiles and executes, and creates a window at the same time, detects the status of the process, and enters in the new window:

while :; do ps axj | head -1 && ps ajx | grep myproc | grep -v grep;sleep 1; echo "-------------------------------"; done

 

After 5 seconds, the child process exits, and only the parent process is running. 

 Observe the status of the process at this time and know that after 5 seconds, the status of the child process becomes Z+, zombie status.

So how do we recycle the zombie process at this time , we use the wait interface.

wait()

Let's check its usage in man 2 wait .

What it does is wait for a process until its state changes. 

This parameter status is used to obtain the result of the subprocess. This is what waitpid will talk about later, and it is the same. For now, just write NULL.

Then look at the return value:

 Chen Gong, return the pid of the child process, otherwise return -1.

At this point we use it and make the following changes to the code in the parent process module:

First output once, and then wait for the child process. If the wait is successful, it will output "waiting for the child process to succeed".

Exit vim, make to compile. At the same time, the right window is still monitored.

In this way, the parent process will wait for the child process from running until it dies, and when the state changes, it will be recycled.

 We found that at this time, the child process did not generate a zombie state after 5 seconds, but disappeared directly, leaving only the parent process to continue running.

At this time, the child process is recycled by the parent process, and wait until the state changes.

So what is the difference between waitpid and this wait?

waitpid()

Similarly, we man 2 waitpid to view usage.

The first parameter, pid, is used to wait for the child process of a specific pid.

There are two options:

        1.Pid=-1, wait for any child process. Equivalent to wait.
        2.Pid>0. Waiting for a child process whose process ID is equal to pid.

The third parameter options defaults to 0 , which means blocking and waiting.

The second parameter status is a pointer type, which is an output parameter.

For example, we define an int status = 0 outside the function,

Then pass this status into the second parameter in waitpid.

Then when the function ends, the exit result of the child process will be automatically filled into the status, which is the output parameter.

So, waitpid(-1,NULL,0) is equivalent to wait(NULL).

We change the wait in the parent process to waitpid:

 make compiles and runs

 The result is the same.

Use of status★

Then let's use status.

We manually add an exit code 69 in the child process module, and then define the status variable in the parent process module first, then pass it into waitpid, and finally output it.

Exit, make compiles and runs.

 We found that the exit code is not 69, but such a large number, what is the situation?

In fact, status is not used as an integer, but is divided into 32 bits according to the way of bits.

We only learn the lower 16 bits. The lower 16 bits basically meet our needs.

Among them, this time the lower 8 bits identify the exit code of the subprocess. It is equivalent to the 8th-15th bits. If we want to get it, we must first shift it to the right by 8 bits, and then & 0xFF, 0xFF means that only the last 8 bits are 1, The remaining bits are all 0, and any &1 is itself, and &0 is 0, so you get the 8 bits.

 

 At this point we get the result we want 69.

It feels very troublesome to write this every time, so is there a faster way?

The C standard library provides some macros:

WIFEXITED(status) : True if this is the status returned by a normally terminated child process . (Check to see if the process exited normally)

WEXITSTATUS(status) : If WIFEXITED is non-zero, extract the exit code of the child process. (Check the exit code of the process)

Let's take a look at how to use it:

 run:

 It can be seen that the child process ends normally at this time, and we get the exit code we returned

If it is still adding a division by 0 error in the child process at this time, then output again.

You can see that the child process exited abnormally, and  WIFEXITED returned a value of 0.

It is also relatively simple.

stop signal

Let’s talk about the abnormal exit or crash of the process. The essence is that the operating system kills the process.

How did the OS kill it? The essence is to kill the process by sending a signal .

Here are all the signals under Linux.

 So the lowest 7 bits, that is, the termination signal, indicate the signal received by the process.

Among them, the code dump will be explained later when explaining the signal.

We also output the value of the last 7 bits.

At this time, you need to get the last 7 bits, then you need to & add 0111 1111, which means 0x7 F in hexadecimal.

 run:

 The last signal received by the child process is 0, indicating that it has finished running normally.

The exit code is used to judge whether the result is correct or incorrect on the basis of normal running .

Then we write a division by 0 error at this time, so that the child process ends abnormally.

run:

 

 We found that the signal received by the child process is 8, and the signal 8 is SIGFPE, which means that the floating point calculation error.

At this time, since the program has not finished running normally, the exit code is meaningless. Because the program has not finished running, it is meaningless to judge that the result is correct at this time.

Moreover , the abnormality of the program is not only a problem with the internal code, but also an external cause (such as a signal, etc.).

options★

In the waitpid we wrote before, the parent process has been blocking and waiting until the end of the child process, that is to say, the parent process has been waiting until the end of the child process, and can't do anything. This seems a bit strange, so can we make the parent process very What about blocking waits ?

The third parameter of waitpid is used here.

WNOHANG : If the child process specified by pid has not ended, the waitpid() function returns 0, does not wait, and returns immediately. If it ends normally, the ID of the child process is returned.

This is actually a macro, and the inside is actually #define WNOHANG 1, that is to say, you can fill in WNOHANG for the third parameter, and you can also fill in 1.

The following is a pseudocode of waitpid. When it is detected that the child process has not exited, if the detected options is 0, the parent process will be blocked directly. The essence is to block inside the system function and wait for the child process to be awakened .

And if it is 1, that is, WNOHANG, it will return directly without blocking and waiting.

Then the question is, when options==0, when the child process is awakened, should it continue to execute from the back of if, or re-execute waitpid?

But continue to execute the statement after if, because the register EIP holds the address of the next line of code.

 Let's switch to code to understand it.

 If the wait is successful, and the child process exits or the wait fails, it exits. 

If the child process does not exit, then the detection will be done all the time, which is equivalent to polling detection. 

It can be seen that during the running of the child process, the parent process polls and detects it every second. When the child process ends, waitpid also detects that the child process exits. At this time, res>0, the exit information of the child process is output. Then The parent process loop also ends at the same time, ending the process.

Of course, you can let the parent process handle some tasks specifically, such as adding these codes at the beginning of the function:

 

This module while waiting:

 

 Then we execute again:

At this time, the parent process can perform corresponding tasks while waiting for the child process to launch. 

Question: Since the process is independent, isn't the exit code of the process also the data of the child process, why should the parent process get it? What exactly does wait/waitpid do?

We need to start with the zombie process: it is a dead process, but at least the PCB information of the process must be preserved, which contains the exit result information when any process exits!

The purpose of the zombie process to keep its own exit information is to let other processes read it!

wait and waitpid essentially read the task_struct structure of the child process.

Let's take a look at the kernel source code:

 The essence of wait/waitpid is to read these two fields, and then set the bit operation to the output variable of status, and we get the .

So wait and waitpid have permission to read the contents of this kernel data structure?

The answer is yes, because wait and waitpid are system calls! System calls are called by the operating system.

My parent process does not have permission to read, but I can call wait and waitpid to let the operating system get it for me.

In summary: the parent process does not have permission, but wait/waitpid has permission, and the parent process can call wait/waitpid to obtain the exit status of the child process.

wait/waitpid essentially sets the exit status of the read subprocess to the output variable status through bit operations.

So here, the waiting process is over.

Guess you like

Origin blog.csdn.net/weixin_47257473/article/details/131802018