进程创建fork()和vfork()

一个现有的进程可以通过两种方式创建一个新的进程,下面详细介绍两种 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 函数会保证子进程先运行,直到子进程调用 exitexec 函数后,父进程才会被调度执行,此间,父进程一直被挂起等待子进程的运行结束。如下代码:

#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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值