【Linux】fork函数详解and写时拷贝再理解

2024-04-28 03:12

本文主要是介绍【Linux】fork函数详解and写时拷贝再理解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在这里插入图片描述

💐 🌸 🌷 🍀 🌹 🌻 🌺 🍁 🍃 🍂 🌿 🍄🍝 🍛 🍤
📃个人主页 :阿然成长日记 👈点击可跳转
📆 个人专栏: 🔹数据结构与算法🔹C语言进阶🔹C++🔹Liunx
🚩 不能则学,不知则问,耻于问人,决无长进
🍭 🍯 🍎 🍏 🍊 🍋 🍒 🍇 🍉 🍓 🍑 🍈 🍌 🍐 🍍

文章目录

  • 一、fork是什么?
  • 二、fork使用演示
  • 三、问题解决
    • 1.fork为什么fork()要给子进程返回0,给父进程返回子进程pid?
    • 2. fork()函数创建子进程
    • 3.读时共享,写时拷贝
  • 四、写时拷贝的原理

一、fork是什么?

fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。

总结来说就是:
1️⃣ 创建一个子进程,其返回值为pid_t。
2️⃣ 调用fork函数之后,一定是两个进程同时执行fork函数之后的代码,而之前的代码以及由父进程执行完毕。
3️⃣ fork成功后产生两个进程,一个是子进程,一个是父进程。
(1)子进程fork函数返回0,
(2)父进程fork返回新创建子进程的进程PID,
(3)错误返回负值。
4️⃣ 4.fork是把已有的进程复制一份,当然把PCB也复制了一份,然后申请一个PID:子进程的PID=父进程的PID+1;
5️⃣fork后的代码会执行两遍。
6️⃣读时共享,写时拷贝

二、fork使用演示

运行如下代码:

  1 #include<stdio.h>2 #include<unistd.h>3 4 int main(void)                                                                                                                               5 {6 7   fork();8   printf("after: only one line\n");9   sleep(1);10   return 0;11 }

我们只写了一个printf语句,却打印了两遍。也印证了5️⃣fork后的代码会执行两遍。原因就是:fork执行后生成父子进程。
在这里插入图片描述
那么根据第三点3️⃣ fork成功后产生两个进程,一个是子进程,一个是父进程。子进程fork函数返回0;父进程fork返回新创建子进程的进程PID。我们在写一段代码来验证一下。

#include <stdio.h>#include <unistd.h>#include <sys/types.h>int main(void){printf("begin: 我是一个进程, pid: %d, ppid: %d\n", getpid(), getppid());pid_t id = fork();if(id == 0){// 子进程while(1){printf("我是一个子进程, pid: %d, ppid: %d\n", getpid(), getppid());sleep(1);}}else if(id > 1){// 父进程while(1){printf("我是一个父进程, pid: %d, ppid: %d\n", getpid(), getppid());          sleep(1);}}else{printf("error, fork创建子进程失败\n");}return 0;
}

在这里插入图片描述
看到这里你会惊奇的发现,这个代码的if…else…分支可以同时进去。这在以前是没有发生过的。这也正是父子进程同时执行的结果。

三、问题解决

1.fork为什么fork()要给子进程返回0,给父进程返回子进程pid?

  • 上面我们说到当进程的代码执行到fork()函数的时候,会将执行流一分为二,父子进程通过不同的 id 返回值来区分,以此执行不同的代码块。:因为父子进程是两个不同的进程,所以需要根据这个不同的返回值来进程区别
  • 再就是为什么一定是给父进程返回子进程的PID呢?不能反过来吗?这是因为,父亲只能有一个,但是可以有很多儿子,如果给父进程返回0,那么有那么儿子的标识都是0,就无法区分了。

2. fork()函数创建子进程

  • 在上面我们讲到过【进程 = 内核数据结构 + 代码+数据 】,当我们在执行完fork()函数后,子进程被创建出来,那么它的PCB结构体即 task_struct 会被构建出来,我们知道的是在每个进程的结构体中有PID和PPID这两个成员,而且对于子进程中的PPID恰好就是父进程中的PID +1。所以子进程大部分的属性就是以父进程为模版创建的,相当于把父进程拷贝了一份,对部分属性做了修改。
    在这里插入图片描述

也就是说,子进程几乎拷贝了父进程的所有内容,包括代码数据和绝大部分的PCB。上面我们谈到子进程要去拷贝数据的原因是在于【并发修改】的问题,但若是子进程只是读取数据但是不修改呢?也需要去完整地拷贝一份数据吗?不,完全不需要!这会使得资源消耗过大!(如上图🖕)

3.读时共享,写时拷贝

fork函数执行时,不会直接给子进程拷贝一份父进程的数据,在子进程刚被创建的时候,代码和数据全部都是被共享的,只有当操作系统识别到子进程要对父进程中的数据做修改时,才会在系统的某一个位置开辟一段空间,然后在修改的时候不去修改父进程内部的这个数据,而是去修改拷贝出来的这块数据此为父子进程之间数据层面的写时拷贝。
在这里插入图片描述

四、写时拷贝的原理

1.为什么要使用写时拷贝?

其核心思想是,如果有多个调用者同时请求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这个过程对其他的调用者是透明的(transparently)。此作法的主要优点是如果调用者没有修改该资源,就不会有副本(private copy)被建立,因此多个调用者只是读取操作是可以共享同一份资源。

  • 上面的官方术语简单来说就是: 写时拷贝是浅拷贝解决浅拷贝析构冲突的一种解决方案,几个对象共用一块空间,当执行读操作时不会有影响,当你需要进行写操作改变一个对象的内容时,空间的值不能被修改,会互相影响,那么就需要单独开辟一块空间将对象拷贝过去然后改,不改变就不需要开辟。

2.写时拷贝的原理

  • 写时拷贝技术实际上是运用了一个 “引用计数” 的概念来实现的。

  • 在开辟的空间中多维护四个字节来存储引用计数
    有两种方法:
    ①:多开辟四个字节(pCount)的空间,用来记录有多少个指针指向这片空间。
    ②:在开辟空间的头部预留四个字节的空间来记录有多少个指针指向这片空间。【常用】

  • 当我们多开辟一份空间时,让引用计数+1,如果有释放空间,那就让计数-1,但是此时不是真正的释放,是假释放,等到引用计数变为 0 时,才会真正的释放空间。

  • 如果有修改或写的操作,那么也让原空间的引用计数-1,并且真正开辟新的空间。

这篇关于【Linux】fork函数详解and写时拷贝再理解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/942195

相关文章

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

linux-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

hdu1171(母函数或多重背包)

题意:把物品分成两份,使得价值最接近 可以用背包,或者是母函数来解,母函数(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v) 其中指数为价值,每一项的数目为(该物品数+1)个 代码如下: #include<iostream>#include<algorithm>

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)