【Linux】Linux 操作系统 - 16 , 进程控制 ! ------- 「一文彻底搞懂」进程等待和进程程序替换 !


一、进程等待


  在之前笔者介绍过进程状态相关内容 , 其中进程等待和进程状态部分相关联 , 以下具体介绍 !

是什么 ?


是指一个进程因为某些原因暂时无法继续执行,必须等待某个条件满足或某个事件发生后才能继续执行的状态 。

  光看概念还是很抽象的 , 以下所有部分会详细介绍 !


为什么有进程等待 ? (重要)


  这里就和进程状态部分有关了 , 若需要具体了解进程状态部分 , 请看 : 进程状态详解

在这里插入图片描述


等待方式(重要)

在这里插入图片描述

1 . wait()

在这里插入图片描述


  这里先看一个代码 : 子进程正常退出 , 父进程没有回收资源 , 子进程僵尸了

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
//验证wait 可以解决僵尸问题 
int main()
{
	printf("我是父进程 , 我的 pid  : %d , ppid : %d\n",getpid(),getppid());

	//创建子进程
	pid_t id = fork();	
	if(id == 0)
	{	
		// 子进程走 5 s
		int cns = 5;
		while(cns)
		{
			printf("我是子进程 , 我的 pid  : %d , ppid : %d\n",getpid(),getppid());
			sleep(1);
			cns--;
		}
		// 子进程正常退出
		exit(0);
	}		
	
	// 父进程不退出 , 也不回收子进程 pcb 
	while(1)
	{
		//father
		printf("我是父进程 , 我的 pid  : %d\n",getpid());
		sleep(1);
	}
	return 0;
}

在这里插入图片描述

● 父进程 wait() 资源 , 即 : 回收资源 !

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
///// 第一次演示 wait , 验证wait 可以解决僵尸问题 
int main()
{
	printf("我是父进程 , 我的 pid  : %d , ppid : %d\n",getpid(),getppid());

	//创建子进程
	pid_t id = fork();	
	if(id == 0)
	{	
		// 子进程走 5 s
		int cns = 5;
		while(cns)
		{
			printf("我是子进程 , 我的 pid  : %d , ppid : %d\n",getpid(),getppid());
			sleep(1);
			cns--;
		}
		// 子进程正常退出
		exit(0);
	}		

	sleep(10);
	// 父进程不退出 , 回收资源
	pid_t rid = wait(NULL); // NULL 意思是不获取退出信息
	if(rid > 0)
	{
		printf("success !\n");
	}

	sleep(30);	

	return 0;
}

  所以 , 通过这里可以看到子进程的僵尸状态就不存在了 , 因为父进程回收了子进程的资源 , 这也是标准的用法 , 父进程需要回收资源的 !!!


2 . waitpid()

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

● 父进程 waitpid() 成功

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
	
	printf("我是父进程 , 我的 pid  : %d , ppid : %d\n",getpid(),getppid());

	//创建子进程
	pid_t id = fork();	
	if(id == 0)
	{	
		// 子进程走 5 s
		int cns = 5;
		while(cns)
		{
			printf("我是子进程 , 我的 pid  : %d , ppid : %d\n",getpid(),getppid());
			sleep(1);
			cns--;
		}
		// 子进程正常退出
		exit(0);
	}

	sleep(10);
	//                  any  不获取 阻塞
	pid_t rid = waitpid(-1 , NULL , 0 );
	if(rid > 0)
	{
		printf("wait success ! , rid = %d\n",rid);
	}
	sleep(20);
	return 0;
}

在这里插入图片描述
  返回的是子进程的 ID !!!

● 父进程 waitpid() 失败

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>
// errno 存的是错误码 ,  strerrno 打印错误码信息 !


int main()
{
	
	printf("我是父进程 , 我的 pid  : %d , ppid : %d\n",getpid(),getppid());

	//创建子进程
	pid_t id = fork();	
	if(id == 0)
	{	
		// 子进程走 5 s
		int cns = 5;
		while(cns)
		{
			printf("我是子进程 , 我的 pid  : %d , ppid : %d\n",getpid(),getppid());
			sleep(1);
			cns--;
		}
		// 子进程正常退出
		exit(0);
	}
	
	sleep(5);
			
	int wstatus = 0;
	//父进程等待 , waitpid 等待错误
	pid_t rid = waitpid(id+1 , &wstatus , 0);

	if(rid > 0)
	{
		printf("Wait Success ! , rid : %d\n",rid);
	}

	else
	{	
		printf("Wait Failed ! , err coed : %d -> %s , rid : %d\n",errno,strerror(errno),rid);
	}

	sleep(10);	

	return 0;
}

在这里插入图片描述
  失败返回的是 -1 , 并同时设置错误码 !!


详解 wait 系列接口的参数 wstatus(重要)

在这里插入图片描述

1 . wstatus 到底是怎么获取子进程的退出状态信息的 ??

在这里插入图片描述

  所以 , 系统会写入父进程中定义的 wstatus 中的次低 8 位比特位用来表示该进程的退出状态(退出码) .


2 . 父进程 wstatus 查看退出状态

  • 用宏 - WEXITSTATUS(wstatus)
  • 手动 - ( wstatus >> 8 ) & 0XFF
  • 等宏查看 man waitpid 手册说明 !


  这里看一个代码 :

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <wait.h>
#include <stdlib.h>
int main()
{
	pid_t id = fork();
	if(id == 0)
	{
		printf("我是子\n");	
		//退出码设置为 1
		exit(1);
	}
	//父进程等待获取子进程退出信息
	int wstatus = 0;
	pid_t rid = waitpid(id,&wstatus , 0);
	if(rid > 0)
	{
		printf("Success ! rid : %d , exit code : %d\n",rid , wstatus);
	}
	return 0;
}


运行结果 :
在这里插入图片描述
?????????? 退出码怎么是 256 , 不是说 wstatus 就会带回退出状态吗 ?? 子进程的退出码明明是 1 啊 , 怎么回事 ??????

在这里插入图片描述

在这里插入图片描述

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <wait.h>
#include <stdlib.h>
int main()
{
	pid_t id = fork();
	if(id == 0)
	{
		printf("我是子\n");
//		int num = 1/0;	
		//退出码设置为 1
		exit(100);
	}
	//父进程等待获取子进程退出信息
	int wstatus = 0;
	pid_t rid = waitpid(id,&wstatus , 0);
	if(rid > 0)
	{
		//						                 这个计算下来就是退出状态
		// 因为退出信号就在低 8 位 , 所以直接 & 0x7F 
		printf("Success ! rid : %d , exit code : %d , exit signal : %d\n", rid , (wstatus>>8) & 0XFF , wstatus&0X7F);
		printf("Success ! rid : %d , exit code : %d , exit signal : %d\n", rid , WEXITSTATUS(wstatus) , wstatus&0X7F);
		//查看子进程是否正常退出 
		
		printf("%d\n" ,  WIFEXITED(wstatus));
	}
	return 0;
}


详解 waitpid 中的 optinons (重要)


  这个表示的是选项 , 即 : 阻塞选项 !

1 . 阻塞


  这个很好理解 , 阻塞就是停滞在这里 , 这里就好比我们写了 scanf () 函数 , 会停下来等用户输入 !

在这里插入图片描述


2 . 非阻塞轮询

在这里插入图片描述


3 . options

在这里插入图片描述


二、进程的阻塞等待和非阻塞等待

阻塞等待 :

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>


//////// 阻塞等待  -  父进程啥也不干

int main()
{
	
	printf("我是父进程 , 我的 pid  : %d , ppid : %d\n",getpid(),getppid());

	//创建子进程
	pid_t id = fork();	
	if(id == 0)
	{	
		// 子进程走 5 s
		int cns = 5;
		while(cns)
		{
			printf("我是子进程 , 我的 pid  : %d , ppid : %d\n",getpid(),getppid());
			sleep(1);
			cns--;
		}
		// 子进程正常退出
		exit(0);
	}
	
	sleep(5);
			
	int wstatus = 0;
	//父进程等待 
	pid_t rid = waitpid(id , &wstatus , 0);

	if(rid > 0)
	{
		printf("Wait Success ! , exit code : %d , rid : %d\n", WEXITSTATUS(wstatus) ,rid);
	}

	else
	{	
		printf("Wait Failed ! , err coed : %d -> %s , rid : %d\n",errno,strerror(errno),rid);
	}

	sleep(10);	

	return 0;
}

非阻塞等待 :(了解)

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>

// 函数指针类型
typedef void (*func_t)();

// 任务表数组
#define NUM 5
func_t handlers[NUM+1];

// 如下是任务
void DownLoad()
{
    printf("我是一个下载的任务...\n");
}
void Flush()
{
    printf("我是一个刷新的任务...\n");
}
void Log()
{
    printf("我是一个记录日志的任务...\n");
}


// 注册每个任务       任务表    注册的任务函数
void registerHandler(func_t h[], func_t f)
{
    int i = 0;
    for(; i < NUM; i++)
    {
        if(h[i] == NULL) break;
    }
    if(i == NUM)  return;
    h[i] = f;
    h[i+1] = NULL;
}


int main()
{
	
	// 开始注册任务
	registerHandler(handlers, DownLoad);
	registerHandler(handlers, Flush);
	registerHandler(handlers, Log);


	//创建子进程
	pid_t id = fork();	
	if(id == 0)
	{	
		// 子进程走 5 s
		int cns = 5;
		while(cns)
		{
			printf("我是子进程 , 我的 pid  : %d , ppid : %d\n",getpid(),getppid());
			sleep(1);
			cns--;
		}
		// 子进程正常退出
		exit(0);
	}
	
	while(1)
	{			
		int wstatus = 0;
		//父进程等待 , 非阻塞
		pid_t rid = waitpid(id , &wstatus ,  WNOHANG);
			
		// 走这个证明等到了 !!!
		if(rid > 0)
		{
			printf("Wait Success ! , exit code : %d , rid : %d\n", WEXITSTATUS(wstatus) ,rid);
			break; // 等待结束 , 跳出
		}
		// 等待的进程状态没有改变 , 但存在该进程 
		else if(rid == 0 )
		{
		  	 // 这里面做自己的事情
			 // 函数指针进行回调处理 , 开始做注册表中的每个任务 !!!!!
           		 int i = 0;
           		 for(; handlers[i]; i++)
           		 {
			     // 函数回调
           		     handlers[i]();
           		 }
           		 printf("本轮调用结束,子进程没有退出\n");
           		 sleep(1);	
		
		}
		// 等待失败 		
		else 
		{	
			printf("Wait Failed ! , err coed : %d -> %s , rid : %d\n",errno,strerror(errno),rid);
			break; // 等待失败 , 跳出 !
		}

	}

	return 0;
}

在这里插入图片描述


三、进程的程序替换(重要)


  进程的程序替换也是需要我们重点掌握的内容, 其还是有点用的 !

是什么 ?

fork() 之后,父子各自执行父进程代码的一部分如果子进程就想执行一个全新的程序呢?进程的程序替换来完成这个功能!


  ‌进程的程序替换(Process Program Replacement)‌是指在Linux系统中,一个正在运行的进程通过使用exec 系列函数系列来执行另一个程序的过程 !


  也许概念有些抽象, 这里看一个例子 , 见见程序替换可以带来什么效果 !!

// 程序替换初步见一见
#include <stdio.h>
#include <unistd.h>
int main()
{
	
	printf(" 我要开始执行程序替换了 !\n");
	execl("/usr/bin/ls" , "ls" , "-a" , "-l" , NULL); // 这个系列函数必须以 NULL 结尾
	printf(" 程序替换执行完毕了 !\n");
	printf("Success !\n");
	
	return 0;
}

在这里插入图片描述


深入探讨进程程序替换(重要)


  上面笔者演示了 , 进程程序替换带来的效果 , 但是仔细观察会发现一个问题 .

  以下将进行探讨 !

在这里插入图片描述


1 . 替换的原理


在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


  所以 , 就能解释为什么上面样例代码 , 在执行 execl 调用后后面的程序不执行 , 因为后面的程序已经被替换(覆盖)掉了 , 后面的已经不存在了 !!!


2 . exec 系列接口


  在 Linux 中替换用的是 exec 系列的接口 !

在这里插入图片描述

1 . execl

在这里插入图片描述

// 写法 1
 execl("/usr/bin/ls" , "ls" , "-a" , "-l" ,NULL); // 这个系列函数必须以 NULL 结尾
//写法 2
 execl("/usr/bin/ls" , "ls" , "-al" ,NULL); // 这个系列函数必须以 NULL 结尾

2 . execlp

在这里插入图片描述

//写法 1
execlp("ls" , "ls" , "-a" , "-l" , NULL);	
//写法 2
execlp("ls" , "ls" , "-al", NULL);	

3 . execv

在这里插入图片描述

  首先得有一个命令行参数表 ,然后直接传表名就可以 ,系统会自动执行 !!

// 这里要特别注意 , 这个表的结尾必须也是 NULL !!!!!!!!!!!!!!!!!
char* argv[] = {"/usr/bin/ls" ,"-a" , "-l" ,  NULL};
execv("/usr/bin/ls" , argv);		

4 . execvp

在这里插入图片描述

// 注意 : v - vector , 要数组 , NULL 结尾
char* argv[] = {"ls" ,"-a" , "-l" ,  NULL};
execvp("ls" , argv);            

5 . execvpe

在这里插入图片描述

总结

在这里插入图片描述


3 . putenv 函数

在这里插入图片描述


exec 系列常规写法


  一般都是让子进程去程序替换的 !

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

int main()
{
	printf("程序要运行了!\n");
	pid_t id = fork();
	if(id == 0)
	{
		//child
		printf("I am Child, My Pid Is: %d\n", getpid());
       		sleep(1);		

		char* argv[] = {"ls" , "-l" , "-a" , NULL};
		execvp("ls" , argv);
	
	}
	// father
	 waitpid(-1, NULL, 0);
   	 printf("我的程序运行完毕了\n");
	

}


总结

在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值