1. exec 函数族概述
exec 函数族是 Linux/Unix 系统中用于进程控制的核心函数之一,其主要功能是用指定的程序替换当前进程的内存映像。
1.1 核心概念
- 进程替换:调用 exec 函数后,当前进程的代码段、数据段、堆栈等都被新程序完全替换
- 进程标识保留:进程 ID(PID)、父进程 ID、优先级等属性保持不变
- 文件描述符:默认情况下,打开的文件描述符会被继承(除非设置了 FD_CLOEXEC 标志)
1.2 典型应用场景
- Shell 命令执行:当你在终端输入
ls时,Shell 会 fork 子进程,然后 execls程序 - 服务器进程管理:守护进程 fork 子进程处理请求,子进程 exec 具体的服务程序
- 程序加载器:动态加载并执行其他可执行文件
2. exec 函数族分类
exec 函数族包含 6 个主要函数,它们都声明在<unistd.h>头文件中:
#include<unistd.h>intexecl(constchar*path,constchar*arg,...);intexeclp(constchar*file,constchar*arg,...);intexecle(constchar*path,constchar*arg,...,char*constenvp[]);intexecv(constchar*path,char*constargv[]);intexecvp(constchar*file,char*constargv[]);intexecve(constchar*path,char*constargv[],char*constenvp[]);2.1 命名规则解析
- l(list):参数以可变参数列表形式传递,最后一个参数必须是
NULL - v(vector):参数以字符串数组
argv[]形式传递 - p(path):在
PATH环境变量指定的目录中搜索可执行文件 - e(environment):可以指定自定义的环境变量数组
2.2 返回值说明
- 成功:不返回(进程已被新程序替换)
- 失败:返回 -1,并设置
errno,原进程继续执行
3. execl 和 execlp 函数
3.1 函数原型
intexecl(constchar*path,constchar*arg,...);intexeclp(constchar*file,constchar*arg,...);3.2 参数说明
- path(execl):可执行文件的完整路径
- file(execlp):可执行文件名,系统会在
PATH环境变量中查找 - arg…:可变参数列表,第一个参数通常是程序名,最后必须以
NULL结束
3.3 使用示例:执行 ls 命令
#include<stdio.h>#include<unistd.h>#include<errno.h>intmain(){// 使用 execl - 需要指定完整路径printf("使用 execl 执行 ls 命令...\n");if(execl("/bin/ls","ls","-l","-a","/etc",NULL)==-1){perror("execl 执行失败");return1;}// 这行代码不会被执行,除非 execl 失败printf("这行不会输出\n");return0;}#include<stdio.h>#include<unistd.h>intmain(){// 使用 execlp - 自动在 PATH 中查找printf("使用 execlp 执行 ls 命令...\n");if(execlp("ls","ls","-l","-a","/etc",NULL)==-1){perror("execlp 执行失败");return1;}return0;}3.4 区别对比
| 特性 | execl | execlp |
|---|---|---|
| 路径查找 | 必须提供完整路径 | 自动在 PATH 中查找 |
| 使用便捷性 | 较低,需知道路径 | 较高,类似命令行 |
| 安全性 | 较高,路径明确 | 较低,可能受 PATH 篡改影响 |
4. execv 和 execvp 函数
4.1 函数原型
intexecv(constchar*path,char*constargv[]);intexecvp(constchar*file,char*constargv[]);4.2 参数说明
- argv[]:字符串指针数组,包含所有参数
- argv[0]:通常是程序名
- 最后一个元素:必须是
NULL指针
4.3 使用示例
#include<stdio.h>#include<unistd.h>intmain(){// 准备参数数组char*args[]={"ls","-l","-a","/etc",NULL};// 使用 execv - 需要完整路径printf("使用 execv 执行 ls 命令...\n");if(execv("/bin/ls",args)==-1){perror("execv 执行失败");return1;}return0;}#include<stdio.h>#include<unistd.h>intmain(){// 准备参数数组char*args[]={"ls","-l","-a","/etc",NULL};// 使用 execvp - 自动查找printf("使用 execvp 执行 ls 命令...\n");if(execvp("ls",args)==-1){perror("execvp 执行失败");return1;}return0;}4.4 适用场景
- 动态参数:当参数数量或内容在运行时确定时
- 封装调用:便于将参数封装为数组传递
- 批量处理:适合需要处理多个相似命令的情况
5. execle 和 execve 函数
5.1 函数原型
intexecle(constchar*path,constchar*arg,...,char*constenvp[]);intexecve(constchar*path,char*constargv[],char*constenvp[]);5.2 环境变量控制
#include<stdio.h>#include<unistd.h>intmain(){char*env[]={"PATH=/usr/local/bin:/usr/bin:/bin","HOME=/home/user","MYVAR=custom_value",NULL};char*args[]={"env",NULL};// 使用自定义环境变量执行 env 命令if(execve("/usr/bin/env",args,env)==-1){perror("execve 执行失败");return1;}return0;}6. system 函数
6.1 函数原型
#include<stdlib.h>intsystem(constchar*command);6.2 工作原理
- 调用
fork()创建子进程 - 子进程中调用
execl("/bin/sh", "sh", "-c", command, NULL) - 父进程调用
wait()等待子进程结束
6.3 使用示例
#include<stdio.h>#include<stdlib.h>intmain(){printf("使用 system 执行命令...\n");intret=system("ls -la /etc | head -5");if(ret==-1){perror("system 调用失败");}elseif(WIFEXITED(ret)){printf("命令退出状态: %d\n",WEXITSTATUS(ret));}return0;}6.4 与 exec 的区别
| 特性 | exec 函数族 | system 函数 |
|---|---|---|
| 进程替换 | 是,当前进程被替换 | 否,创建新进程执行 |
| 控制权 | 不返回(除非失败) | 返回命令执行状态 |
| Shell 解析 | 无,直接执行程序 | 有,通过/bin/sh -c |
| 安全性 | 较高,无 Shell 注入风险 | 较低,可能受 Shell 特性影响 |
| 使用便捷性 | 较低,需处理参数 | 较高,类似命令行 |
7. 实战:fork + exec 模式
7.1 典型模式
#include<stdio.h>#include<unistd.h>#include<sys/wait.h>#include<stdlib.h>intmain(){pid_tpid=fork();if(pid<0){perror("fork 失败");exit(1);}elseif(pid==0){// 子进程:执行 ls 命令printf("子进程 PID: %d\n",getpid());char*args[]={"ls","-l","/tmp",NULL};if(execvp("ls",args)==-1){perror("execvp 失败");exit(1);}}else{// 父进程:等待子进程结束printf("父进程 PID: %d,创建子进程 PID: %d\n",getpid(),pid);intstatus;waitpid(pid,&status,0);if(WIFEXITED(status)){printf("子进程正常退出,状态码: %d\n",WEXITSTATUS(status));}}return0;}