Process control: create termination wait
1. Process creation
Talk about the fork() function again
fork
A very important system call function in Linux , which creates a new process from an existing process. The new process is the child process, and the original process is the parent process.
Function prototype:
#include <unistd.h>
pid_t fork(void);
Return value: return 0 in the child process, return the child process id in the parent process, and return -1 in error
A process calls fork, and when 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 part of the data structure content of the parent process to the child process
- Add child process to system process list
- fork returns and starts scheduler scheduling
When a process calls fork, there are two processes with the same binary code, and they both run to the same place, and each process will be able to start its own operation.
Therefore, before the fork, the parent process executes independently, and after the fork, the two execution flows of the parent and the child are executed separately.
Note that after fork, who executes first is completely determined by the scheduler.
common usage of fork
- A parent process wishes to duplicate itself so that the parent and child processes execute different sections of code concurrently. For example, a parent process waits for a client request and spawns a child process to handle the request.
- A process executes a different program. For example, after the child process returns from fork, call
exec
the function.
The reason for the failure of the fork call
- Too many processes in the system
- The number of real user processes exceeds the limit
realistic copy
Usually, father and son share code, and when father and son no longer write, the data is also shared. When either party tries to write, they will each have a copy in the form of copy-on-write, as shown in the figure below:
Second, the termination of the process
Process exit scenario :
- The code runs and the result is correct
- The code runs to completion with incorrect results
- The code terminated abnormally and did not finish running, that is, the process crashed ( the essence of the crash: the process received a signal from the operating system (kill -9) for some reason )
1. The exit code of the process
C/C++ provides us with many exit codes. Through these exit codes, we can know whether the result of the code is correct. If it is not correct, we can use some conversion functions strerror()
to convert the exit code into exit information. Through these information we can You can know why the result is incorrect after running.
For example: when we write some C language code, we often write it at the end return 0
. This 0 is main()
the exit code of the function. The reason why we set it 0
is because it 0
usually means normal exit, and other numbers indicate abnormal exit.
Let's take a look at the exit codes that the C language provides us with!
#include<stdio.h>
#include<string.h>
int main()
{
for(int i = 0; i < 150; ++i)
{
printf("%d: %s\n", i, strerror(i));
}
return 0;
}
The C language probably provides us with more than 130 error codes, which 0
indicate success,
- 1 means the operation is not allowed.
- 2 means no file or directory
- 3 means there is no such process
- 4 indicates a system call conflict
- 5 means I/O error
- …
In Linux
, we can use the following command to view:The exit code of the last executed process!
echo $?
Our last process is test1c
, and the process exit code we set is 0. Let's check it out.
2. Common process exit methods
The exit of a process is actually reducing a process in the OS, and the OS releases the kernel data structure + code and data corresponding to the process
Normal termination : (you can view the process exit code through echo $?)
- return from main
- Call exit (exit function provided by C language)
- Call _exit (system call provided by Linux)
Abnormal exit :
ctrl + c, signal termination
kill -9 signal termination
-
The main function return
The main function returns, the process ends normally, but whether the result is correct or not is unknown. What about the return of other functions? The return of other functions only means that the function returns,The essence of process execution is main execution flow execution! -
exit
Function
Prototype:
Parameter : status defines the termination status of the process, and the parent process can obtain the value through wait
Note : Although status is int,But only the lower 8 bits can be used by the parent process, so when exit(-1), execute $? on the terminal and find that the return value is 255.
The runtime function that calls main will use the return value of main as the parameter of exit, so in the main function isreturn status;
also equivalent toexit (status);
Note: calling the exit function anywhere in the code means that the process exits!
Let's look at the following piece of code to understand the Function usage:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void PrintError()
{
for(int i = 0; i < 150; ++i)
{
printf("%d: %s\n", i, strerror(i));
//打印一次就退出整个进程
exit(123);
}
}
int main()
{
PrintError();
return 0;
}
You can see that the process exited according to the exit code we gave, and exit
the function directly lets the process exit instead of letting the function that called it exit.
_exit
Function
_exit
A function is a system call, its use is similar to the exit function, and the parameters are the same.
But it is different, the underlying package of exit is_exit
a function, the buffer will be refreshed when exit exits the process, and_exit
the buffer will not be refreshed when the process exits!
Let's look at the following piece of code to verify the above conclusion
//exit函数退出
#include<stdio.h>
#include<stdlib.h>
int main()
{
//停顿一秒后打印出"you can see me ?"
printf("you can see me ?");
sleep(1);
exit(0);
printf("you can see also me ?");
return 0;
}
operation result:
//_exit退出
#include <stdio.h>
#include <unistd.h>
int main()
{
//停顿一秒后直接退出不打印出"you can see me ?"
printf("you can see me ?");
sleep(1);
_exit(0);
printf("you can see also me ?");
return 0;
}
exit
Contrast with _exit
functions
3. Process waiting
1. Necessity of process waiting
Before learning about process waiting, let's first understand why the process waits?
- We said before that 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 the "kill without blinking" kill -9 can do nothing, because no one can kill a dead process.
- Finally, the parent process needs to know how well the tasks assigned to the child process are going. For example, whether the child process finishes running, whether the result is correct or not, or whether it exits normally.
- The parent process reclaims the resources of the child process by means of process waiting.
2. What is waiting
Waiting : It is to obtain the exit code or exit signal of the child process through the system call, and release the memory problem by the way.
3. The method of process waiting
There are two ways for a process to wait: wait
and waitpid
These two functions are system calls provided by Linux. Which is waitpid
even more powerful! For specific details, you can use the man command to view related documents.
①wait function
function prototype:
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
Function: Wait for the state change of any child process.
Return value: successfully returns the pid of the waiting process, and returns -1 on failure.
Parameters: output parameters, get the exit status of the child process, if you don’t care, you can set it to NULL
, if NULL
it is, wait
the function only plays the role of recycling the child process
Parameters: detailed explanation of status
- Both wait and waitpid have a status parameter, which is an output parameter filled by the operating system.
- If NULL is passed, it means that the exit status information of the child process is not concerned.
- The operating system will feed back the exit information of the child process to the parent process according to this parameter.
We know that the exit information of the process includes two types, one is the exit code, and the other is the exit signal. If these two values are each stored in a variable, the code will become redundant, and because each of these two data The size of the data will not exceed 255, and can be represented by one byte, so we useint
different sections of a plastic to represent the exit code and exit signal, so although the status isint
a type, it cannot be simply regarded as a plastic. It can be treated as a bitmap, and the details are as follows (only the lower 16 bits of status are studied, and the upper 16 bits are discarded):
status
The second lowest eight bits of data indicate the exit status of the process, that is, the exit code. The termination signal is represented by the lowest 7 bits, and the previous bit of the termination signal represents the core dump signal (not explained here).
For example, the following piece of data is indicated in status, the exit code is 6, and the exit signal is 0.
So when we status
pass it to wait
the function, how do we get the data out? The answer is: bitwise AND &
if we want to get status
the exit code in, we can (status >> 8) &0xFF
, and if we want to get the exit signal, we canstatus &0x7F
However, in the process of actual use, we don't need to adopt the original method above. Linux provides us with two macro functions, through which we can also achieve the effect we want:
WIFEXITED(status): True if this is the status returned by a normally terminated child process. (Check whether 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)
So let's take a look at wait
the use of functions!
#include < stdio.h >
#include < string.h >
#include < stdlib.h >
#include < unistd.h >
#include < sys / types.h >
#include < sys / wait.h >
int main()
{
pid_t id = fork();
if (id == 0)
{
//child process
int second = 3;
while (second)
{
printf("我是子进程,我还活着呢,我还有%dS\n", second--);
sleep(1);
}
exit(0);
}
else if (id < 0)
{
perror("fork() fail:");
return 0;
}
//parent process
//此时子进程已经死亡,我们可以看到子进程处于僵尸状态Z
sleep(5);
//定义一个整形变量,方便从wait中获取子进程的状态信息
int status = 0;
int Pid = wait( & status);
printf("我是父进程,等待子进程成功!子进程的pid是: %d, ", Pid);
//判断子进程是正常退出还是异常退出
if (WIFEXITED(status))
{
//如果是正常退出,就打印子进程退出码。
printf("子进程的退出码是: %d\n", WEXITSTATUS(status));
}
else
{
printf("子进程退出异常\n");
}
//等待两秒,方便观察到子进程僵尸状态消失。
sleep(2);
return 0;
}
The result of running the code:
While the code is running, open another window and enter the following command to check the status of the process every second
while :; do ps -axj | head -1 && ps -axj |grep test1c | grep -v grep ; sleep 1; echo "--------------"; done
We can see the following process status changes:
This is wait
the function, and the use of the exit code of the process exit signal, but we still have to discuss a question, that is, what wait
is the parent process doing when it is there? That's right, the parent process does nothing, just waits, but sometimes, we don't want the parent process to wait
just wait after calling the function, we also want the parent process to do some other things, at this time we will use more powerful functions waitpid
.
② waitpid function
pid_ t waitpid(pid_t pid, int *status, int options);
return value:
- When returning normally, waitpid returns the process ID of the collected child process;
- If the option WNOHANG is set, and waitpid finds that there are no exited child processes to collect during the call, it returns 0;
- If there is an error in the call, -1 is returned, and errno will be set to the corresponding value to indicate the error;
Parameters:
pid :
Pid = -1, waiting for any child process. Equivalent to wait.
Pid > 0, waiting for a child process whose process ID is equal to pid.
status :
output parameter, indicating the status of the child process.
options :
0, if it is set to 0, it means that the parent process will be in a blocked waiting state waitpid
when , wait
similar to a function.
WNOHANG: If the child process specified by pid has not ended, the waitpid() function returns 0, does not wait, and continues to run the parent process. If the child process ends normally, return the ID of the child process.
Let's take a look at the use of non-blocking waiting:
#include < stdio.h >
#include < string.h >
#include < stdlib.h >
#include < unistd.h >
#include < sys / types.h >
#include < sys / wait.h >
int main()
{
pid_t id = fork();
if (id == 0)
{
//child process
int second = 3;
while (second)
{
printf("我是子进程,我还活着呢,我还有%dS\n", second--);
sleep(1);
}
exit(0);
}
else if (id < 0)
{
perror("fork() fail:");
return 0;
}
//parent process
int status = 0;
while (1)
{
//读取子进程的状态,进行非阻塞等待
int Pid = waitpid(id, &status, WNOHANG);
if (Pid > 0)
{
printf("我是父进程,等待子进程成功!子进程的pid是: %d, ", Pid);
if (WIFEXITED(status))
{
printf("子进程的退出码是: %d\n", WEXITSTATUS(status));
}
else
{
printf("子进程退出异常\n");
}
exit(-1);
}
//子进程状态若不改变,将会执行下面的代码
else if (Pid == 0)
{
printf("我是父进程,子进程还没有退出呢!我在做一做其他事情\n");
sleep(1);
}
//waitpid错误
else
{
perror("waitpid() fail:");
exit(-1);
}
}
return 0;
}
operation result: