Linux系列
文章目录
- Linux系列
- 前言
- 一、进程替换的概念
- 二、exec系列函数
-
- [2.1 execve()函数](#2.1 execve()函数)
- [2.2 execl()函数](#2.2 execl()函数)
- [2.3 execv()函数](#2.3 execv()函数)
- [2.4 execlp()函数](#2.4 execlp()函数)
- [2.5 execle](#2.5 execle)
- 三、程序替换的使用
- 总结
前言
进程程需替换是进程管理中一个重要的机制,它是通过exec
系列的函数实现的,它允许一个进程进行程序替换后,替换为另一个新进程,下面我们会通过示例详细分析它的使用及替换机制。
一、进程替换的概念
进程替换允许当前进程,在不创建新进程的情况下通过调用exec
系列函数,将当前进程替换为一个新的进程。替换后,新的程序从main
函数开始执行,进程的PID
保持不变,待代码段、数据段、堆栈等都会被替换。进程替换以便会配合frok
创建子进程,然后使用子进程替换,一般替换为shell
命令,进程替换发生时,当前进程的会被新的程序映像所替换,也就是说一旦发生进程替换该进程的旧代码将不会再被执行。
对于进程替换的机制,我们可以结合进程管理时所讲的内容来理解,发生进程替换时仅改变子进程所拥有的页表中虚拟地址和物理地址间的映射关系,将虚拟地址与新加入内存的数据代码建立联系,发生进程程序替换时,不仅数据会发生写实拷贝,代码同样会发生写实拷贝,所以进程管理机制对这里的程序替换可谓是非常重要。
进程替换的核心特点
- 内存空间替换:进程的内存(代码段、数据段、堆栈等)会被新的程序映像替换。
- 进程描述符:对于部分进程描述符是不改变的,如
PID
、PPID
、进程优先级等。
接下来我们会结合示例,对上面的概念详细分析和验证
二、exec系列函数
2.1 execve()函数

这个函数属于系统调用,是最基础的一个函数,exec
系列函数都是通过封装它来实现的,下面我们来介绍一下它的使用:
头文件
#include<unistd.h>
参数
1、filename:要替换执行程序的路径(绝对路径/相对路径)。
2、argv[]:字符串指针数组,包含要传递给程序的命令行参数,最后以NULL
结尾。
3、字符串指针数组,包含要传递给程序的环境变量,最后以NULL
结尾。
返回值
调用成功不返回,调用失败-1
.
为方便大家理解我先使用单继承版的进程程序替换
示例1:
c
1 #include<stdio.h>
2 #include<unistd.h>
3
4 int main ()
5 {
6 char *argv[]={"ls","-a","-l",NULL};
7 char *env[]={"AAA=1234",NULL};
8 printf("进程开始执行\n");
9 execve("/bin/ls",argv,env);
10 printf("程序未完成替换\n");
11 return 0;
12 }

从程序执行结果可以看到当,程序执行过execve
函数后,程序被替换为ls -a -l
命令,后面代码直接完成替换,所以少打印了一句。对于单进程的替换,不会存在写时拷贝,程序直接完成替换,进行上面替换时,可以更具需要创建不同的环境变量、命令行参数。
2.2 execl()函数

对于exec
系列的函数,都是仅在调用错误时返回-1
并且设置error
标识错误。
c
int execl(const char *path,const char *arg,...);
参数
1.要替换执行程序的路径(绝对路径/相对路径)
2.这是一个可变参数,可以同时接受多个传参
下面全部以多进程为例进行介绍
示例:
c
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/wait.h>
4 int main ()
5 {
6 pid_t id=fork();
7 if(id==0)
8 {
9 printf("I am child,pid:%d,ppid:%d\n",getpid(),getppid());
10 execl("/bin/ls","ls","-l","-a",NULL);
11 }
12 else{
13 printf("I am parent,pid:%d\n",getpid());
14 int status=0;
15 wait(&status);
16 }
17 return 0;
18 }

这里的可变参数允许我们传递多个命令参数,exec
系列函数是使用execve
封装而产生的,所以在功能上都差不多,只是使用上略微有差异。
2.3 execv()函数
c
int execv(const char *path,char const*argv[])
和execl
使用方法相似
参数
1.要替换执行程序的路径(绝对路径/相对路径)
2.字符串指针数组,存储的是我们需要执行的命令参数,以NULL结尾
示例:
c
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/wait.h>
4 int main ()
5 {
6 pid_t id=fork();
7 if(id==0)
8 {
9 char *const argv[]={"ls","-a","-l",NULL};
10 printf("I am child,pid:%d,ppid:%d\n",getpid(),getppid());
11 //execl("/bin/ls","ls","-l","-a",NULL);
12 execv("/bin/ls",argv);
13 }
14 else{
15 printf("I am parent,pid:%d\n",getpid());
16 int status=0;
17 wait(&status);
18 }
19 return 0;
20 }

执行结果相同,这一块真的没什么讲的。。。
2.4 execlp()函数
c
int execlp(const char *file,const char *arg,...);
这个函数相较于execl
,不需要传递路径信息,只需要传递要执行的程序名,该函数会去PATH
环境中查找。
1、file:程序名。
2、可变参数列表,可同时接受多个参数,以NULL收尾。
c
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/wait.h>
4 int main ()
5 {
6 pid_t id=fork();
7 if(id==0)
8 {
9 //char *const argv[]={"ls","-a","-l",NULL};
10 printf("I am child,pid:%d,ppid:%d\n",getpid(),getppid());
11 execlp("ls","ls","-l","-a",NULL);
12 //execv("/bin/ls",argv);
13 }
14 else{
15 printf("I am parent,pid:%d\n",getpid());
16 int status=0;
17 wait(&status);
18 }
19 return 0;
20 }

函数功能都类似,接下来我们在介绍一个,大家对比上面的理解即可
2.5 execle
c
int execle(const char *path, const char *arg,..., char * const envp[]);
execle
函数是exec
系列中比较复杂的一个,分析:
1、path:要替换执行程序的路径(绝对路径/相对路径)。
2、可变参数,包含要传递给程序的命令行参数,最后以NULL
结尾。
3、字符串指针数组,包含要传递给程序的环境变量,最后以NULL
结尾。
这个环境变量数组,根据自己的需要进行设置
c
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/wait.h>
4 int main ()
5 {
6 pid_t id=fork();
7 if(id==0)
8 {
9 char* env[]={"AAA=123",NULL}; ;
10 printf("I am child,pid:%d,ppid:%d\n",getpid(),getppid());
11 execle("bin/ls","ls","-l","-a",NULL,env);
12 }
13 else{
14 printf("I am parent,pid:%d\n",getpid());
15 int status=0;
16 wait(&status);
17 }
18 return 0;
19 }
需要注意的是,在传递环境变量时,是覆盖式传递。
三、程序替换的使用
早在学习指令是我们就说过,指令就是操作系统写好的可执行程序,所以我们不仅可以使用指令程序进程替换,我们也可以使用我们自己的程序进行替换,在我们介绍的main
函数的三个参数,刚好对应着exec
系列参数的类型。

当前目录下存在,可执行文件process
,我使用test
通过进程程序替换对它进行调用,并修改环境变量验证,子进程创建继承下来的环境变量不会应为,进程替换而改变。
test.c文件
c
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/wait.h>
4 #include<stdlib.h>
5 int main ()
6 {
7 pid_t id=fork();
8 setenv("AAA","123",0);
9 if(id==0)
10 {
11 execl("./process","process",NULL);
12 }
13 else{
14 printf("I am parent,pid:%d\n",getpid());
15 int status=0;
16 wait(&status);
17 }
18 return 0;
19 }
process.c文件
c
1 #include<stdio.h>
2 #include<stdlib.h>
3 int main()
4 {
5 printf("%s\n",getenv("AAA"));
6 return 0;
7 }

上面代码我们通过执行test
文件达到对其他文件的调用,并且证明了程序替换后子进程继承的环境变量不会改变。这里我们可以使用进程程序替换的方式对任意(不受语言限制),可执行文件进行调用,而exec
系列函数,只是充当了一个加载器的作用。
bash
命令行对于外部命令的调用也是使用fork()
和exec()
,通过进程程序替换来完成工作的。
总结
进程程序替换通过exec
系列函数实现,高效切换程序逻辑,广泛应用于命令行工具和动态服务。使用时需精准处理参数、环境变量及资源,确保稳定性和安全性。理解其机制对开发高效、可靠的多进程应用至关重要。