您的位置:首页 > 其它

进程创建fork()和vfork()

2018-03-10 16:04 134 查看
一个现有的进程可以通过两种方式创建一个新的进程,下面详细介绍两种
fork()
vfork()


fork()

函数原型( man 手册):

#include <unistd.h>
pid_t fork(void);


描述:

fork()
以当前的进程为副本创建一个新的进程,新创建的进程被称为子进程, 当前的进程被称为父进程,父进程和子进程运行在各自的地址空间。

返回值:

在父进程中返回子进程 的pid,子进程中返回0。可以这样理解,对于父进程来说它可以有多个子进程,所以父进程需要知道子进程的 pid , 而对于子进程来说,它只有一个父亲,而且其父进程 ppid 已经记录在它的
PCB
中了,所以就没必要再返回父进程 pid 了。而fork失败的话返回-1。

fork失败原因:

进程当前系统进程数量达到“上限”;

系统内存内存不足;

操作系统不支持 fork。

用法实例:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main()
{
pid_t ret = fork();

if(ret < 0){
perror("fork");
return 1;
}
else if(ret > 0){//father
sleep(3);
printf("father start!\n");
printf("pid : %d\n", getpid());
printf("ret(child’s pid) : %d\n", ret);
printf("father end!\n");
wait(NULL);
}else{//child
printf("child start!\n");
printf("pid: %d\n",  getpid());
printf("ppid: %d\n", getppid());
printf("ret: %d\n", ret);
printf("child end!\n");
}
printf("%d come here\n", getpid());

return 0;
}


运行结果



从代码中看,我们让父进程睡眠三秒(三秒并不保证子进程能执行完),以保证让子进程先执行,3 秒后父进程开始执行,打印结果如我们所料。

下面看看系统调用 fork 的原理。



在执行
fork
函数后,内核将拷贝一份父进程的副本作为子进程。子进程获得父进程的数据空间,堆和栈、文件描述符表(这通常在网络服务中使用,即父子进程各自关闭自己不需要的文件描述,这样就不会对对方造成影响)…的副本,但是父子进程共享正文段,即代码段。所以
fork
之后,父子进程仍然执行的是同一份代码,但我们根据返回值得不同,可以让它们执行不同的逻辑,如上图所示,在
fork
之后父子进程各自执行灰色部分代码,从上面执行结果中也可以看出 (注意!这只是让它们执行不同的逻辑,本质上执行的还是同一份代码)

一般情况下,被创建的子进程会调用进程替换函数
exec
,所以
fork
之后子进程一般就不去执行和父进程同样的代码。


父子进程除了共享代码段之外,它们也共享数据段。父子进程的数据段通过页表映射到同一块物理内存中,当有一方企图修改数据段时,便以写时拷贝的方式 —— 先拷贝一份要修改的数据,再通过页表映射到新拷贝的内存处,这样一方对数据的修改就不会影响另一方。如下实例:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>

int g_val = 0;

int main()
{
int i = 10;
printf("g_val: %d,i: %d\n", g_val, i);
pid_t ret = fork();

if(ret < 0){
perror("fork");
return 1;
}
else if(ret > 0){//father
sleep(3);
printf("father start!\n");
g_val = 1;
i = 11;
printf("g_val: %d,i: %d\n", g_val, i);
printf("father end!\n");
wait(NULL);
}else{//child
printf("child start!\n");
g_val = 2;
i = 12;
printf("g_val: %d,i: %d\n", g_val, i);
printf("child end!\n");
}
printf("%d come here\n", getpid());

return 0;
}


运行结果:



可以看到,父子进程对数据的修改并不会影响到对方。如下图:



vfork()

vfork
函数也用来创建一个进程,但不同于
fork
的是
vfork
出的子进程不会获得一份父进程的副本,而是直接在父进程的地址空间内运行,所以子进程对数据的修改会影响到父进程内。这一点将在下面验证。这么做的原因是:创建的子进程会立即调用
exec
进程函数替换,所以子进程不会使用父进程的地址空间。

除此之外,
vfork
函数会保证子进程先运行,直到子进程调用
exit
exec
函数后,父进程才会被调度执行,此间,父进程一直被挂起等待子进程的运行结束。如下代码:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>

int main()
{
int i = 0;
pid_t ret = vfork();

if(ret < 0){
perror("fork");
return 1;
}
else if(ret > 0){//father
printf("father start!\n");
printf("%p: %d\n", &i, i);
printf("father end\n");
}else{//child
printf("child start!\n");
++i;
printf("%p: %d\n", &i, i);
sleep(5);
printf("child end\n");
exit(0);
}

return 0;
}


让子进程睡眠
5
秒是为了验证子进程先执行的问题,而变量
i
则是为了验证子进程在父进程的地址空间内运行。运行结果如下:



可以看到,子进程运行完了,父进程才运行,而且子进程中对变量
i
的修改影响到了父进程。

——完!

【作者:果冻:http://blog.csdn.net/jelly_9
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  fork 进程