Detailed explanation of Linux process programming and fork function example (5) -【Linux Communication Architecture Series】

Series Article Directory

C++ skill series
Linux communication architecture series
C++ high-performance optimization programming series
Deep understanding of software architecture design series
Advanced C++ concurrent thread programming

Looking forward to your attention! ! !
insert image description here

现在的一切都是为将来的梦想编织翅膀,让梦想在现实中展翅高飞。
Now everything is for the future of dream weaving wings, let the dream fly in reality.

进程的概念

一个可执行程序执行一次就是一个进程,再执行一次就有是一个进程(多个进程共享同一个可执行文件),换句话说,进程一般定义为程序为程序执行的一个实例。

1. Understanding the fork function and simple examples

In a process, fork can be used to create a child process. When the child process is created, it will execute the same code as the parent process from the next statement of the fork function (or the return of fork). in other words,fork函数产生一个和当前进程完全一样的新进程,并和当前进程一样从fork函数调用中返回。

Just imagine, there was only one parent process running, which was one execution path. After calling fork, it became two execution paths (one for the parent process and one for the child process). As shown in Figure 1.1:
insert image description here

Figure 1.1 After calling fork, the program execution path changes from 1 to 2 (1 for the parent process and 1 for the child process)

See the following example:

#include <stdio.h>
#include <stdlib.h> //malloc,exit
#include <unistd.h> //fork
#include <signal.h>

//信号处理函数
void sig_usr(int signo)
{
    
    
	printf("收到了SIGUSR1信号,进程ID = %d!\n", getpid());
}

int main(int argc, char *const *argv)
{
    
    
	pid_t pid;
	printf("进程开始执行!\n");
	//先简单处理一个信号
	if(signal(SIGUSR1, sig_usr) == SIG_ERR)
	{
    
    
		printf("无法捕捉SIGUSR1信号!\n");
		exit(1);
	}
	//创建1个子线程
	pid = fork();
	//要判断子进程是否创建成功
	if(pid < 0)
	{
    
    
		printf("子进程创建失败,很遗憾!\n");
		exit(1);
	}
	//现在,父进程和子进程同时开始运行了
	for(;;)
	{
    
    
		sleep(1);
		printf("休息1s,进程ID = %d!\n", getpid());
	}
	printf("再见了!\n");
	return 0;
}

Compile, link and run, the result is as follows:

insert image description here

Figure 1.2 The parent process calls fork to create a child process, and after killing the child process, the parent process receives the SIGCHLD signal

insert image description here

Figure 1.3 After killing the child process, you can still see the child process (zombie process)

(1) It can be noticed that the parent process ID of the process ID 1183 is 1182, indicating that the process 1182 calls the fork function to create the 1183 process. Also note that the status of these two processes is S+. S is dormancy because the process executes sleep most of the time, so dormancy is normal; + means it is in the foreground process group.

(2) But here is one thing to pay attention to. After calling the fork function to create a child process, it is uncertain whether the subsequent code will be executed first by the parent process or by the child process. It does not mean that the parent process must be fast, because there is a process time slice scheduling problem. (This is related to the kernel scheduling algorithm).

(3) It can be seen from the printed results that both the parent process and the child process can receive this signal, indicating that the signal captures this code, which is the common code of the child process and the parent process (valid for both the parent process and the child process, or this The piece of code is both in the parent process and in the child process - although the child process is created by the fork function later, all the code executed by the parent process before the child process is created is equivalent to the child process has been executed).

(4) It can also be noticed that we kill the child process through the kill -9 command (-9 represents the SIGKILL signal, which cannot be intercepted or captured), and the parent process receives the SIGCHLD signal.

(5) From Figure 1.3, we can see that the 1183 subprocesses that were killed still exist in the list of the ps command, but the COMMAND column shows defunct (meaning failure), and the STAT column shows Z+ (Z state means zombie process ). In short, both the Z state and the word defunct are typical signs of a zombie process.

2. Generation and resolution of zombie processes, SIGCHLD

⚠️(1)僵尸进程是怎么产生的能呢?

In the Linux operating system, if a child process is terminated, but the parent process is still alive, when the parent process does not call the () wait / waitpidfunction to perform some additional processing (disposing of the termination of the child process), then the child process will become one 僵尸进程.

This zombie process has been terminated and does not work anymore, but it is still not discarded by the kernel, because the kernel believes that the parent process may still need some information about the child process.

Zombie processes are resource hogging, at least process ID (PID). The process number in the whole operating system is limited, so, 作为开发者不应该允许僵尸进程的存在.

⚠️(2)那么怎么能让僵尸进程消失呢?

Restart the computer? Manually kill the parent process of this zombie process? Neither of these is a good idea.
We should avoid the generation of zombie processes from the perspective of code .

When the child process is killed, the parent process receives a SIGCHLD signal. Therefore, for the process of fork behavior (which will create child processes) in the source code, we should intercept and process the SIGCHLD signal.

See the following example:

#include <stdio.h>
#include <stdlib.h> //malloc,exit
#include <unistd.h> //fork
#include <signal.h>
#include <sys/wait.h> //waitpid

//信号处理函数
void sig_usr(int signo)
{
    
    
	
	int status;
	switch(signo)
	{
    
    
		case SIGUSR1:
		{
    
    
			printf("收到了SIGUSR1信号,进程ID = %d!\n", getpid());
		}
		break;
		case SIGCHLD:
		{
    
    
			printf("收到了SIGCHLD信号,进程ID = %d!\n", getpid());
			//waitpid获取子进程的终止状态,子进程就不会成为僵尸进程了
			//第一个参数:-1,表示等待任何的子进程
			//第二个参数:保存子进程的状态信息
			//第三个参数:WNOHANG表示不要阻塞,让这个waitpid()立即返回
			pid_t pid = waitpid(-1, &status, WNOHANG);
			
			if(pid == 0)
				return;
			//子进程没结束,会立即返回该数字,但这里应该不是该数字,这里的情况是子进程结束才出发父进程的该信号
			if(pid == -1)
				return;
			//走到这里,表示成功,程序返回
			return;
		}
		break;
	}

}

int main(int argc, char *const *argv)
{
    
    
	pid_t pid;
	printf("进程开始执行!\n");
	//先简单处理一个信号
	if(signal(SIGUSR1, sig_usr) == SIG_ERR)
	{
    
    
		printf("无法捕捉SIGUSR1信号!\n");
		exit(1);
	}
	//增加SIGCHLD信号的捕捉
	if(signal(SIGCHLD, sig_usr) == SIG_ERR)
	{
    
    
		printf("无法捕捉SIGCHLD信号!\n");
		exit(1);
	}
	//创建1个子线程
	pid = fork();
	//要判断子进程是否创建成功
	if(pid < 0)
	{
    
    
		printf("子进程创建失败,很遗憾!\n");
		exit(1);
	}
	//现在,父进程和子进程同时开始运行了
	for(;;)
	{
    
    
		sleep(1);
		printf("休息1s,进程ID = %d!\n", getpid());
	}
	printf("再见了!\n");
	return 0;
}

Compile, link and run, the result is as follows:
insert image description here

Figure 2.1 After killing the child process, there is no zombie process

3. The memory space of the process and the generation of the process

forkThe speed of generating a new process is very fast, and the generated new process does not copy the memory space of the original process, but shares a memory space with the original process (parent process). The characteristic of this memory space is "copy-on-write", that is to say, the original process and the forked child process can freely read the memory at the same time, but if the child process (or parent process) modifies the memory, the memory will be destroyed. Make a copy for the process to use alone, so as not to affect the use of other processes in the memory space.

See the following example:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

int main(int argc, char * const * argv)
{
    
    
	fork(); //一般fork都会成功,所以不判断返回值了
	fork();
	
	for(;;)
	{
    
    
		sleep(1); //休息1s
		printf("休息1s, 进程ID = %d!\n", getpid());
	}
	printf("再见了!\n");
	return 0;
}

⚠️上面的代码执行后会产生几个进程?

The ability of fork is simply divided into two (one route is divided into two routes/one process becomes two processes).

In the code, the first fork is divided into two, and the two lines go down at the same time. Both lines have experienced the second fork, and each fork is divided into two, so it is divided into four (4 processes are finally generated)

Next, let's look at the execution results:
insert image description here

Figure 3.1 call fork 2 times, resulting in 4 nginx processes

Fourth, determine the execution branch of the parent process and the child process

Once the fork is executed, 1 route becomes 2 routes, so the fork function actually returns 2 times (the parent process returns 1 time; the child process also returns 1 time).

The value returned by the fork function in the parent process is different from the value returned in the child process. Based on this, code can be written to identify whether it is the parent process or the child process, so that the parent and child processes execute different code branches.

By observing the following code example, it can be found that the program is exactly通过判断fork的返回值来决定父进程执行哪些代码、子进程执行哪些代码。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

int g_mygbltest = 0;
int main(int argc, char * const * argv)
{
    
    
	pid_t pid;
	printf("进程开始执行!\n");
	//创建一个子进程
	pid = fork();
	//要判断子进程是否成功
	if(pid < 0)
	{
    
    
		printf("子进程创建失败,很遗憾!\n");
		exit(1);
	}
	//走到这里,fork()成功,执行后续代码的可能是父进程,也可能是子进程
	if(pid == 0)
	{
    
    
		//子进程,因为子进程的fork()返回值会是0
		//这是专门针对子进程的处理代码
		while(1)
		{
    
    
			g_mygbltest++;
			sleep(1); //休息1s
			printf("我是子进程,我的进程ID = %d, g_mygbltest = %d\n", getpid(), g_mygbltest);
		}
	}else{
    
    
		//这里就是父进程,因为父进程的fork()返回值会 >0
		//这是专门针对父进程的处理代码
		while(1)
		{
    
    
			g_mygbltest++;
			sleep(5);
			printf("我是父进程,我的进程ID = %d, g_mygbltest = %d\n", getpid(), g_mygbltest);
		}
	}
	return 0;
}

The result of the operation is as follows:
insert image description here

Figure 4.1 Execution branch execution results of parent process and child process

Observe the above results and focus on the value of the g_mygbltest global variable. It can be seen that the value of the global variable of the parent process and the child process is different, and each process is counted separately.

Through the above example, a conclusion can be drawn :

fork returns 0 for the child process; for the parent process, the return value is the ID of the newly created child process.

The value of the global variable g_mygbltest of the parent process and the child process is also different, and each process has a different value, because these two processes have writing actions (rewriting the value of the global variable g_mygbltest, that is, rewriting the memory), the kernel will give each A process allocates a piece of memory for its own use alone, so the g_mygbltest value of each process does not interfere with each other.

5. A logical judgment related to fork execution

#include<stdio.h>
#include<stdlib.h> //malloc,exit
#include<unistd.h> //fork
#include<signal.h>

int main(int argc, char * const * argv)
{
    
    
	((fork() && fork()) || (fork() && fork()));
	for(;;)
	{
    
    
		sleep(1);
		printf("休息1s, 进程id = %d!\n", getpid());
	}
	printf("再见了!\n");
	return 0;
}

The result of the operation is as follows:
insert image description here

Figure 5.1 Use the ps command to view the 7 processes generated by (fork() && fork()) || (fork() && fork());

6. Summary of possible reasons for fork failure

(1) There are too many processes in the system:

  • Something must be wrong, like too many zombie processes. In the entire system, the process ID (PID) seen when using the ps command to list processes is limited. The ID value of the created child process is greater than the ID value of the parent process by 1, and the process ID can be reused. For example, when a process ends ( After a period of time, the operating system will assign the ID of this process to other newly created processes for use (recycling).

  • By default, the maximum process ID value is generally 32767. If all the numbers from 0 to 32767 are occupied, fork will fail. Of course, this is an extreme situation.

(2) The number of created processes exceeds the maximum number of processes allowed by the current user.

  • Each user will have a total number of processes allowed to open.
printf("每个用户允许创建的最大进程数 = %ld\n", sysconf(_SC_CHILD_MAX));

Guess you like

Origin blog.csdn.net/weixin_30197685/article/details/131343093