用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec*函数以执行另一个程序。当进程调用一种exec*函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec*并不创建新进程,所以调用exec*前后该进程的id并未改变。
进程属于内核数据结构,所以要对进程做修改只能通过系统调用!
替换函数
其实有六种以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[]);
首先我们来演示execl:
拿execl为例子,如果想让我们的进程中执行ls -a -l 的命令与在命令行解释器上执行出的效果一样,该怎么使用这个函数呢
执行结果: 在我们写的可执行程序中也可以实现在命令行上执行出来的一样的效果。
注意:
- 从上面程序的执行结果可以看出,begin打印出来了,但是end并没有打印出来,说明程序一旦替换成功,exec*后续代码不在执行,因为被替换成新的代码了。
- exec* 只有失败返回,没有成功返回。
- 替换完成,不创建新的进程,我们可以通过查看前后两个进程的pid,发现没有改变,可以说明是一个进程。
- 创建一个进程,先创建pcb,地址空间,页表等,然后再把程序加载进来。
int execlp(const char *file, const char *arg, ...);
最后一个字母p:表示PATH 表示你不用告诉系统绝对或者相对路径,只要告诉系统名字就好。系统替换的时候,会自动去PATH中寻找。
同时命名中带“l” 的表示list 表示使用时要按照列表的形式传递,也就是一个一个的传,命名中带“v ” 的就是吧要执行的放置在一个指针数组中,然后直接把指针数组的地址传递过去。
我们我会发现前两个参数都是 “ls” 这重复吗?不重复!!!第一个表示怎么执行程序的名,后面的就是命令行参数怎么写那里就怎么写。这就比如生活中有两个人叫一样的名字,但不能说这两个是同一个人。
int execv(const char *path, char *const argv[]);
那么如何让自己写的程序调用自己的程序呢?
首先我们来写一个简单的c++文件。
如何让make 一下生成两个可执行程序呢?
首先定义一个为目标 all 表示 该命令都是被执行,但是没有依赖方法。
如果不这样写的话make 一下只能生成一个可执行程序,因为make 会寻找第一个目标文件 并且编译成为最终文件。
这样make一下会生成两个可执行程序。
用我们自己写的c程序调用c++程序。
为什么这样可以呢?不管任何语言写的代码,只要运行起来都是进程!!!!!
当程序执行起来,我们可以发现程序不仅仅能够替换,也能够拿到我们的命令行参数,并且也能够打印出来。
那么我们在子进程中替换我们的程序,看一看子进程是否会继承父进程的环境变量呢?
#include<iostream>
using namespace std;
#include <unistd.h>
int main(int agrc,char* argv[])
{
for(int i = 0;environ[i];i++)
{
printf("environ[%d]:%s\n",i,environ[i]);
}
cout << "hello c++"<<endl;
cout << "hello c++"<<endl;
cout << "hello c++"<<endl;
cout << "hello c++"<<endl;
cout << "hello c++"<<endl;
cout << "hello c++"<<endl;
cout << "hello c++"<<endl;
return 0;
}
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
printf("i am a process, pid:%d\n",getpid());
pid_t id = fork();
if(id == 0){
execl("./mytest","mytest","-a","-b","-c",NULL);
printf("############## end ################\n");
}
pid_t rid = waitpid(id,NULL,0);
if(id > 0)
{
printf("wait success:rid:%d\n",rid);
}
return 0;
}
通过以上的执行结果我们可以看出子进程确实可以继承父进程的环境变量。
默认可以通过进程地址空间继承的方式,让所有的子进程拿到环境变量。
进程程序替换不会替换环境变量的数据。
综上所诉:子进程默认能够继承父进程的环境变量。
如果要新增环境变量就要使用 putenv
putenv("MYVAL=bbbbbbbbbbbbbbbbbbbbbbbbbbbb");
如果单纯想要子进程继承新的环境变量呢?
int execle(const char *path, const char *arg, ...,char *const envp[]);
这时就要使用我们的程序调用函数了,首先我们需要自己定义一个环境变量表,然后使用程序替换的时候传进去。
执行结果如下,这样我们替换的程序就能够拿到我们自己定义的环境变量了