目录
一.exec函数族
1.进程体替换
使用函数fork()创建新的子进程后,子进程往往需要调用函数exec()以执行另一个程序。当进程调用函数exec()时,该进程执行的程序完全替换为新程序,而新程序则从其函数main()开始执行。
与 fork 或 vfork 函数不同,exec 函数不是创建调用进程的子进程,而是创建一个新的进程取代调用进程自身。新进程会用自己的全部地址空间,覆盖调用进程的地址空间,但进程的 PID 保持不变。exec 只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段和栈段。
我们可以简单的理解为,每一个新进程的产生都要经过 fork 和 exec两步,fork用来产生一个新进程,而exec就是将这个由fork产生的进程替换成我们所需要的进程。
exec系列函数由一组相关的函数组成,它们在进程的启动方式和程序参数的表达方式上各有不同。
2.exec函数族
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...,(char *)0);
int execlp(const char *file, const char *arg, ...,(char *)0);
int execle(const char *path, const char *arg, ...,(char *)0, 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[]);
int fexecve(int fd, char *const argv[], char *const envp[]);
// 7 个函数返回值:若出错,返回 -1;若成功,不返回
exec函数族的作用就是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。这里的可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。新进程由path或file参数指定。特别注意,file指的是具体的文件,而path指的是路径+具体的文件。
这些函数可以分为两大类。execl、exclp和execle的参数个数是可变的,参数以一个空指针结束。execv和execvp的第二个参数是一个字符串数组。不管是哪种情况,新程序在启动时会把在argv数组中给定的参数传递给main函数。
这些函数通常都是用execve实现的
以字母p结尾的函数通过搜索PATH环境变量来查找新程序的可执行文件的路径。如果可执行文件不在PATH定义的路径中,我们就需要把包括目录在内的使用绝对路径的文件名作为参数传递给函数。全局变量environ可用来把一个值传递到新的程序环境中。此外,函数execle和execve可以通过参数envp传递字符串数组作为新程序的环境变量。
例如:
a.c (例1)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
int main(int argc,char* argv[],char* envp[])
{
printf("pid = %d\n",getpid());
char *myargv[10]={
"ps","-f"
};
execl("/bin/ps","ps","-f",(char*)0);
exit(0);
}
执行结果:
当我们给出一个不存在的路径时:
b.c (例2)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
int main(int argc,char* argv[],char *envp[])
{
printf("pid = %d\n",getpid());
char *myargv[10] = {
"ps","-f"
};
int res = execvp("qwer",myargv);
if(res == -1)
{
perror("execl error");
}
exit(0);
}
执行结果:
c.c (例3)
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
int main(int argc, char* argv[], char *envp[])
{
printf("pid = %d\n", getpid());
execlp("ps", "ps", "-f", 0);
printf("Done.\n");
exit(0);
}
执行结果:
我们发现 ps -f 正常输出,但字符串 Done. 并没有输出,
因为程序先打印出它的第一条信息,接着调用execlp,这个函数在PATH环境变量给出的目录中搜索程序ps。然后用这个程序替换exec程序,就好像直接使用如下所示的shell命令一样:
ps -f
ps命令结束时,我们看到一个新的Shell提示符,因为我们并没有再返回到exec程序中,所以第二条信息 Done. 是不会打印出来的。新进程的PID、PPID和原先的完全一样。
main()函数参数的解析与使用
一般来说,我们写main函数,就定义main(),它的参数部分看似没有,实际上,main函数也是有参数的,只是在我们写时省略了。
main函数的参数由三部分组成,分别是
① argc (int型,表示命令行参数的个数)
② argv[](char* 数组型,指向命令行的每一个命令参数)
③ envp[] (char* 数组型,指向环境变量)
注意:
- argc包括程序本身,所以它大于等于1。
- argv以NULL结尾,且也包括程序本身,所以它的元素个数大于等于2。
d.c (例4)
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
int main(int argc, char* argv[], char *envp[])
{
printf("d.c -> pid = %d\n",getpid());
int i;
for(i = 0; i < argc; i++)
{
printf("argv[%d] = %s\n",i, argv[i]);
}
}
test.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
int main(int argc, char* argv[], char *envp[])
{
printf("test.c - > pid = %d\n", getpid());
execl("./d", "Hello", "World", "Linux", (char *)0);
exit(0);
}
执行结果:
我们看到,test3.c 打印出的pid值与 b.c 所打印出的pid值是相同的,即 ./test3 进程运行到execl函数处,使用 b.c 的运行进程 ./b 替换了当前进程 ./test3,并且新进程的PID和原先的完全一样。