Cross-platform (Linux) development based on VS2019 C++ (1.3) - process management

I. Introduction

  • Processes, Subprocesses, and Zombies
  • The creation of child processes and the relationship between parent and child processes
  • fork,exec,system,wait,waitpid system call
  • How to avoid "zombie" processes?

The concept of process

1. Processes and procedures

Before we learned about files, files are stored on disk, and the programs inside will take up memory space when running (for example, if our computer is stuck and replaced with a larger memory stick), of course, it will also take up resources such as CPU, network, power supply, etc.

A program is a simple entity. To put it bluntly, a program is an executable file stored in a disk file. Some operating systems use tasks to represent the program being executed.

A process is the basic unit for allocating resources by a computer operating system (a process is one execution of a program), but a program is not only one process, but can have multiple (for example, open the task manager to see how much a tinder application is opened on windows). process)

2. Process and process ID

The operating system will assign a unique integer ID to each process as the process ID (pid) (must be a non-negative integer). The computer system uses this process ID to manage the process, which is equivalent to the task on windows in the following figure pid in manager

The process pid is unique every time the computer is turned on. The pid of each program will change after the computer is shut down and restarted. The process pid is randomly assigned. The maximum number of pids of a computer is 65535 (the process start pids are not necessarily in order)

In addition to its own ID, the process also has a parent process ID. The ancestor process of all processes is the same process, which is called the init process (like the new crown, one person must first serve to infect others), the ID is 1, and no Terminated (unkillable), similar to the systemd process No. 0 in the ubuntu system (as shown below)

View the process tree: pstree

View process command: ps -aux

Kill process: kill -9 pid

3. Get the process ID

#include <sys/types.h>
#include <unistd.h>

uid_t geteuid(void); Returns: the effective user ID of the calling process
gid_t getgid(void); Returns: the actual group ID of the calling process
gid_t getegid(void); Returns: the effective group ID of the calling process

pid_t getpid(void); return: the process ID of the calling process
pid_t getppid(void); return: the parent process ID of the calling process
uid_t getuid(void); return: the actual user ID of the calling process

4. Process structure under linux

The Linux system is a multi-process system, and the processes have the characteristics of parallelism and non-interference with each other.

A process in linux includes a PCB (process control block), a program, and a set of data structures operated by the program: it can be divided into "code segments",

"Data segment" and "Stack segment".

5. Three basic states of the process

Processes constantly change their running states during running. Usually, a running process must have the following three basic states

Ready state (ready) : When the process has been allocated all the necessary resources except the CPU, as long as the processor can be executed immediately, the process state at this time is called the ready state.

Execution state (running) : When the process has obtained a processor, its program is executing on the processor

Blocked state (blocked) : When the executing process cannot execute due to waiting for an event to occur, it will give up the processor and be in a blocked state. For example, the application buffer cannot be satisfied, waiting for signals, waiting for IO completion, etc. may cause blocking

1) Ready -> Execute

A process in the ready state, when the process scheduler assigns a processor to it

2) Execute --> Ready

The process in the execution state has to give up the processor because the time slice allocated to it has been used up

3) Execute --> Block

The executing process cannot continue because it is waiting for some event to occur

4) blocking --> ready

Process in blocking state, if other waiting events have occurred

6. Several process states in linux
 

  • Running state R (TASK_RUNNING)
  • Interruptible sleep state S (TASK_INTERRUPTIBLE)
  • Uninterruptible sleep state D (TASK_UNINTERRUPTIBLE)
  • Pause state T (TASK_STOPPED or TASK_TRACED)
  • Zombie state Z (TASK_ZOMBIE)
  • exit status X (TASK_DEAD)

The basic flow of a process is: New -> Ready -> Running -> Death

These states may appear again in the running state: zombie-pause-sleep-uninterruptible sleep

  • Sleep state: The process sleeps for a while and wakes up and returns to the ready state
  • Paused state: The process will return to the ready state if it is started manually
  • Zombie state: The process has completed all logic, but the memory is not emptied

2. Process creation

Introduced: Two ways to run projects in linux

  1. Execute the .out file in Debug in X64 in the bin file (./*.out——* is the file name)
  2. Use the g++ command: g++ main.cpp -o main, will generate main (executable file), execute main: ./main 

Note: If the code in VS is modified, if you want ubuntu to respond, do not click to run directly, but click to regenerate the solution, the entire project will be recompiled and generated to replace the old version. and then execute the command

1. Fork system call

The fork function is used to create a new process from an existing process, the new process is called a child process, and the original process is called a parent process

Header file: #include <unistd.h>

pid_t fork(void);
Return value: 0 in the child process, child process ID in the parent process, error -1

Notice:

1. The fork call returns twice at a time. The difference between the two returns is that the return value of the child process is 0, and the parent process returns the process ID of the child process.

Because each process will have corresponding PCBs, code segments, data segments and stack segments, etc., the child process created by fork () naturally also has code segments, by copying a copy of the parent process code segment (that is, the content of the entire cpp file, but The child process will not fork() once), so there will be two pids, output (call fork once, return twice)

The result is as follows: 

From the above figure, in general, it is uncertain whether the parent process executes first or the child process executes first after fork, which depends on the scheduling algorithm used by the kernel

2. The child process obtained by using the fork function inherits the address space of the entire process from the parent process. Including process context, process stack, memory information, open file descriptors, process priority, etc.

4. The addresses of variables (global variables) used in the code segment of the parent and child processes are the same, but they cannot be shared. Because multiple processes do not interfere with each other, data sharing cannot be performed in the process (that is, if a variable is defined, the parent process and the child process. are used, although the data is different, but the address is the same)

5. After the fork system call, the parent and child processes are executed alternately. If the parent process exits first and the child process has not exited, then the parent process of the child process will become the init process, because any process must have a parent process. If the child process exits first and the parent process does not exit, then the child process must wait until the parent process captures the exit status of the next child process before it will actually end, otherwise the child process will be called a zombie process at this time.

  • Process dragging orphaning : The parent process dies before the child process, the child process becomes an orphan process, and is managed by the system. It will find a new parent and the child process is still going. Even if the parent process dies, the process has not ended.
  • Zombie process : The child process completes the logic before the parent process, enters a dead state, does not do any business, resources will not be recovered immediately, and the child process becomes a zombie process (occupying memory)
  • The zombie state occupies memory. We need to avoid the appearance of the zombie state. You can add the statement exit(0) after the child process ends to release the memory.
     

The meaning of the complete end of the process: all processes, including all sub-processes, have ended and the memory can be released before the end can be counted.

6. Classic process creation error : It is taken for granted that multiple processes can be directly fork multiple times in the main process (parent process), or fork multiple times with a loop


The resulting problem: the child process will also take the fork function (the last three forks in the above figure), which will cause the child process to reopen the child process (matryoshka)
Solution: avoid it through if...else.

If you want the child process to start the child process again, put the fork in the if, if you want the parent process to start the child process again, put the fork in the else, that is, start the child process again within the execution scope of the father

Difference between child process and parent process:

  • The lock set by the child process cannot be inherited by the child process
  • The respective process ID is different from the ID of the parent process
  • The pending alarm of the child process is cleared;
  • The set of pending signals for the child process is set to the empty set

sample code

#include <iostream>
#include <sys/types.h>
#include <unistd.h>

using namespace std;

int number = 0;
int main()
{
	int pid = 0;
	int ppid = 0;
	pid = getpid();//获取当前main函数的启动进程ID
	//cout << "ppid   " << ppid << endl;
    
    /*调用fork函数,其返回值为pid*/
	pid = fork();//一次调用,两次返回

	//cout << "pid   " << pid << endl;
    /*通过ret的值来判断fork函数的返回情况,首先进行出错处理*/
	if(pid == -1)
	{
		perror("fork error");
		exit;
	}
	
    /*返回值=0代表子进程*/
	else if (pid == 0)//子进程运行范围
	{
		while (1)
		{
			number++;
			cout << number << "地址" << &number << endl;
			/*cout << "子进程运行范围 pid =" << pid << "当前进程id = " << getpid()
				<< "当前进程的父进程 id =" << getppid()<< endl;*/
			sleep(3);
		}
	}
	else {//父进程运行范围
		while (1)
		{
			number++;
			cout << number << "地址"<< & number << endl;
			/*cout << "父进程运行范围 pid =" << pid << "当前进程id = " << getpid()
				<< "当前进程的父进程 id =" << getppid() << endl;*/
			sleep(3);
		}
	}

	
	return 0;
}

2. exec family 

A child process created with fork executes the same program as the parent process (but may execute a different code branch), and the child process usually calls the exec function to execute another program . After the call, the user space code and data of this process are completely replaced by the new process, and the execution starts from the startup program of the new process. Calling the exec function does not create a new process, so the process ID does not change after the call

Under the name exec is a complete series of multiple associated functions:

Function prototype: (header file <unistd.h>)

  • int execl(const char *path, const char *arg, ...);// need some paths    
  • int execlp(const char *file, const char *arg, ...);    
  • int execle(const char *path, const char *arg , ..., char * const envp[]);//char * const envp[] environment variable    
  • int execv(const char *path, char *const argv[]);    
  • int execvp(const char *file, char *const argv[]);
  • int execve(const char *path, char *const argv[], char *const envp[]);

parameter    

  • The path parameter indicates the name of the program you want to start including the path name    
  • The arg parameter represents the parameters that the startup program takes

Return value: 0 for success, -1 for failure

Note : l and v are mutually exclusive; p and e are mutually exclusive

Notice:

  • The number of parameters for execl, execlp, and execle is variable, and the parameters end with a null pointer
  • The second parameter of execv and execvp is a string array. When the new program starts, it will pass the parameters given in the argv array to main.
  • Almost all of the above functions are implemented with execve, which is customary and understandable.
  • The last letter of the function name in the exec family is "p", this function will look up the path environment variable, and then search for the executable file of the new program. If the executable is on the path defined by the step path, the absolute filename including subdirectories must be passed as an argument to these functions
  • A new process started by exec inherits many things from the original process, and file descriptors already open will remain open in the new process unless their "close this file on exec call" flag is modified

sample code

//使用文件名的方式来查找可执行文件,同时使用参数列表的方式
if(fork()==0){
/*调用execlp 函数,这里相当于调用了“ps-f”命令*/
	if (execlp("ps","ps","-ef",NULL)<0)
	{
		perror("execlp 错误!");
		exit(1);
	}
}


//使用完整的文件目录来查找对应的可执行文件
if(fork()==0){
	/*注意这里给出ps程序的完整路径*/
	if (execl("/etc/ps","ps","-ef",NULL)<0)
	{
		perror("execl 错误!");
		exit(1);
	}
}


//将环境变量添加到新建的子进程中去   
/*命令参数列表,必须以NULL结尾*/
char *envp[]={"PATH=/tmp","USER=zqw",NULL};
if(fork()==0){
	/*注意这里也要指出env的完整路径  env:查看当前进程环境变量*/
	if (execle("/etc/env","env",NULL,envp)<0)
	{
		perror("execle 错误!");
		exit(1);
	}
}



//通过构造指针数组的方式来传递参数,注意参数列表一定要以NULL作为结尾标识符
char*arg[]={"ls", "-a", NULL};
if(fork()==0){
	if (execve("/etc/ls",arg,NULL)<0)
	{
		perror("execve 错误!");
		exit(1);
	}
}

Three, exit and _exit

exitAnd _exitused to abort the process , directly stop the process to clear the used memory space, including various data structures including PCB. However, there are still differences between these two functions. The exit function checks the open status of the file before calling the exit system, and writes the contents of the file buffer back to the file. Such as calling the printf function. The calling process of this function is shown in the following figure:

exit and _exit function syntax

#include <unistd.h>   //  _exit
#include <stdlib.h>   //    exit

Function prototype:

void _exit(int status)
void exit(int status)

Parameters:
status: 0 means normal end; other values ​​indicate that an error occurred and the process ended abnormally
 

 Small scale chopper

The assignment will be reviewed in the next article

Originality is not easy, please indicate the source when reprinting:

Cross-platform (Linux) development based on VS2019 C++ (1.3) - process management

Let's enter the second part of the study:
Cross-platform (Linux) development based on VS2019 C++ (1.3.2) - process management

Guess you like

Origin blog.csdn.net/hml111666/article/details/123368543