进程创建是否只依赖与fork()和execve()?
-
fork()通过完整复制当前进程的方式创建新进程。
-
execve()根据参数覆盖进程数据(一个不留)。会重置内存空间,通过fork()辛辛苦苦复制的进程空间没有了。
vfork
NAME
vfork - create a child process and block parent
SYNOPSIS
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);
-
vfork()用于创建子进程,然而不会复制父进程空间中的数据。(为了效率考虑,不会做无畏的拷贝操作)
-
vfork()创建的子进程直接使用父进程空间(没有完整独立的进程空间)。
-
vfork()创建的子进程对数据(变量)的修改会直接反馈到父进程中。
-
vfork()是为了execve()系统调用而设计。
下面来看一个demo。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
int main(int argc,char* argv[])
{
pid_t pid=0;
int var =88;
if((pid=vfork()) < 0)
{
printf("vfork error\n");
}
else if(pid == 0) //子进程
{
printf("pid=%d,var=%d\n",getpid(),var); //88
var++;
printf("pid=%d,var=%d\n",getpid(),var); //89
return 0; //子进程结束
}
printf("pid=%d,var=%d\n",getpid(),var); //由父进程打印
return 0;
}
首先,思考一下,23行会打印出来什么?23行到底打印88,还是89?
编译运行,来看一下结果:
wj@wj:~/WORK/Learning/DT/07$ ./test.out
pid=57553,var=88
pid=57553,var=89
pid=57552,var=1260087904
段错误 (核心已转储)
为什么会出现段错误???
vfork深度分析
-
vfork创建出来的进程没有独立的内存空间,和父进程的内存空间进行共享。
-
子进程会使用父进程的堆栈段,数据段,代码段等。
vfork要点分析
-
vfork()成功后,父进程将等待子进程结束。
-
子进程可以使用父进程的数据(堆、栈、全局数据区)。
-
子进程可以从创建点调用其他函数,但不要从创建点返回。
3.1 当子进程执行流回到创建点/需要结束时,使用 _exit(0)系统调用。
3.2 如果使用return 0,这个时候需要回收栈帧,那么将破坏栈结构,导致后续父进程执行出错,这也是上述那个demo第三次打印var,出现那么奇怪的值的原因了。
解决方案
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
int main(int argc,char* argv[])
{
pid_t pid=0;
int var =88;
if((pid=vfork()) < 0)
{
printf("vfork error\n");
}
else if(pid == 0) //子进程
{
printf("pid=%d,var=%d\n",getpid(),var);
var++;
printf("pid=%d,var=%d\n",getpid(),var);
//return 0; //子进程结束
_exit(0);
}
printf("pid=%d,var=%d\n",getpid(),var); //由父进程打印
return 0;
}
程序编译运行输出:
wj@wj:~/WORK/Learning/DT/07$ gcc test.c -o test.out
wj@wj:~/WORK/Learning/DT/07$ ./test.out
pid=61065,var=88
pid=61065,var=89
pid=61064,var=89 //输出符合预期,父进程并没有改变var的值;子进程改变了var的值,所以父进程输出89
fork与vfork的选择
-
为什么选用vfork?因为我们觉得fork的效率比较低下。
-
fork不容易产生问题,vfork陷阱点比较多
fork的现代优化
copy-on-Write技术
-
多个任务访问同一资源,如果只是读资源,是没有问题的。当然,如果是写资源,就可能发生问题。
-
在写入操作修改资源时,复制资源的原始副本。(类型延时绑定,把事情推迟到不得不做的时候才来做)。
fork引入copy-on-write之后,父子进程共享相同的进程空间
-
当父进程或者子进程的其中之一修改内存数据,则实时复制进程空间
-
fork() + execve() <-> vfork() + execve()
-
vfork()是一个替代品,不是万不得已,不得使用vfork()
///helloworld.c
#include <stdio.h>
int main(int argc,char* argv[])
{
printf("hello world\n");
return 0;
}
//test.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
/*
wait: true :代表父进程是否等待子进程结束
wait: false :代表父进程不等待子进程结束
*/
int create_process(char* path,char* const args[],char* const env[],int wait)
{
int ret = fork();
if(ret == 0)
{
if(execve(path,args,env) == -1)
{
exit(-1);
}
}
if(wait && ret) // 1.wait为true。2.并且父进程是否成功创建子进程?来共同解决父进程是否等待子进程结束?
{
waitpid(ret,&ret,0);
}
return ret; //1.如果等待,ret为子进程的退出状态;2.如果不等待,返回为子进程的pid
}
int main(int argc,char* argv[])
{
char* target = argv[1];
char* const ps_argv[] = {target,NULL};
char* const ps_envp[] = {"PATH=/bin:/usr/bin","TEST=Delphi",NULL};
int result =0;
if(argc < 2) exit(-1);
printf("current: %d\n",getpid());
result = create_process(target,ps_argv,ps_envp,0);
printf("result = %d\n",result);
return 0;
}
编译运行输出:
wj@wj:~/WORK/Learning/DT/07$ gcc helloworld.c -o helloworld.out
wj@wj:~/WORK/Learning/DT/07$ gcc test.c -o test.out
wj@wj:~/WORK/Learning/DT/07$ ./test.out helloworld.out
current: 68411
result = 68412
wj@wj:~/WORK/Learning/DT/07$ hello world
我们可以看到上述输出结果,hello world是之后打印输出的,先打印 result = 68412,所以,我们可以确认父进程是没有等待子进程的。
如果我们改为父进程等待子进程结束,如下代码所示:
result = create_process(target,ps_argv,ps_envp,1);
编译运行输出为:
wj@wj:~/WORK/Learning/DT/07$ gcc test.c -o test.out
wj@wj:~/WORK/Learning/DT/07$ ./test.out helloworld.out
current: 68568
hello world
result = 0