关于fork()的那些事

关于fork()的那些事?

fork()是什么?有什么用?

  • fork()是一个系统调用函数。
  • fork()的返回值是一个整形。并且不需要只能和参数。
  • 在Linux下,fork()包含在#include <unistd.h>这个头文件中,Linux中的大部分系统调用都在这个头文件里面。
  • fork()可以创建一个子进程,使父子进程可以继续执行fork()后的代码。
  • fork()是一次调用两次返回,对父进程返回子进程的pid,对子进程返回0。如果调用失败则返回负数。

我们来看一个代码说明这个问题:

#include <iostream>
#include <unistd.h>

int main(void)
{
    pid_t id = fork();

    if (id > 0)//parent
    {
        std::cout << "I am parent. My PID is : " << getpid() << std::endl;
    }
    else if (id == 0)//child
    {
        std::cout << "I am child. My PID is : " << getpid() << std::endl;
    }
    else//error
    {
        std::cout << "bad_fork" << std::endl;
    }

    return 0;
}

在这里插入图片描述

fork()之后就有了两个进程,继续执行fork()后面的代码。

fork()的运行规则

  • 以父进程为模板创建子进程。
  • 会把父进程的PCB拷贝一份,稍加修改称为自己的PCB。
  • 会把父进程的地址空间拷贝一份,成为子进程的地址空间。
  • fork()返回会在父子进程分别返回,在fork()后继续往下执行。
  • 父子进程的执行顺序没有先后顺序,全靠调度器调度。

fork()的写时拷贝

创建一个子进程,如果说直接拷贝父进程的PCB和地址空间,往往开销比较大,所以,fork()经过过年的演化,衍生出了一种写时拷贝的规则。就是说,fork()之后,会拷贝父进程的PCB,和地址空间的某些东西。像不改变的东西,就共用一份就行。比如说,代码一般不可改变,因为代码段是只读的(但还是有某种方法可以改变,不然外挂是怎么工作的),所以父子进程的地址空间里的代码段就指向内存中的同一份代码。只有某一放发生改变的时候,子进程才会拷贝一份代码自己用。数据也是一样的,只不过数据容易被改变,我们思想默认都是直接拷贝数据。

通常情况下,父子进程共用同一份代码,各自有一份数据。由于大部分内存空间可能被拷贝,创建进程开销仍然比较高。(和线程相比)

fork()调用失败的原因

我们先看看Linux的man手册是怎么说的:

在这里插入图片描述

就两点失败的原因:

  1. 系统的进程太多了,没有足够的资源来创建新的进程。
  2. 内存不够了。

fork()的兄弟—vfork()

vfork()fork()的作用是一样的,也是创建子进程。它跟fork()的不同在于:

  • vfork()保证了创建子进程后,子进程先运行。
  • vfork()创建子进程后,父子进程共享地址空间。

我们来对比一下:

先来看看fork()的效果:

int main(void)
{
    int a = 0;

    pid_t id = fork();
    //pid_t id = vfork();

    if (id > 0)//parent
    {
        std::cout << "I am parent. My pid is : " << getpid() << "  a : " << a << std::endl;
        sleep(2);
        std::cout << "I am parent. My pid is : " << getpid() << "  a : " << a << std::endl;
    }
    else if (id == 0)
    {
        sleep(1);
        a = 2;
        std::cout << "I am child. My pid is : " << getpid() << "  a : " << a << std::endl;
        exit(0);
    }
    else
    {
        std::cout << "bad_fork" << std::endl;
    }

    return 0;
}

在这里插入图片描述

子进程修改了变量a,无论父进程是在子进程前运行还是在子进程后运行,它都显示a == 0,所以我们推断出,父子进程发生写时拷贝后,各自有一份变量。

那么再来看看vfork()

int main(void)
{
    int a = 0;

    //pid_t id = fork();
    pid_t id = vfork();

    if (id > 0)//parent
    {
        std::cout << "I am parent. My pid is : " << getpid() << "  a : " << a << std::endl;
        sleep(2);
        std::cout << "I am parent. My pid is : " << getpid() << "  a : " << a << std::endl;
    }
    else if (id == 0)
    {
        sleep(1);
        a = 2;
        std::cout << "I am child. My pid is : " << getpid() << "  a : " << a << std::endl;
        exit(0);
    }
    else
    {
        std::cout << "bad_fork" << std::endl;
    }

    return 0;
}

在这里插入图片描述

首先就是,vfork()保证了子进程先运行,父进程等着子进程运行完了再运行。

然后,父子进程共享地址空间,子进程修改了变量a后,相当有父进程指向的变量也会改变。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值