Linux进程替换
1.复习
问题1:我们怎么解决僵尸进程?僵尸进程为什么要存在?什么是僵尸进程?
答案是僵尸进程是我们子进程死掉之后,它不能被完全杀掉,它在PCB里面还保存了这个进程的退出信息等待父进程去获取,父进程拿到僵尸进程的退出码可以帮助我们拿到子进程任务执行的结构。成功了还是没成功,我们通过子进程里的status这个16个比特位的值就可以知道。
问题2:退出信息在哪里?
退出信息在子进程的内核数据结构里保存着,status表示了代码执行的情况,第一种是代码跑完,结果不对,第二种是代码跑完,结果正确,第三张是代码未跑完,直接异常退出了。前两种是退出信号编码为0,也就是16个比特位的后七位,退出码正确是0,不正确是返回对应的错误。如果是第三张代码异常退出,就会是退出码为0,但是退出信号编码为对应的错误,让我们用户知道错误所在。
问题3:什么是阻塞等待
子进程退出时,我们父进程说是要退出回收,但是怎么回收呢?就是我们的wait和waitpid,一般我们多用后者,因为后者可以回收指定的子进程,并且决定是阻塞等待还是阻塞轮询,功能比较全面,阻塞等待就是我们使用父进程的waitpid进行回收子进程,不回收子进程父进程就一直在那里等待,叫阻塞等待,阻塞轮询是多次查询,并不是一直在那里等待。当我们waitpid的返回值==0是子进程未退出,未等待成功,。0子进程退出等待成功。<0等待错误。
我们一般用while来实现阻塞轮询。为什么要这么做呢?因为它不会卡住父进程,进程就可以在等待的间隙做其他事情了。
2.进程替换
我们知道我们fork可以创建一个子进程我们创建子进程可以让子进程程序不同的程序,也可以让子进程复制自己,使父子进程完成不同的代码段。fork之后代码共享,数据共享但是进程又是相互独立的,这是通过写实拷贝实现的。
下面就是我们备份数据过程中,我们让父进程继续去执行它的任务,即使它的数据被覆盖,但是我们创建了一个子进程,子进程会继承父进程的数据和代码,这个时候父进程如果发生数据改变会进行写实拷贝。这样我们实现了简单的备份。
那么如果我们想让子进程成为一个全新的程序,有自己独立的数据呢?
这就要用到我们的程序替换了,我们知道我们的一个命令也是进程,它的父进程是.bash,它就是创建子进程帮助它去执行任务的,这个在我们Linux下时通过进程替换实现的。
3.程序替换的原理
程序替换的过程中我们并没有创建新的进程。
在程序替换的过程中,我们是把代码和数据去改掉,我们使用进程替换的时候,我们的代码就被替换掉了,我的进程已经开始执行另一个程序的代码了,我自己原来的程序代码已经没有了。程序替换一旦成功后续代码不再执行,程序替换成功不会返回值,只要返回值,替换必然失败。返回-1.
我们常提到,我们的文件要想执行要先加载到内存,但是加载是什么呢?我们的进程进行加载的时候先把PCB加载到内存,然后再加载代码和数据,我的程序是如何加载到内存呢?是不是程序替换的过程就像是一种加载呢?
总之,我们进程替换是没有创建新的进程的,只是让当前进程的后续代码不再执行。而是执行我们替换之后的代码了,我自己写的代码,已经没有了。
同时这种进程替换也不会影响父进程,因为父进程和子进程一定是相互独立的,当发生数据不一样的时候它会进行写实拷贝进行写入数据的。可以理解为,代码如果要进行替换,也要进行写实拷贝。
总而言之,进程一定是相互独立的,即便是父子。
我们知道,根据冯若依曼结构,CPU只和内存打交道,不和外设打交道,我们的二进制文件想要执行就要加载到内存里去,加载的时候,PCB先加载,然后再加载代码和数据,那么我们所说的程序替换,它进行程序替换之后就要把它替换的代码和数据加载到内存,那这个是不是就相当于一种加载器呢?
我们的程序替换有6个函数可以实现
总结对比表
l代表list,代表可变参数列表,v代表vector,代表我们的argv[],p代表我们的PATH环境变量
函数 | 参数传递方式 | 是否搜索 PATH | 是否自定义环境变量 | 备注 |
---|---|---|---|---|
execl() | 可变参数列表 | ❌ 否 | ❌ 否 | 必须提供完整路径 |
execv() | 字符串数组 | ❌ 否 | ❌ 否 | 必须提供完整路径 |
execle() | 可变参数列表 | ❌ 否 | ✔️ 是 | 可自定义 envp[] |
execve() | 字符串数组 | ❌ 否 | ✔️ 是 | 底层系统调用 |
execlp() | 可变参数列表 | ✔️ 是 | ❌ 否 | 自动搜索 PATH |
execvp() | 字符串数组 | ✔️ 是 | ❌ 否 | 最常用,自动搜索 PATH |
当我们的exec带上p我们就可以省略去路径,因为它回去PATH默认去搜索。
list需要自己去在函数里打出来,vector就可以直接使用我们的main函数参数列表argv[]。
但是他们最后的一个参数必须是NULL结尾的。
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>int main() {pid_t pid = fork();if (pid == 0) {// 子进程替换为 `ls -l`char *argv[] = {"ls", "-l", NULL};execvp("ls", argv);perror("execvp failed"); // 只有出错时才会执行return 1;} else {wait(NULL); // 父进程等待子进程结束printf("Child process finished.\n");}return 0;
}
进程替换(Process Replacement):
exec
不会创建新进程,而是 替换当前进程的代码段、数据段等。调用成功后,原进程的代码不再执行(除非
exec
失败)。
返回值:
只有 出错时 才会返回
-1
(成功时不会返回,因为原进程已被替换)。
典型用途:
在
fork()
后的子进程中加载新程序(如 Shell 执行外部命令)。实现 最小权限原则(如
execle
限制环境变量)。
如果我们默认去传环境变量,它会把系统的环境变量替换掉,如果我们想要在系统原有的环境变量进行追加。需要进行putenv添加环境变量。