【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

相关文章

PHP轻松处理千万行数据的方法详解

《PHP轻松处理千万行数据的方法详解》说到处理大数据集,PHP通常不是第一个想到的语言,但如果你曾经需要处理数百万行数据而不让服务器崩溃或内存耗尽,你就会知道PHP用对了工具有多强大,下面小编就... 目录问题的本质php 中的数据流处理:为什么必不可少生成器:内存高效的迭代方式流量控制:避免系统过载一次性

MySQL的JDBC编程详解

《MySQL的JDBC编程详解》:本文主要介绍MySQL的JDBC编程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言一、前置知识1. 引入依赖2. 认识 url二、JDBC 操作流程1. JDBC 的写操作2. JDBC 的读操作总结前言本文介绍了mysq

Redis 的 SUBSCRIBE命令详解

《Redis的SUBSCRIBE命令详解》Redis的SUBSCRIBE命令用于订阅一个或多个频道,以便接收发送到这些频道的消息,本文给大家介绍Redis的SUBSCRIBE命令,感兴趣的朋友跟随... 目录基本语法工作原理示例消息格式相关命令python 示例Redis 的 SUBSCRIBE 命令用于订

防止Linux rm命令误操作的多场景防护方案与实践

《防止Linuxrm命令误操作的多场景防护方案与实践》在Linux系统中,rm命令是删除文件和目录的高效工具,但一旦误操作,如执行rm-rf/或rm-rf/*,极易导致系统数据灾难,本文针对不同场景... 目录引言理解 rm 命令及误操作风险rm 命令基础常见误操作案例防护方案使用 rm编程 别名及安全删除

Linux下MySQL数据库定时备份脚本与Crontab配置教学

《Linux下MySQL数据库定时备份脚本与Crontab配置教学》在生产环境中,数据库是核心资产之一,定期备份数据库可以有效防止意外数据丢失,本文将分享一份MySQL定时备份脚本,并讲解如何通过cr... 目录备份脚本详解脚本功能说明授权与可执行权限使用 Crontab 定时执行编辑 Crontab添加定

使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解

《使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解》本文详细介绍了如何使用Python通过ncmdump工具批量将.ncm音频转换为.mp3的步骤,包括安装、配置ffmpeg环... 目录1. 前言2. 安装 ncmdump3. 实现 .ncm 转 .mp34. 执行过程5. 执行结

Python中 try / except / else / finally 异常处理方法详解

《Python中try/except/else/finally异常处理方法详解》:本文主要介绍Python中try/except/else/finally异常处理方法的相关资料,涵... 目录1. 基本结构2. 各部分的作用tryexceptelsefinally3. 执行流程总结4. 常见用法(1)多个e

C++统计函数执行时间的最佳实践

《C++统计函数执行时间的最佳实践》在软件开发过程中,性能分析是优化程序的重要环节,了解函数的执行时间分布对于识别性能瓶颈至关重要,本文将分享一个C++函数执行时间统计工具,希望对大家有所帮助... 目录前言工具特性核心设计1. 数据结构设计2. 单例模式管理器3. RAII自动计时使用方法基本用法高级用法

SpringBoot日志级别与日志分组详解

《SpringBoot日志级别与日志分组详解》文章介绍了日志级别(ALL至OFF)及其作用,说明SpringBoot默认日志级别为INFO,可通过application.properties调整全局或... 目录日志级别1、级别内容2、调整日志级别调整默认日志级别调整指定类的日志级别项目开发过程中,利用日志

Java中的抽象类与abstract 关键字使用详解

《Java中的抽象类与abstract关键字使用详解》:本文主要介绍Java中的抽象类与abstract关键字使用详解,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录一、抽象类的概念二、使用 abstract2.1 修饰类 => 抽象类2.2 修饰方法 => 抽象方法,没有