[Linux] Process control: create, terminate, wait


1. Process creation

Talk about the fork() function again

forkA 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.

insert image description here

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 execthe 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:

insert image description here

Second, the termination of the process

Process exit scenario :

  1. The code runs and the result is correct
  2. The code runs to completion with incorrect results
  3. 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 0is because it 0usually 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;
}

insert image description here
The C language probably provides us with more than 130 error codes, which 0indicate 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.

insert image description here

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 $?)

  1. return from main
  2. Call exit (exit function provided by C language)
  3. Call _exit (system call provided by Linux)

Abnormal exit :
ctrl + c, signal termination
kill -9 signal termination


  1. 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!

  2. exitFunction
    Prototype:

    insert image description here
    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 is return status;also equivalent to exit (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;
}  

insert image description here
You can see that the process exited according to the exit code we gave, and exitthe function directly lets the process exit instead of letting the function that called it exit.

  1. _exitFunction
    _exitA function is a system call, its use is similar to the exit function, and the parameters are the same.
    insert image description here
    But it is different, the underlying package of exit is _exita function, the buffer will be refreshed when exit exits the process, and _exitthe 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:

insert image description here

//_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;
    }

insert image description here

exitContrast with _exitfunctions

insert image description here

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.
insert image description here

3. The method of process waiting

There are two ways for a process to wait: waitand waitpidThese two functions are system calls provided by Linux. Which is waitpideven more powerful! For specific details, you can use the man command to view related documents.

insert image description here

①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 NULLit is, waitthe 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 use intdifferent sections of a plastic to represent the exit code and exit signal, so although the status is inta 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):
    insert image description here

statusThe 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.
insert image description here
So when we statuspass it to waitthe function, how do we get the data out? The answer is: bitwise AND &
if we want to get statusthe exit code in, we can (status >> 8) &0xFF, and if we want to get the exit signal, we canstatus &0x7F
insert image description here

insert image description here

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 waitthe 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:

insert image description here

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:

insert image description here

This is waitthe function, and the use of the exit code of the process exit signal, but we still have to discuss a question, that is, what waitis 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 waitjust 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 waitpidwhen , waitsimilar 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:

insert image description here

Guess you like

Origin blog.csdn.net/qq_65207641/article/details/129776630