对 fork 函数,我们知道是创建子进程的,那创建子进程干吗?一般有下面两种情况:
- 一个父进程希望复制自己,使父进程和子进程同时执行不同的代码段。
- 一个进程要执行不同的程序,在这种情况下,子进程从 fork 返回之后立即调用 exec 系列
进程替换
当一个进程调用一种 exec 函数时,该进程执行的程序完全替换为新程序,而新程序从其 main 函数开始执行。
调用 exec 并不创建新进程, 前后的进程 ID 并未改变, 只是用磁盘上的一个新程序替换当前进程的正文段、数据段、堆段和栈段。
exec 函数族
#include <unistd.h>
使用 exec 系列函数必须包含execl、execlp、execle
execv、execvp、execvpe成功没有返回值
失败返回 -1当指定 filename 作为参数时:
如果 filename 中包含 / ,则就将其视为路径名
否则就按 PATH 环境变量,在其所指定的各目录中搜寻可执行文件PATH 变量包含了一张目录表(称为路径前缀),目录之间用冒号(:)分隔。
在 bash 窗口可以使用 echo $PATH 打印PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/zbq/.local/bin:/home/zbq/binl : 可变参数列表
v : 数组–字符串指针数组
p : 环境变量的 PATH
e : 环境变量
在执行 exec 之后,进程 ID 没有改变,新程序从调用进程继承了下列属性:
- 进程 ID 和父进程 ID
- 实际用户 ID 和实际组 ID
- 附属组 ID
- 进程组 ID
- 会话 ID
- 控制终端
- 闹钟尚余留的时间
- 当前工作目录
- 根目录
- 文件模式创建屏蔽字
- 文件锁
- 进程信号屏蔽
- 未处理信号
- 资源限制
- nice 值
- tms_utime、tms_stime、tms_cutime 以及 tms_cstime 值
接下来我们一个个的看看
- execl 函数
(1) 函数原型: int execl(const char * path, const char * arg, …);
(2) 这里的 path 是可执行程序的路径 取路径名作为参数
(3) arg 是所要替换程序的参数
(4) 返回值: 成功没有返回值,失败返回 -1
(5) 我们写个程序测试一下
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
// ls 打印根目录下的文
int res = execl("/usr/bin/ls", "ls", "-l", "/", NULL);
if(res == -1)
{
perror("execl error");
exit(1);
}
printf("hahaha!\n");
return 0;
}
程序编译(编译环境 CentOS 7)执行结果:
并且我们发现,并没有打印出 “hahaha” 这个字符串,当然也没有出错 ?
我们在这里将程序稍加修改:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
// 只更改了这里
int res = execl("ls", "ls", "-l", "/", NULL);
if(res == -1)
{
perror("execl error");
exit(1);
}
printf("hahaha!\n");
return 0;
}
函数在 execl 处出错,返回值为 1, 没有这个文件的原因是我们没有把路径加上,他找不到这个程序,自然失败了!
2. execlp 函数
(1) 函数原型:
int execlp(const char * file, const char * arg, …);
(2) 这里 file 参数可以是一个 可执行程序名,并且可以不用指定全路径,它会自动在 PATH 中的一个找到该可执行文件,如果该文件不是由连接编译器产生的机器可执行文件,则就认为该文件是一个 shell 脚本,于是试着调用 /bin/sh, 并以该 filename 作为 shell 的输入, 取文件名作为参数
(3) arg 还是可执行程序的参数
(4) 返回值: 成功返回
(5) 我们写个简单的代码,看看执行结果
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
int res = execlp("ls", "ls", "-l", "/", NULL);
if(res == -1)
{
perror("execl error");
exit(1);
}
printf("hahaha!\n");
return 0;
}
这次即使没加全路径,也执行成功
- execle 函数
(1) 函数原型:
int execle(const char * path, const char * arg, …, char * const envp[]);
(2)这里的最后一个参数是一个自己构建的环境,是一个字符串指针数组,取路径名作为参数
(3) 写个小程序
// filename: process_exec.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
char *env[] = {
"AAAA=aaaa",
NULL
};
int res = execle("./exec_test", "./exec_test", NULL, env);
if(res == -1)
{
perror("execl error");
exit(1);
}
printf("hahaha!\n");
return 0;
}
// filename: test_exec.c
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("AAAA = %s\n", getenv("AAAA")/*需要包含 stdlib.h */);
return 0;
}
程序编译执行结果:
我们看到,已经把设置进去的环境变量获取到。
- execv 函数
(1) 函数原型:
int execv(const char * path, char * const argv[]);
(2) 这和 execl 函数一样,区别就是,execl 函数的第二个参数为可变参数列表,而 execv 函数的第二个参数为字符串指针数组。取路径名作为参数
(3) 写个小程序看看
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
char * arg[] = {
"ls",
"-l",
"/",
NULL
};
int res = execv("/usr/bin/ls", arg);
if(res == -1)
{
perror("execl error");
exit(1);
}
printf("hahaha!\n");
return 0;
}
5. execvp 函数
(1) 函数原型:
int execvp(const char * file, char * const argv[]);
(2) 与函数 execlp 一样,取文件名作为参数
(3) 看代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
char * arg[] = {
"ls",
"-l",
"/",
NULL
};
int res = execvp("ls", arg);
if(res == -1)
{
perror("execl error");
exit(1);
}
printf("hahaha!\n");
return 0;
}
程序执行结果:
- execve 函数
(1) 函数原型:
int execve(const char * file, char * const argv[], char * const envp[]);
(2) 与函数 execle 函数一样, 取路径名作为参数
(3) 看代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
char * arg[] = {
"./exec_test",
NULL
};
char * env[] = {
"AAAA=aaaa",
NULL
};
int res = execve("./exec_test", arg, env);
if(res == -1)
{
perror("execl error");
exit(1);
}
printf("hahaha!\n");
return 0;
}
程序执行结果:
以上就是 exec 函数族了
- fexecle 函数
这也是一个进程替换函数,依赖调用进程来完成这项工作。调用进程可以使用文件描述符验证所需要的文件并且无竞争地执行该文件。