【Linux】进程等待

进程需要等待的原因

  • 子进程退出,父进程如果不管,就会导致子进程变成僵尸进程,进而导致内存泄漏
  • 进程一旦变成僵尸进程,使用kill -9信号也无法杀死,因为无法对一个死去的进程进行操作。
  • 同时,子进程完成自己的任务后,需要父进程获取到子进程任务完成的结果。

所以,父进程可以通过进程等待的方式回收子进程的资源,从而获取子进程的退出信息。

僵尸进程无法被杀死,需要进程等待来杀死,进而解决内存泄漏问题(必须解决)。因此需要通过进程等待来获得子进程的退出情况(可选的需求)。

进度等待的概念


通过系统调用wait()或者waitpid(),来对子进程进行状态检测与回收的功能。


进程等待的方法

cpp 复制代码
int main()
{
        // 创建子进程
        pid_t id = fork();
        if(id < 0)
        {
                // 进程创建失败
                perror("fork fail!\n");
                return 1;
        }
        else if(id == 0)
        {
                // 子进程
                int cnt = 5;
                while(cnt)
                {
                        printf("child process : pid->%d ; ppid->%d ; cnt->%d\n", getpid(), getppid(), cnt);
                        sleep(1);
                        cnt--;
                }
                // 进程终止
                exit(0);
        }
        else
        {
                // 父进程
                while(1)
                {
                        printf("father process : pid->%d ; ppid->%d\n", getpid(), getppid());
                        sleep(1);
                }
        }

        return 0;
}

【perror知识点】fork创建子进程失败,会修改errno值,perror()可以将错误码描述输出出来。


父进程通过调用wait()或者waitpid()进行僵尸进程的回收问题!


cpp 复制代码
int main()
{
        // 创建子进程
        pid_t id = fork();
        if(id < 0)
        {
                // 进程创建失败
                perror("fork fail!\n");
                return 1;
        }
        else if(id == 0)
        {
                // 子进程
                int cnt = 5;
                while(cnt)
                {
                        printf("child process : pid->%d ; ppid->%d ; cnt->%d\n", getpid(), getppid(), cnt);
                        sleep(1);
                        cnt--;
                }
                // 进程终止
                exit(0);
        }
        else
        {
                // 父进程
                int cnt = 10;
                while(cnt)
                {
                        printf("father process : pid->%d ; ppid->%d ; cnt->%d\n", getpid(), getppid(), cnt);
                        sleep(1);
                        cnt--;
                }
                // 回收子进程??参数设置为NULL??返回值为子进程pid
                pid_t ret = wait(NULL);
                if(ret == id)
                {
                        printf("wait success\n");
                }
        }

        return 0;
}

所以------进程等待是创建进程后必须存在的一种工作

【注意】如果进程被创建出了很多个,wait会将哪一个进程回收?wait会等待任意一个子进程退出

wait随机等待任意子进程

cpp 复制代码
  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 #include<sys/types.h>
  5 #include<sys/wait.h>
  6 
  7 #define N 10
  8 
  9 // 子进程需要执行的任务
 10 void ChildRun(int i)
 11 {
 12     int cnt = 5;
 13     while(cnt)
 14     {
 15         printf("child[%d]-running-pid : %d ; ppid : %d ; cnt : %d\n", i, getpid(), getppid(), cnt);
 16         sleep(1);
 17         cnt--;
 18     }
 19 }
 20 
 21 int main()
 22 {
 23     // 父进程创建10个子进程
 24     for(int i = 0; i < N; ++i)
 25     {   
 26         pid_t id = fork();
 27         if(id == 0)
 28         {   
 29             // 创建子进程成功
 30             // 子进程执行任务
 31             ChildRun(i);
 32             // 终止当前子进程
 33             exit(1);
 34         }
 35     }
        // 此时10个子进程均创建成功
 38     printf("child fail success\n");
 39 
 40     // 让10个子进程均变成僵尸进程
 41     sleep(10);
 42 
 43     // 父进程开始回收子进程,wait()随机等待子进程
 44     for(int i = 0; i < N; ++i)
 45     {
 46         // 随机等待一个子进程
 47         pid_t ret = wait(NULL);
 48         if(ret > 0)
 49         {
 50             // 当前子进程回收成功
 51             printf("child %d wait success\n", ret);
 52         }
 53     }
 54     sleep(5); // 父进程进行执行
 55 
 56     return 0;
 57 }

【注意】父进程是随机等待子进程的

子进程不退出父进程一直等待子进程

子进程中如果有一个或者多个不退出,父进程会一直等待回收子进程,并不会进行后续的工作,父进程会一直在wait进行阻塞等待。

  • 如果子进程不退出,父进程默认在wait的时候,在调用这个系统调用的时候,也就不返回,默认叫做阻塞状态。
  • 阻塞状态不仅仅只有等待硬件(cin输入流),也会存在等待软件的情况(等待子进程退出)。

子进程的退出结果------waitpid

wait提供的功能是waitpid所提供的功能的子集。


pid_t waitpid(pid_t pid, int *status, int options);

返回值:

  • 当正常返回的时候waitpid返回等待成功的子进程的进程ID。
  • 如果参数options设置了WHOHANG,而调用waitpid后发现没有子进程可以等待,则返回0;
  • 如果调用失败,返回-1,这时会将errno设置成相应的值以指示错误所在。

参数:

  • pid :
  1. pid=-1,等待任意一个子进程,与wait效果相同
  2. pid>0,等待其进程ID与pid相等的子进程,相当于可以选择某一个子进程退出。
  • status:
  1. WIFEXITED(status):若为正常终止子进程返回的状态,则为真。(查看进程是否正常退出)
  2. WEXITSTATUS(status):若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
  • options:
  1. WNOHANG:若pid指定的子进程没有结束,则waitpid()j函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

【测试】将上述代码中的wait替换成waitpid,其中第一个参数设置成-1,效果是相同的。

cpp 复制代码
 99 int main()
100 {
101     // 创建子进程
102     pid_t id = fork();
103     if(id < 0)
104     {
105         // 进程创建失败
106         perror("fork fail!\n");
107         return 1;
108     }
109     else if(id == 0)
110     {
111         // 子进程
112         int cnt = 5;
113         while(cnt)
114         {
115             printf("child process : pid->%d ; ppid->%d ; cnt->%d\n", getpid(), getppid()    , cnt);
116             sleep(1);
117             cnt--;
118         }
119         // 进程终止
120         exit(0);
121     }
122     else
123     {
124         // 父进程
125         int cnt = 10;
126         while(cnt)
127         {
128             printf("father process : pid->%d ; ppid->%d ; cnt->%d\n", getpid(), getppid(    ), cnt);
129             sleep(1);
130             cnt--;
131         }
132         // 回收子进程??参数设置为NULL??返回值为子进程pid
133         //  pid_t ret = wait(NULL);
134         pid_t ret = waitpid(id, NULL, 0);
135         if(ret == id)
136         {
137             printf("wait success\n");
138         }
139     }
140 
141     return 0;
142 }

第一个参数设置成子进程id可以选择性的等待进程。

获取子进程的退出信息

是否需要获取资金的退出信息,这件事情是可选的,如果不需要获取,直接在wait或者waitpid的status参数设置成为NULL即可。

++如何理解status指针?++

  1. status指针是一个输出形参数
  2. int 是被当作多个部分使用的

++问题1:子进程退出一共会有几种退出场景?++

【答】:三种(1)代码运行完毕,结果正确;(2)代码运行完毕,结果不正确;(3)代码异常终止。

++问题2:父进程等待中,期望获得子进程退出的哪些信息?++

【答】:(1)代码是否异常;(2)没有异常,结果是否正常,通过exitcode来获取退出信息;(3)用1,2,3,4....表示不同的出错原因

status这个退出信息,需要考虑三个因素:(1)退出是否正常;(2)不正常退出,原因是合适;(3)正常退出,结果是否正常。status是int变量,有32个bit位置,只需要关注低16位即可。

  1. 前0到7个比特位置(除了第8个位置core dump后续讲解),是描述进程异常退出的情况,进程退出一定是收到了某种信号,才会退出。
  2. 前8到15个bit位是描述进程正常退出的退出状态,也就是子进程退出时exit的退出信息。(上面我们的exit退出码是1,将1放在第8个比特上,也就是256)

++问题3:父进程需要获取子进程的状态数据的任意数据,为什么必须要使用wait系统调用呢?为什么不能使用全局变量?++

【答】:不能使用全局变量,是因为进程具有独立性,当子进程修改了全局变量,父进程的该全局变量的值并不会修改。只能通过系统调用获取相对应的进程状态信息。

【总结】status可以通过位图的操作,反应进程是否异常退出以及退出时的情况

使用系统设置的宏来获取进程退出的信息

【系统设置status的宏】

  1. WIFEXITED(status):若为正常终止子进程返回的状态,则为真。(查看进程是否正常退出)
  2. WEXITSTATUS(status):若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

进程退出的原理

非阻塞轮询

每一个子进程的PCB中也有一个等待队列,当父进程想要回收子进程的时候,子进程就会让父进程处于该等待队列中,waitpid如果一直回收不到子进程,就会一直处于阻塞等待状态。而waitpid第三个参数options可以选择父进程等待的方式。

当options设置成为这个宏WNOHANG的时候,返回值pid_t就会多一种选择:

  • pid_t ret > 0 ; 等待成功
  • pid_t ret < 0 ; 等待失败
  • pid_t ret == 0; 代表所等待的条件还没有就绪
cpp 复制代码
int main()
{
	// 创建子进程
	pid_t id = fork();
	if(id < 0)
	{
		// 进程创建失败
		perror("fork fail!\n");
		return 1;
	}
	else if(id == 0)
	{
		// 子进程------10s的操作
		int cnt = 10;
		while(cnt)
		{
			printf("child process : pid->%d ; ppid->%d ; cnt->%d\n", getpid(), getppid(), cnt);
			sleep(1);
			cnt--;
		}
		// 进程终止
		exit(1);
	}
	else
	{
		// 父进程
		int cnt = 3;
		while(cnt)
		{
			printf("father process : pid->%d ; ppid->%d ; cnt->%d\n", getpid(), getppid(), cnt);
			sleep(1);
			cnt--;
		}
		// 回收子进程------参数设置为NULL------返回值为子进程pid
		//	pid_t ret = wait(NULL);
		// 设置子进程的退出信息
		while(1)
		{
	    	int status = 0;
	    	pid_t ret = waitpid(id, &status, WNOHANG);// 设置轮询等待
	    	if(ret > 0)
	    	{
	    		//printf("wait success, ret : %d , exit sig : %d ; exit code : %d \n",
	    				//ret, status & 0x7F, (status >> 8) & 0xFF);

				// 等待成功
	    		// 使用系统的宏来判断进程是否正常终止
	    		if(WIFEXITED(status))
	    		{
	    			printf("进程是正常退出的,退出码:%d\n", WEXITSTATUS(status));
	    		}
				else
				{
					printf("进程异常终止.\n");
				}
				break;
	    	}
			else if(ret < 0)
			{
				printf("等待子进程失败!\n");
				break;
			}
			else
			{
				// 子进程还在运行,父进程可以先做自己的事情一会等待
				printf("父进程做自己的事情,一会等待子进程.\n");
				sleep(1);
			}

		}

	}

	printf("所有任务完成\n");

	return 0;
}

上述代码演示了父进程轮询等待子进程。父进程等待子进程,子进程没有终止,父进程会返回0,让父进程处于循环中,再次循环等待。

相关推荐
云和数据.ChenGuang1 小时前
运维面试题之oracle和mysql单表最大容量
运维·mysql·oracle
互联网老欣2 小时前
2025年保姆级教程:阿里云服务器部署Dify+Ollama,打造专属AI应用平台
服务器·阿里云·ai·云计算·dify·ollama·deepseek
偶像你挑的噻2 小时前
12-Linux驱动开发- SPI子系统
linux·驱动开发·stm32·嵌入式硬件
酷柚易汛智推官2 小时前
Fastlane赋能移动研发:从全流程自动化到工程效能升级
运维·自动化·酷柚易汛
落798.2 小时前
Genlogin × Bright Data,一键解锁自动化采集的高成功率方案
运维·自动化·数据采集·亮数据
羑悻的小杀马特2 小时前
轻量跨云·掌控无界:Portainer CE + cpolar 让远程容器运维像点外卖一样简单——免复杂配置,安全直达对应集群
运维·网络·安全·docker·cpolar
松涛和鸣2 小时前
16、C 语言高级指针与结构体
linux·c语言·开发语言·数据结构·git·算法
L***86532 小时前
Failed to restart nginx.service Unit nginx.service not found
运维·nginx
念风2 小时前
[lvgl]如何优雅地向lv_port_linux中添加tslib支持
linux