一、exec函数族接口。
1、什么是exec函数族?
exec函数是一系列的函数接口,然后这些接口的作用就是让一个程序替换(覆盖)掉原来的子进程的。
2、exec函数族接口有哪些?
功能: execl, execlp, execle, execv, execvp, execvpe - execute a file
//执行一些文件。
头文件:#include <unistd.h>
原型:
int execl(const char *path, const char *arg, .../* (char *) NULL */);
int execlp(const char *file, const char *arg, .../* (char *) NULL */);
int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
参数:
path: 需要执行的那个程序的绝对路径 /home/gec/project
arg:以","分开所有的参数,以NULL作为结束标识
file: 文件名 project
envp: 环境变量
argv: 参数的数组
返回值:
成功:一直执行那个文件。
失败:只有发生错误时,才会返回-1。
3、示例: 尝试产生一个子进程,然后子进程执行"ls -l"这个程序。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
//int execvpe(const char *file, char *const argv[],char *const envp[]);
int main(int argc,char *argv[])
{
//1. 现在还是一个单进程的程序。
//2. 在单进程的状态下,去创建一个新的子进程。
pid_t x;
x = fork();
//3. 通过判断返回值。
if(x > 0)
{
//4. 父进程打印helloworld,就主动回收资源,再退出。
printf("helloworld!\n");
wait(NULL);
exit(0);
}
if(x == 0)
{
printf("child 1\n");
//5. 子进程使用exec函数族的接口去执行"ls -l"这个程序。
//execl("/bin/ls","ls","-l",NULL); //为什么这里能执行ls,因为在/bin下找到ls -> 99%
//execlp("ls","ls","-l",NULL); //为什么这里能执行ls,因为ls是在环境变量/bin下
//execle("/bin/ls","ls","-l",NULL,NULL);
char *arg[3] = {"ls","-l",NULL};
//execv("/bin/ls",arg);
//execvp("ls",arg);
//execvpe("ls",arg,NULL);
printf("child 2!\n");
exit(0);
}
return 0;
}
4、 结论。
1)以上6个函数功能类似的,记住一个就可以了。
2)exec函数族功能替换(覆盖)掉一个进程,所以在exec函数之后的代码都变成无效的。
3)替换前后,子进程的PID号不会改变。
二、如何确保子进程先运行? -> vfork() -> man 2 vfork
功能: vfork - create a child process and block parent
//创建一个子进程并且让父进程阻塞。
头文件:#include <sys/types.h>
#include <unistd.h>
原型:
pid_t vfork(void);
参数:无
返回值:
成功: 产生新子进程 父进程 子进程
子进程的ID号 0
失败: 没有产生新的子进程 父进程
-1
注意:
1)fork()虽然父子随机先后,但是同时开始执行。
2)vfork()产生了孩子之后,孩子就会正常执行,但是父进程就会阻塞。
父进程阻塞到
1)孩子调用exit()
2)孩子调用exec()函数族
3)孩子调用exit(),父进程调用exit()。
4)孩子不调用exit(),父进程调用exit(),子进程执行结束后,虽然没有exit(),但是父进程中有,也能解锁父进程。
为止。
3)父子进程都不调用exit()就会出错。
Aborted (core dumped)
三、分析父子进程在内存中资源问题。
1、研究fork()的资源问题。
例子1:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
//1. 在fork之前,我定义一个变量。
int a = 100;
//2. 现在是单进程的程序。
//3. 在单进程的程序,创建了一个新的子进程。
pid_t x;
x = fork();
//4. 通过返回值判断
if(x > 0)
{
printf("parent &a = %p\n",&a); //0x0011
printf("parent a = %d\n",a); //100
wait(NULL);
exit(0);
}
if(x == 0)
{
printf("child &a = %p\n",&a); //0x0011
printf("child a = %d\n",a); //100
exit(0);
}
return 0;
}
结果:
parent &a = 0x7fffce337c90
parent a = 100
child &a = 0x7fffce337c90
child a = 100
结论:
为什么小孩能够使用a,因为fork()之前的资源,都会给小孩拷贝一份。
例子2:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
//1. 在fork之前,我定义一个变量。
int a = 100;
//2. 现在是单进程的程序。
//3. 在单进程的程序,创建了一个新的子进程。
pid_t x;
x = fork();
//4. 通过返回值判断
if(x > 0)
{
a = 50;
//printf("parent &a = %p\n",&a); //0x0011
printf("parent a = %d\n",a); //50
wait(NULL);
exit(0);
}
if(x == 0)
{
//printf("child &a = %p\n",&a); //0x0011
printf("child a = %d\n",a); //100
exit(0);
}
return 0;
}
结果:
parent a = 50
child a = 100
结论: 父进程与子进程拥有独立的空间,父亲的a不会影响小孩的a
例子3:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
//1. 在fork之前,我定义一个变量。
//int a = 100;
//2. 现在是单进程的程序。
//3. 在单进程的程序,创建了一个新的子进程。
pid_t x;
x = fork();
//4. 通过返回值判断
if(x > 0)
{
int a = 100;
//a = 50;
//printf("parent &a = %p\n",&a); //0x0011
printf("parent a = %d\n",a);
wait(NULL);
exit(0);
}
if(x == 0)
{
//printf("child &a = %p\n",&a); //0x0011
printf("child a = %d\n",a);
exit(0);
}
return 0;
}
结果:编译不通过。
原因:子进程的a没有声明。
例子4:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
//1. 在fork之前,我定义一个变量。
//int a = 100;
//2. 现在是单进程的程序。
//3. 在单进程的程序,创建了一个新的子进程。
pid_t x;
x = fork();
//4. 通过返回值判断
if(x > 0)
{
int a = 100;
//a = 50;
//printf("parent &a = %p\n",&a); //0x0011
printf("parent a = %d\n",a); //100
wait(NULL);
exit(0);
}
if(x == 0)
{
int a = 50;
//printf("child &a = %p\n",&a); //0x0011
printf("child a = %d\n",a); //50
exit(0);
}
return 0;
}
结果:
parent a = 100
child a = 50
==========================================================
综上所述:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
//政府有规定,只要生了一个小孩,本来的父亲有多少财产,政府就会补贴一模一样的数据给这个小孩。
//1. 现在还是只有1个父亲,结果父亲中奖,中了100万。
int a = 100;
//2. 这时候父亲带着这个100万,去生了一个小孩。
pid_t x;
x = fork();
if(x > 0)
{
//2.5 父亲生完小孩之后,偷偷去买了一套200平方的房子(fork之后的资源,不会继承给孩子)
int b = 200;
//3. 父亲查看一下自己的财产,看看有多少钱。
printf("parent a = %d\n",a); //100
//6. 看到自己有钱了,赶紧去澳门,输了50万。
a -= 50;
//7. 父亲再次查看自己的财产,看看有多少钱。
printf("parent a = %d\n",a); //50
//8. 父亲去看看自己的房子,去休息一下。
printf("parent b = %d\n",b); //200
b = 300;
}
if(x == 0)
{
//4. 孩子查看一下自己的财产,看看有多少钱。
printf("child a = %d\n",a); //100
//5. 小孩有点累,想睡眠一下
sleep(3);
//8. 这时候小孩醒了之后,发现父亲去赌博输钱,赶紧查看自己的财产:
printf("child a = %d\n",a); //100 心里想着父亲去赌博输钱了,关我屁事 表面父子
//9. 孩子也想去父亲的新房看看。
printf("child b = %d\n",b); //父亲说,这是我的房子,关你屁事,还要把你告上法庭(编译出错)
}
return 0;
}
2、研究vfork()资源问题。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
//fork(): 父子进程的资源分开的。
//vfork(): 父子进程的资源共享的。
int a = 0;
pid_t x;
x = vfork();
if(x > 0)
{
int b;
a++;
printf("parent a = %d\n",a);//3
printf("parent b = %d\n",b);//0 -> vfork()之后的资源b,不会与子进程的资源b共享。
wait(NULL);
}
if(x == 0)
{
int b = 200;
a+=2;
printf("child a = %d\n",a); //2
printf("child b = %d\n",b); //200
exit(0);
}
return 0;
}
结果:
child a = 2
child b = 200
parent a = 3
parent b = 0
总结fork()与vfork()异同:
1)fork()与vfork()都是可以创建一个新的子进程。
2)fork()与vfork()函数返回值都是一样的。
3)在fork之前的所有资源,在fork()之后,都会拷贝一份给子进程,父子进程拥有独立的空间。
所以修改了父进程的资源,子进程的资源不会修改。
修改了子进程的资源,父进程的资源不会修改。
在父进程中定义的变量,在子进程中不可以使用。
在子进程中定义的变量,在父进程中不可以使用。
4)在vfork()之前的所有资源,在vfork()之后,可以共享给子进程。
所以修改了父进程的资源,子进程的资源跟着修改。
修改了子进程的资源,父进程的资源跟着修改。
在父进程中定义的变量,在子进程中不可以使用。
在子进程中定义的变量,在父进程中不可以使用。
5)fork()随机先后运行。
vfork()确保子进程先运行,子进程中调用exit()/exec()就会导致父进程解锁。
fork() vfork()
分裂之前的资源 分开 共享
分裂之后的资源 分开 分开