linux系统编程:分叉函数fork && 父子进程


fork 基本用法

作用:通过系统调用创建一个与原来进程几乎完全相同的进程。系统为新的进程分配资源,将原来的进程的所有数据都复制到新的新进程中,除了某些细节有所不同,在某种意义上相当于克隆了一个自己。

我们来看一段代码,先简单了解一下fork函数的功能:

#include <iostream>
#include <unistd.h>
using namespace std;

int main(){
    int num = 0;
    pid_t id = fork();
    cout << getpid() <<",父进程为" << getppid() << endl;
}

以上还用到了两个函数:

  • getpid():获取进程ID
  • getppid():获取进程父进程ID

运行结果为:

在这里插入图片描述
显然,cout << getpid() <<",父进程为" << getppid() << endl;这条代码执行了两次,这是因为ID为7541的进程也就是我们上述代码生成的进程中由于fork分叉出了7542的子进程。该子进程中拥有父进程中所有数据,他们执行的代码完全相同。

fork 特点

1. 返回值

fork函数如果执行成功,就会生成一个新的进程,该进程的父进程为原进程。但是我们该如何区分这两个进程呢?这就要说到我们fork函数的返回值,该函数很特殊,它执行一次,返回值却有两个。在原进程也就是新生进程的父进程中,该函数返回值为新生进程ID,在新生进程中,该函数的返回值为0。所以我们可以通过区分fork函数的返回值区分当前正在处理的进程是父进程还是子进程。

除此以外,fork函数的返回值有三种值,具体如下表:

返回值含义
-1失败
0目前处于子进程逻辑控制流
其他(子进程PID)目前处于父进程逻辑控制流

执行下面的代码:

#include <iostream>
#include <unistd.h>
using namespace std;

int main(){
    int num = 0;
    pid_t id = fork();
    if(id == 0){  // 子进程
        cout <<"这是刚创建的子进程:" << getpid() <<",父进>程为" << getppid() << endl;
    }else{  // 父进程
        cout << "当前进程为父进程:" << getpid() << ",创建>的子进程为:"<< id << endl;
    }
}

代码执行结果如下:

在这里插入图片描述

2. 拥有相同且独立的虚拟空间

我们首先介绍一下程序的虚拟地址空间,每一个进程都会对应一个虚拟地址空间,在2位系统中的寻址能力为 2 32 2^{32} 232,也就是4G空间大小,在这种系统中将会默认可以提供4G的空间给程序,其中0-3G的空间存放该进程的数据、代码等信息,这一部分中各进程的用户空间是彼此独立的,对实际物理地址的映射是不同的。3-4G是内核空间,主要存放的是机器指令,操作系统内核的各个模块,这一部分是公用的,不同程序对实际物理地址的映射相同。

在这里插入图片描述

各变量所处的位置:

变量位置段名字
经过初始化的全局变量和静态变量.data数据段
未经初始化的全局变量和静态变量.bssBSS段
函数内部声明的局部变量stack栈区
const修饰的全局变量.text代码段
const修饰的局部变量stack栈区
字符串常量.text代码段

该部分测试代码如下:

#include <iostream>
#include <unistd.h>
using namespace std;

int main(){
    int num = 0;
    pid_t id = fork();
    if(id == 0){  // 子进程
        for(int i=0; i<=10; ++i)  cout << "子进程:"  << &num << ":" << ++num << endl;
    }else{  // 父进程
        for(int i=0; i<=10; ++i)  cout << "父进程:"  << &num << ":" << --num << endl;
    }
}

由于每个进程都有各自的虚拟空间,所以fork之后得到的新进程和原进程中各有一份相同的数据,甚至连地址都相同,但是这个相同的地址指的只是虚拟空间地址不是真实地址,上述代码运行结果如下,两个进程中的n不受对方程序改变的影响:

在这里插入图片描述

3. 并发执行

fork创建出的子进程与父进程的执行顺序是“同时”进行的,在这里我们由两个概念,并发并行

概念状态硬件特点
并发两个或者多个进程在同时存在单核进程指令同时或者交错执行
并行两个或者多个进程在同时执行多核一种特殊的并发

并行是很好理解的,我们每一个内核可以执行一个进程,并行就是多个处理器同时处理多个进程,它们的时间是连续的,没有资源的冲突。并发是指在一段时间内两个甚至多各程序同时执行,但是在某个时刻,该处理器只能处理一个进程,但是如果我们在一段时间中看待,这两个程序同时都在被处理。

我们用一段代码进行测试:

#include <iostream>
#include <unistd.h>
using namespace std;

int main(){
    int num = 0;
    pid_t id = fork();
    if(id == 0){  // 子进程
         for(;;)  cout <<"这是刚创建的子进程:" << getpid() <<",父进程>为" << getppid() << endl;
    }else{  // 父进程
         for(;;)  cout << "当前进程为父进程:" << getpid() << ",创建的>子进程为:"<< id << endl;
    }
}

在这里插入图片描述

4. 共享文件

我们知道fork后的子进程和父进程实际上的物理存储空间是独立的,两者的交互其实都是依托文件进行的,我们执行代码如下:

#include <iostream>
#include <unistd.h>
#include <fstream>
using namespace std;

int main(){
    // fork后可以使用文件进行交互
    ofstream ofs("./test");
    int num = 0;
    pid_t id = fork();
    if(id == 0){  // 子进程
        for(int i=0; i<=10; ++i)  ofs << "子进程:"  << &num << ":" << ++num << endl;
    }else{  // 父进程
        for(int i=0; i<=10; ++i)  ofs << "父进程:"  << &num << ":" << --num << endl;
    }
    ofs.close();
}

代码执行结果如下:
在这里插入图片描述

父进程和子进程的输出都存放在了文件中,两者是可以通过文件的方式进行交互的。

父子进程共享内容

在刚fork之后

  • 父子进程共享:data、.text、栈、堆、环境变量、用户ID、宿主目录、进程工作目录、信号处理方式;
  • 父子进程不同点: 进程ID、fork返回值、父进程ID 、进程运行时间 、闹钟(定时器) 、未决信号集。

除此以外,我们从用户区进程和内核区进程两个方面再来讨论一下这个问题:

  1. 用户区进程:
    父子进程的代码是完全相同的,同时还会复制父进程的数据,但是为了防止无谓的拷贝浪费内存,这种复制采用的往往是读时共享,写时拷贝的方式,也就是只有在任一进程对数据进行写操作时,复制才会发生(先缺页中断,操作系统再给子进程分配内存并复制父进程的数据)。
  2. 内核区进程:
    内核区实际上基本不共享,共享的只有文件描述符和mmap映射区(可以用其建立共享内存).
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值