Linux下的exec函数

本文详细介绍了在Linux环境下如何利用fork和六种exec函数实现进程间程序的替换,包括execl、execlp、execv、execvp、execve和execle的具体用法及区别。

在进程间的程序替换
用fork创建子进程后执行的是和父进程相同的程序(但有可能执⾏行不同的代码分⽀支),
子进程往往要调用一种exec函数以执⾏行另⼀一个程序。
当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。
调用exec并后该进程的id并未改变。不创建新进程,所以调用exec前后该进程的id并未改变。因此在task_struct中的其他信息并没有发生改变

这里写图片描述

其实有六种以exec开头的函数,统称exec函数:

#include <unistd.h> 
int execl(const char *path, const char *arg, ...);

int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);

int execv(const char *path, char *const argv[]);

int execvp(const char *file, char *const argv[]);

int execve(const char *path, char *const argv[], char *const envp[]); 

1、使用execl:
带有字母l(表⽰示list)的exec函数要求将新程序的每个命令行参数都当作一个参数传给 它,命令行 参数的个数是可变的,因此函数原型中有…,…中的最后⼀一个可变参数应该是 NULL, 起sentinel的作用

#include<stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{

     pid_t id=fork();
     if(id==0)
     {
          printf("child start!\n");
          sleep(3);
          execl("/bin/ls","ls","-a","-l","-n","-i",NULL);
          printf("child end!\n");    
     }
     else
     {
          pid_t ret=wait(NULL);
          if(ret>0)
          {
               printf("wait child success!\n");
          }
     }
     return 0;
}

运行结果:
[admin@localhost linux8]$ ./wait
child start!
total 24
1054015 drwxrwxr-x. 2 500 500 4096 May 10 01:18 .
1053939 drwxrwxr-x. 7 500 500 4096 May 9 20:39 ..
1054019 -rw-rw-r–. 1 500 500 65 May 9 20:41 makefile
1054018 -rwxrwxr-x. 1 500 500 5224 May 10 01:18 wait
1054021 -rw-rw-r–. 1 500 500 2099 May 10 01:18 wait.c
wait child success!
在子进程中进行了程序的替换也就是ls程序的代码和数据替换了子进程的代码和数据

2、使用int execlp(const char *file, const char *arg, …);
不用加具体路径
不带字母p(表⽰示path)的exec函数 第⼀一个参数必须是程序的相对路径或绝对路径,例如 “/bin/ls”或”./a.out”,⽽而不能 是”ls”或”a.out”。对于带字母p的函数: 如果参数中包含/,则 将其视为路径名。 否则视为不带路径的程序名,在PATH环境变量的⽬目录列表中搜索这 个程序

int main()
{

     pid_t id=fork();
     if(id==0)
     {
          printf("child start!\n");
          sleep(3);
          execlp("ls","ls","-a","-l","-n","-i",NULL);
          printf("child end!\n");    
     }
     else
     {
          pid_t ret=wait(NULL);
          if(ret>0)
          {
               printf("wait child success!\n");
          }
     }
     return 0;
}

这里写图片描述

3、int execv(const char *path, char *const argv[]);

带有字母v(表⽰示vector)的函数,则应该先构造⼀一个指向各参数的指针数 组,然后将该数 组的⾸首地址当作参数传给它,数组中的最后⼀一个指针也应该是NULL,就像main函数 的 argv参数或者环境变量表⼀样。

int main()
{

     pid_t id=fork();
     if(id==0)
     {
          printf("child start!\n");
          sleep(3);
          char*_argv[]={"ls","-a","-l","-n","-i",NULL};
          execv("/bin/ls",_argv);
          printf("child end!\n");    
     }
     else
     {
          pid_t ret=wait(NULL);
          if(ret>0)
          {
               printf("wait child success!\n");
          }
     }
     return 0;
}

这里写图片描述

4、int execvp(const char *file, char *const argv[]);

带有字母v(表⽰示vector)的函数,则应该先构造⼀一个指向各参数的指针数 组,然后将该数 组的⾸首地址当作参数传给它,数组中的最后⼀一个指针也应该是NULL,就像main函数 的 argv参数或者环境变量表⼀一样。
不带字母p(表⽰示path)的exec函数 第⼀一个参数必须是程序的相对路径或绝对路径,例如 “/bin/ls”或”./a.out”,⽽而不能 是”ls”或”a.out”。对于带字母p的函数: 如果参数中包含/,则 将其视为路径名。 否则视为不带路径的程序名,在PATH环境变量的⽬目录列表中搜索这 个程序。

int main()
{

     pid_t id=fork();
     if(id==0)
     {
          printf("child start!\n");
          sleep(3);
          char*_argv[]={"ls","-a","-l","-n","-i",NULL};
          execvp("ls",_argv);
          printf("child end!\n");    
     }
     else
     {
          pid_t ret=wait(NULL);
          if(ret>0)
          {
               printf("wait child success!\n");
          }
     }
     return 0;
}

这里写图片描述

5、int execle(const char *path, const char *arg, …, char *const envp[]);

对于以e(表⽰示environment)结尾的exec函数,可以把一份新的环境变量表传给它,其他 exec函数 仍使⽤用当前的环境变量表执行新程序

#include<stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main()
{
   pid_t id=fork();
   if(id==0)
   {
        printf("child start\n");
        char* envTable[]={"MYENV=/hello/word/aa/bb/cc/xx/yy",NULL};
       execle("./myenv","myenv",NULL,envTable);
   }
    else
     {
     pid_t ret=wait(NULL);
     printf("father end \n");
}
return 0;
}

#include<stdio.h>
int main()
{
     printf("MYENV is %s\n",getenv("MYENV"));
     return 0;
}

这里写图片描述
这里写图片描述

这里写图片描述
这里写图片描述

<think> 我们正在讨论Linux系统中的exec函数族。根据用户提供的引用和问题,我们需要详细解释exec函数族的功能、使用方法以及相关注意事项。 注意:用户之前询问了fork函数的返回值,现在转向exec函数,因此我们需要在回答中保持连贯性,可以适当提及fork与exec的协同使用,但重点在exec。 引用说明: 引用[1]提到:当进程调用exec函数时,该进程被完全替换为新程序,进程ID不变。 引用[2]给出了execv函数的例子:使用参数数组传递命令行参数。 我们需要涵盖以下内容: 1. exec函数族的作用:替换当前进程的映像(即运行新的程序)。 2. 为什么使用exec:通常在fork之后,在子进程中使用exec来执行另一个程序,这样父子进程可以执行不同的程序。 3. exec函数族的多个变体(如execl, execv, execle, execvp等)及其区别(参数传递方式、环境变量处理等)。 4. 函数原型和参数说明(重点几个)。 5. 注意事项:调用exec后,原进程的代码、数据、堆栈等都被新程序替换,只有进程ID不变;如果exec调用失败,会返回-1,并设置errno。 我们按照常见的exec函数分别介绍: 1. execl:参数以可变参数形式传递,最后一个参数必须是NULL。 函数原型:int execl(const char *path, const char *arg0, ..., (char *)NULL); 示例:execl("/bin/ls", "ls", "-l", NULL); 2. execv:参数以字符指针数组传递,数组最后一个元素必须是NULL。 函数原型:int execv(const char *path, char *const argv[]); 示例:char *args[] = {"ls", "-l", NULL}; execv("/bin/ls", args); 3. execvp:类似于execv,但是会在PATH环境变量指定的目录中搜索可执行文件。 函数原型:int execvp(const char *file, char *const argv[]); 示例:char *args[] = {"ls", "-l", NULL}; execvp("ls", args); // 不需要写全路径 4. execle:可以指定环境变量,环境变量通过一个字符指针数组传递,数组最后一个元素为NULL。 函数原型:int execle(const char *path, const char *arg0, ..., (char *)NULL, char *const envp[]); 示例:char *env[] = {"PATH=/bin", NULL}; execle("/bin/ls", "ls", "-l", NULL, env); 5. execlp:类似于execl,但是会搜索PATH。 函数原型:int execlp(const char *file, const char *arg0, ..., (char *)NULL); 示例:execlp("ls", "ls", "-l", NULL); 6. execvpe:GNU扩展,类似于execvp,但可以指定环境变量(非标准,可能不在所有系统都存在)。 注意:所有exec函数在成功时不会返回,因为原进程的映像已经被替换。只有失败时才会返回-1,因此我们通常在调用exec后检查错误并处理。 一个典型的使用模式是fork之后在子进程中调用exec: ```c #include <stdio.h> #include <unistd.h> #include <sys/wait.h> int main() { pid_t pid = fork(); if (pid == -1) { perror("fork失败"); return 1; } else if (pid == 0) { // 子进程 execlp("ls", "ls", "-l", NULL); // 如果execlp返回,说明调用失败 perror("execlp失败"); return 1; } else { // 父进程 wait(NULL); // 等待子进程结束 printf("子进程执行完毕\n"); } return 0; } ``` 在这个例子中,子进程通过execlp执行了`ls -l`命令,而父进程则等待子进程结束。 总结: - exec函数族用于替换当前进程的映像,执行新程序。 - 有多个变体,主要区别在参数传递方式、是否搜索PATH以及环境变量的处理。 - 通常与fork配合使用,实现父子进程执行不同程序。 最后,生成相关问题。 </think>### Linux系统中exec函数族详解 `exec`函数族是Linux进程管理的核心工具之一,主要用于**替换当前进程的代码段和数据段**,加载并执行新程序。它们与`fork()`协同工作,实现经典的多进程编程模式:`fork()`创建子进程,`exec()`加载新程序。以下是详细解析: --- #### 1. **核心功能与原理** - **进程映像替换**: 调用`exec`后,**当前进程的代码、数据、堆栈被新程序完全覆盖**,但进程ID(PID)、文件描述符、信号处理等属性保留不变[^1]。 - 例如:进程A调用`exec("ls")`后,A的代码变为`ls`的代码,但PID不变。 - **无新进程创建**: 与`fork()`不同,`exec`**不创建新进程**,仅替换当前进程的映像[^1]。 - **调用后行为**: 成功时**不返回**(原程序代码已被覆盖),失败时返回`-1`并设置`errno`(如`ENOENT`文件不存在)。 --- #### 2. **exec函数族的六种变体** 所有`exec`函数均以`exec`开头,后缀指示**参数传递方式**和**环境变量处理**: | 函数原型 | 参数传递方式 | 环境变量继承 | 示例 | |----------|--------------|--------------|------| | `int execl(const char *path, const char *arg0, ..., NULL)` | **列表形式**(可变参数) | 继承父进程环境 | `execl("/bin/ls", "ls", "-l", NULL)` | | `int execv(const char *path, char *const argv[])` | **数组形式** | 继承父进程环境 | `char *args[] = {"ls", "-l", NULL}; execv("/bin/ls", args)`[^2] | | `int execle(const char *path, const char *arg0, ..., NULL, char *const envp[])` | 列表形式 | **自定义环境变量** | `char *env[] = {"PATH=/usr", NULL}; execle("/bin/ls", "ls", NULL, env)` | | `int execve(const char *path, char *const argv[], char *const envp[])` | 数组形式 | 自定义环境变量 | `execve("/bin/ls", args, env)` | | `int execlp(const char *file, const char *arg0, ..., NULL)` | 列表形式 | 继承父进程环境,**搜索`PATH`** | `execlp("ls", "ls", "-l", NULL)`(无需完整路径) | | `int execvp(const char *file, char *const argv[])` | 数组形式 | 继承父进程环境,搜索`PATH` | `execvp("ls", args)` | > **关键区别**: > - **`l` vs `v`**:列表传参(`l`)需显式列出所有参数并以`NULL`结尾;数组传参(`v`)使用指针数组。 > - **`p`后缀**:自动在`PATH`环境变量中搜索可执行文件,无需完整路径。 > - **`e`后缀**:可自定义环境变量数组(`envp`),否则继承父进程环境。 --- #### 3. **典型使用模式:fork + exec** ```c #include <unistd.h> #include <sys/wait.h> int main() { pid_t pid = fork(); // 创建子进程 if (pid == 0) { // 子进程 execlp("ls", "ls", "-l", NULL); // 替换为ls命令 perror("exec失败"); // 若exec返回,说明出错 _exit(1); // 终止子进程 } else { // 父进程 wait(NULL); // 等待子进程结束 printf("子进程执行完毕\n"); } return 0; } ``` - **执行流程**: 1. `fork()`创建子进程(与父进程相同映像)。 2. 子进程调用`execlp()`加载`ls`程序,原代码被覆盖。 3. 父进程通过`wait()`回收子进程资源。 --- #### 4. **关键注意事项** 1. **参数规范**: - 第一个参数通常是程序名(`argv[0]`),最后一个参数必须是`NULL`。 - 环境变量数组`envp`需以`NULL`结尾。 2. **错误处理**: - 常见错误:`ENOENT`(文件不存在)、`EACCES`(无执行权限)。 - 必须检查返回值(失败时返回`-1`)。 3. **资源继承**: - 子进程继承父进程的**打开的文件描述符**(除非设置`FD_CLOEXEC`)。 - 信号处理函数可能被重置(取决于新程序实现)。 --- #### 5. **应用场景** - **Shell实现**:如`bash`解析命令后,`fork()`+`exec()`执行程序。 - **守护进程**:通过`exec`重新加载配置后的程序。 - **安全沙盒**:在受限环境中执行不可信程序(结合`chroot`/`seccomp`)。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值