Linux——进程替换(exec)
一、进程替换的概念及目的
进程替换是指在操作系统中,一个正在运行的进程被另一个进程所替代的过程。
目的主要有以下几点:
-
多道程序设计:为了实现多道程序设计,一个进程在执行时可能需要被挂起,让其他进程获得执行机会。进程替换可以实现这种切换,确保系统中有多个进程在运行。
-
内存管理:在内存资源有限的情况下,为了最大程度地利用内存,操作系统可能需要对进程进行替换,将某些进程从内存中移除,以便为其他进程腾出空间。
-
提高系统性能:有时候,某个进程可能因为某种原因而无法继续执行,此时可以通过替换该进程来维持系统的正常运行,并且提高整体系统的性能。
-
允许动态加载:一些应用程序需要在运行过程中动态地加载新的代码或数据。进程替换可以实现这一目的,让应用程序能够在运行时动态地调整和更新自身。
二、进程替换的实现原理
进程替换的实现原理主要包括以下几个步骤:
-
创建新的进程控制块(PCB):首先需要创建一个新的进程控制块,用来存储关于进程的信息,比如程序计数器、栈指针、寄存器等信息。
-
加载新程序:将新程序的代码和数据加载到内存中,在进程的地址空间中为新程序分配空间。
-
更新进程控制块:将新程序的入口地址等信息更新到进程控制块中,确保进程能够正确执行新程序。
-
执行程序:将控制权转移到新程序的入口地址,开始执行新程序。
-
清理旧程序:在新程序开始执行后,清理和释放原来进程的资源,包括内存空间、文件描述符等。
三、进程替换函数
一共有六个,分别是:execl() ,execlp(),execle(),execv(),execvp() 和 execve();
其中前五个都是库函数,而最后一个是系统调用。
它们本质上没有区别,唯一区别就是参数形式不同。
我们通过man exec 命令可以在帮助手册上看出这些函数的定义形式如下图所示:
3.1execl()
execl("usr/bin/ps","ps","-f",(char*)0);
第一个参数是指定替换的程序路径,也就是你想执行谁?后面两个的参数是替换的程序的命令行参数,可以是一个,也可以是多个。
最后一个参数是要求必须以NULL结尾,也可以是NULL。
代码实现如下:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
int main()
{
printf("main pid=%d\n",getpid());
execl("/usr/bin/ps","ps","-f",(char*)0);
printf("execl err\n");
}
运行结果如下:
那么代码为什么不执行printf(“execl err\n”); 这条呢?
因为代码先执行execl(“/usr/bin/ps”,“ps”,“-f”,(char*)0); 只要替换成功了,进程就不会继续往下执行,所以不执行后面的代码。只要继续往后执行,就替换失败了。其中,替换过程没有创建新的进程,而是把原来进程的代码的数据替换了。
3.2execlp()
这个函数相较于execl多了一个p,这个p表示的意思就是path也就是环境变量中的path
环境变量中的PATH记录着各种操作系统指令所在的地址,所以使用这个函数替换程序时不需要传地址,直接传程序名就行,因为这个函数会自动在PATH所记录的路径查找这个程序,这个函数的第一个参数就对应着程序名 。
代码如下:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
int main()
{
printf("main pid=%d\n",getpid());
execlp("ps","ps","-f",(char*)0);
printf("execl err\n");
}
这个函数里面有两个ps字符串,虽然内容相同但是这两个字符串表示的意思是不一样的,第一个ps表示的是程序名,第二个ps表示的是执行程序的方法,那么这段代码的运行结果如下:
3.3execle()
这个函数就多一个参数,第三个参数表示的意思是环境变量,如果子进程要用到自定义环境变量或者系统的环境变量的话就可以用到第三个参数。
代码实现如下:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
int main(int argc,char* argv[],char* envp[])
{
printf("main pid=%d\n",getpid());
execle("/usr/bin/ps","ps","-f",(char*)0,envp);
printf("execl err\n");
}
运行结果:
3.4execv()
这个函数的v表示的是数组的意思也就是说将程序的所有指令全部放入到数组中
代码实现如下:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
int main(int argc,char* argv[],char* envp[])
{
printf("main pid=%d\n",getpid());
char* myargv[10]={"ps","-f",0};
execv("/usr/bin/ps",myargv);
printf("execl err\n");
}
运行结果:
3.5execvp()
这个就是将v,p组合到一起也就是说这个函数需要传的指令的名称和指令用法所组成的数组就可以了。
代码实现如下:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
int main(int argc,char* argv[],char* envp[])
{
printf("main pid=%d\n",getpid());
char* myargv[10]={"ps","-f",0};
execvp("ps",myargv);
printf("execl err\n");
}
运行结果:
3.6execve()
第一个参数是文件名,第二个参数是文件的使用方法所组成的数组,第三个参数就是环境变量 。
所以上面所用到的那么多函数其实底层实现都来自于execve函数,之所以有那么多不同的功能和参数其最终目的就是方便我们日常的使用。
代码实现如下:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
int main(int argc,char* argv[],char* envp[])
{
printf("main pid=%d\n",getpid());
char* myargv[10]={"ps","-f",0};
execve("/usr/bin/ps",myargv,envp);
printf("execl err\n");
}
运行结果: