进程(六)—— 进程程序替换(exec函数族)

本文深入探讨了在Unix/Linux系统中,如何通过fork创建子进程,并利用exec函数族进行进程程序替换,实现子进程执行不同代码。exec函数包括execl、execlp、execv、execvp和execvpe,它们在参数传递和搜索路径上有不同特点。这些函数允许程序在不创建新进程的情况下执行其他程序,广泛应用于进程间的任务切换和执行不同任务的场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

fork创建子进程以后,子进程继承父进程的一部分代码和数据,如果我们希望子进程和父进程执行不同的代码,我们可以通过判断 fork函数 的返回值来判断当前进程是父进程还是子进程,进而分配不同的任务。

exec 函数族的作用是替换原本要执行程序,转而去执行其他程序,可以让我们更加灵活的给子进程分配任务。 


目录

一、exec函数族

1、execl / execlp

(1) execl函数参数解析

(2) execlp 函数参数解析

2、execv / execvp / execvpe

(1) execv函数参数解析

(2) execvp函数参数解析

(3) execvpe函数参数解析

二、exec 函数族的应用场景

三、进程程序替换原理


一、exec函数族

exec函数族的命名存在规律性,exec之后的字母代表了当前函数传递参数的类型。

  • “l”:表示list,传递的是形参列表;
  • “v”:表示vector,传递的是形参数组;
  • “p”:表示path,说明当前函数会去环境变量中搜索替换程序文件;
  • “e”:表示environment,说明当前函数搜索替换程序程序时,搜索的是自定义环境变量。

1、execl / execlp

execl 和 execlp函数的声明如下:

(1) execl函数参数解析

execl函数是去指定路径下面搜索并执行相应的程序。execl 的最后一个参数必须是NULL

  • 参数 path:替换程序所在路径。(如果是自己编写的替换程序,替换程序必须包含main函数)
  • 参数arg:传递给替换程序的参数。arg 会被传递给替换程序 main 函数的 argv,main函数中的 argv 根据NULL来判断参数是否传递结束。
// argc —— 接收到的外部参数的个数
// argv —— 接收到的外部参数的内容
int main(int argc, char** argv){}
  • 返回值:调用失败返回 -1,errno 会被设置(可以根据错误码对应到错误信息)
// execl 
const char* path = "/bin/ls";
if(execl(path, "ls", "-a", "-l", NULL)<0){    // execl 最后一个参数必须是NULL
    perror("execl");
}

(2) execlp 函数参数解析

execlp函数是去当前Shell 的环境变量PATH中搜索并执行相应的程序。execlp 的最后一个参数必须是NULL

  • 参数 file:替换程序的名称。因为是去环境变量PATH中搜索,所以不需要明确的路径,只需要程序名称。(如果是自己编写的替换程序,替换程序必须包含main函数)
  • 参数arg:传递给替换程序的参数。同 execl 函数。
  • 返回值:调用失败返回 -1,errno 会被设置(可以根据错误码对应到错误信息)
// execlp
// 环境变量PATH包含了/bin,程序可以在bin目录下找到 ls文件     
const char* file= "ls";       
if(execl(file, "ls", "-a", "-l", NULL)<0){    // execl 最后一个参数必须是NULL
    perror("execlp");
}

2、execv / execvp / execvpe

execv 的作用和 execl 的作用是一样的,execv中的“v”表示vector,传递的是一个数组;execl中的“l”表示list,传递的是参数列表。两者的区别仅仅只是传递参数的方式不同。

(1) execv函数参数解析

execv函数是去指定路径下面搜索并执行相应的程序,传入的是一个参数数组,而不是参数列表。

  • 参数 path:替换程序所在路径。(如果是自己编写的替换程序,替换程序必须包含main函数)
  • 参数argv:传递给替换程序的参数数组。argv 的最后一个参数必须是NULL(原因参考execl)
  • 返回值:调用失败返回 -1,errno 会被设置(可以根据错误码对应到错误信息)
// execv 
const char* path = "/bin/ls";
char* argv[] = {"ls", "-a", "-l", NULL};    // argv 最后一个参数必须是NULL
if(execv(path, argv)<0){    
    perror("execv");
}

(2) execvp函数参数解析

execvp函数是去当前Shell 的环境变量PATH中搜索并执行相应的替换程序。

  • 参数 file:替换程序的名称。因为是去环境变量PATH中搜索,所以不需要明确的路径,只需要程序名称。(如果是自己编写的替换程序,替换程序必须包含main函数)
  • 参数argv:传递给替换程序的参数数组。argv 的最后一个参数必须是NULL(原因参考execl)
  • 返回值:调用失败返回 -1,errno 会被设置(可以根据错误码对应到错误信息)
// execv 
// 环境变量PATH包含了/bin,程序可以在bin目录下找到 ls文件     
const char* file= "ls";
char* argv[] = {"ls", "-a", "-l", NULL};    // argv 最后一个参数必须是NULL
if(execvp(file, argv)<0){    
    perror("execvp");
}

(3) execvpe函数参数解析

execvpe函数比execvp函数多了一个“e”,“e”表示environment,execvp函数是在环境变量PATH中搜索并执行替换程序;而execvpe函数则是使用自定义的环境变量,也就是相当于我们自己提供替换程序的搜索路径(搜索路径可以有多个)

  • 参数 file:替换程序的名称。因为是去环境变量PATH中搜索,所以不需要明确的路径,只需要程序名称。(如果是自己编写的替换程序,替换程序必须包含main函数)
  • 参数argv:传递给替换程序的参数数组。argv 的最后一个参数必须是NULL(原因参考execl)
  • 参数envp:自定义的环境变量数组。
  • 返回值:调用失败返回 -1,errno 会被设置(可以根据错误码对应到错误信息)
// execvpe
const char* file= "ls";
char* argv[] = {"ls", "-a", "-l", NULL};    // argv 最后一个参数必须是NULL
char* const envp[]={"PATH=/bin:/usr/bin", "TERM=console", NULL};    //打包成数组
if(execvpe(file, argv, envp) < 0) {
    perror("execvpe");
}

二、exec 函数族的应用场景

exec函数执行的时候有如下特点:

  • 进程间具有独立性,子进程的程序替换不会影响到父进程
  • 进程替换成功以后,就不会执行后续代码,即后续的语句不会执行

1、单进程

如果是单进程调用了exec函数,那么exec函数之后的代码都不会被执行。

2、父子进程

如果是父子进程中,让子进程调用了 exec 函数,那么子进程就会转而去执行 exec函数指向的程序,但是父进程不会受到影响。

三、进程程序替换原理

一个完整的进程 = 进程控制块(task_struct) + 虚拟内存 + 页表 + 物理内存中的代码和数据

由这个图可以知道,进程程序替换,替换的是物理内存中的代码段数据段的内容

现在假设磁盘上有一段程序B

我们要用程序B的代码和数据去替换上面这个进程的代码和数据

修改的只是物理内存中的代码和数据,PCB、虚拟内存并没有受到影响,所以这里我们需要知道的是,进程程序替换不会创建新的进程!!!

### 使用 `exec` 函数替换当前进程映像为新程序Linux 中,`exec` 函数族用于将当前进程的用户空间代码和数据完全替换成新的程序。这并不是创建一个新的进程,而是用新程序替代现有进程的内容[^1]。 #### 基本概念 当调用了 `exec` 类函数之一时,当前进程中的原有代码、全局变量以及其他运行时的数据结构都会被清除并由指定的新程序所代替。然而,某些属性会保留下来,比如进程 ID 和已经打开的文件描述符(如果未设置相应的关闭标志)。这意味着即使更换了执行逻辑,依旧可以维持原有的资源连接状态[^2]。 #### 主要形式及其特点 常见的几种 `exec` 形式的声明如下: - **execl**: 参数列表方式传参; - **execv**: 数组指针方式传参; - **execle**: 同时支持参数列表和环境变量数组的方式传参; - **execve**: 支持显式提供路径名以及环境字符串向量的形式; - **execlp**, **execvp**: 自动查找 PATH 环境变量下的命令位置[^3]。 这些变体的主要区别在于传递给目标应用程序的参数格式不同,但最终效果都是相同的 —— 将现有的进程转换成另一个正在运行的应用实例。 #### 实际应用案例 下面给出一段简单的 C 语言代码片段演示如何利用 `fork()` 创建子进程,并通过 `exec` 来让这个子进程加载并启动 `/bin/ls` 工具显示目录内容: ```c #include <unistd.h> #include <stdio.h> int main(void){ int pid; /* Fork a child process */ pid = fork(); if (pid < 0){ // Error occurred fprintf(stderr, "Fork Failed"); return 1; } else if (pid == 0){ // Child Process char *args[]={"ls", "-l", "/tmp", NULL}; printf("Child will execute ls -l /tmp\n"); execvp(args[0], args); // Replace current image with 'ls' perror("Exec failed"); // Only reached on error return 1; // Should never reach here }else{ // Parent Process wait(NULL); puts("Parent done."); } return 0; } ``` 上述例子展示了怎样先生成一个子进程 (`fork`) ,接着在这个子进程中调用 `execvp` 方法来更改其行为模式去执行特定的任务(`ls`). 如果一切顺利的话,则原本应该继续往下走的子线程会被立即中断转而进入全新的上下文中工作[^4].
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值