尝试带你理解 - 进程地址空间,写时拷贝

序言

 在上一篇文章 进程概念以及进程状态,我们提到了 fork 函数,该函数可以帮我们创建一个子进程。在使用 fork 函数时,我们会发现一些奇怪的现象,举个栗子:

  1 #include <stdio.h>2 #include <unistd.h>3 4 int main(){5     int val = 1;6     pid_t pid = fork();7 8     // 创建子进程失败9     if(pid < 0){10         printf("Failed to create child process.");11         return 1;12     }13     // 子进程14     else if(pid == 0){15         val += 1;16         printf("I am child process, pid is %d, val = %d, &val = %p.\n", getpid(), val, &val);17     }18     // 父进程19     else{                                                                                                                                                                                                     20         printf("I am parent process, pid is %d, val = %d, &val = %p.\n", getpid(), val, &val);21     }22 23     return 0;24 }

注意:这里我为了简化代码以便大家更为方便的理解,并没有回收子进程,这是不规范的,会导致僵尸进程的产生QAQ。

运行上段代码,我们得到以下的输出结果:

I am parent process, pid is 4092, val = 1, &val = 0x7ffdde232328.
I am child process, pid is 4093, val = 2, &val = 0x7ffdde232328.

我们在上篇文章中也提到了,fork 函数会返回两个返回值,这对于我们来说简直就是不符合常识呀!

通过程序的运行结果,我们得出以下疑问:

  • 为什么 fork 函数会返回两个返回值?
  • 为什么程序走了 if else 条件控制结构的两个分支?
  • 为什么同一个存储 val 的地址,存储的数值还不同?

1. 进程地址空间

 首先我们肯定的是同一个物理地址只能存储一个值,不可能存储多个值。但是上面变量的地址确实是相同的呀?这又怎么解释呢?有没有可能我们打印出的地址不是真正的物理地址,那是什么呢?虚拟地址。

1.1 物理内存空间

 物理内存可以根据操作系统和程序的需要,按照存储的数据类型和用途来划分为不同的段。以下是按照存储的数据类型进行的分类:
在这里插入图片描述
在创建一个进程时,操作系统通常会为该进程分配以上几种数据段,这些段共同构成了进程的空间。可以发现这些进程空间在物理上是并不连续的,当系统正常的运行时,每时每刻会产生大量的进程完成各项任务,内存管理复杂性是相当高的,所以说我们肯定不会直接在物理内存上进行操作。

1.2 虚拟地址空间

 大家在一定都看过这幅描述进程地址空间的图像吧:
在这里插入图片描述
大家到这里肯定会有疑问,你刚才才说进程的空间在物理内存上是分散的,不连续的,那你为什么还给出这幅图呢?这确实不是在物理内存上的图像,这是我们的虚拟地址空间,它包含了进程执行所需要的所有代码、数据、堆栈等信息。
一个程序的虚拟地址空间是连续的,这大大地简化了我们的内存管理。 但是我们的数据最终肯定是在物理内存上,那和这个虚拟地址空间有什么关系呢?

1.3 页表 — 连通物理和虚拟的桥梁

定义

页表是一种数据结构,通过页表,操作系统能够将程序中的虚拟地址转换为实际的物理地址,从而实现内存的访问。

作用

  • 地址映射:页表实现了虚拟地址到物理地址的映射,使得程序可以使用连续的虚拟地址空间来访问物理内存中可能不连续的内存块。
  • 内存保护:页表还包含了每页的访问权限信息(如可读、可写、可执行等),从而实现对内存访问的控制,提高系统的安全性。

综上所述,通过页表我们可以将在虚拟内存上的操作映射到物理内存上,并且还会提前预警不合法的访问修改操作,确保进程的正确执行和系统的稳定运行。

1.4 进程地址空间的优点

  • 有序化空间地址:将物理地址空间将无序变为有序,让进程以统一的视角看待物理内存以及自己的各个区域
  • 进程隔离:每个进程都拥有自己独立的地址空间,这确保了不同进程之间的内存是相互隔离的。
  • 读写保护:进程地址空间通过页表等机制实现了对内存访问权限的控制。操作系统可以设定页表的权限字段,如只读、可写等,从而限制进程对内存的访问方式。
  • 提高内存使用效率:当我们使用 malloc 等等库函数动态申请空间,却并没有立即使用时,会在页表先登记申请的虚拟空间地址,不立即在物理内存的堆上申请,等到你要使用时再申请

1.5 进程地址空间和总结

 进程地址空间中的虚拟地址通过操作系统的地址转换机制(如页表)映射到物理地址。当程序试图访问某个虚拟地址时,操作系统会查找页表,将虚拟地址转换为对应的物理地址,然后访问物理内存中的数据。


2. fork 函数背后的逻辑

2.1 进程复制

  • 复制进程fork 函数会复制当前进程的上下文来创建一个新的进程。这个复制过程包括进程的 PCB 内的部分信息、虚拟地址空间(写时复制)、页表、文件描述符、环境变量等。
  • 共享与独立:虽然子进程是父进程的复制品,但两者在操作系统中被视为独立的进程,拥有各自的进程 ID(PID)。它们各自独立地执行程序,且可以通过不同的返回值来区分是父进程还是子进程。

2.2 返回值

  • 父进程中的返回值:在父进程中,fork 函数会返回新创建的子进程的PID(一个正整数)。
  • 子进程中的返回值:在子进程中,fork 函数返回 0 。如果 fork 调用失败,则在父进程中返回 -1,并设置 errno 以指示错误原因。

2.3 执行起点

子进程的执行起点:子进程的执行起点是从 fork 调用之后的那条指令开始的。这意味着 fork 在这里插入代码片调用之前的所有变量赋值、文件操作等都会被子进程继承。


3. 虚拟地址中的写时复制

3.1 使用场景

 上面提到当我们创建一个子进程的时候,子进程会直接复制父进程的虚拟地址空间,页表等信息,很明显直接复制这是一个浅拷贝:
在这里插入图片描述
就比如,在上附图中,父进程一个变量 A,他的页表信息也被子进程所直接复制。

当我们的子进程想要修改该变量 A 的值时,如果不进行额外处理,那么父进程的值也会改变,这违背了 父子进程相互独立 规则。所以,会为子进程重新申请一个新的空间,放置修改后 A 的值,这也会使子进程页表中对应的物理地址发生改变。

3.2 写时拷贝的优点

  1. 减少不必要的资源分配,不需要为不用修改的变量申请空间
  2. 提高复制效率,在写时拷贝机制下,资源的复制操作被延迟到实际需要修改资源内容时进行。这种懒惰复制的方式减少了在资源初始分配时的开销,提高了系统的整体效率。

4. 解释开头的疑问

为什么 fork 函数会返回两个返回值?

 使用 fork 函数后,会变为两个进程,一个父进程,一个子进程,两个进程中接收的返回值是不同的,父函数接收的为子进程的 pid,子进程接收的为 0

为什么程序走了 if else 条件控制结构的两个分支?

 不是同一个进程同时走两个分支,是两个进程走各自的分支。


为什么同一个存储 val 的地址,存储的数值还不同?

 子进程直接拷贝了父进程的进程地址空间和页表内的信息,并且打印出来的是页表内的虚拟地址,而非真是物理地址,所以地址相同。但是子进程修改变量时,发生写时拷贝,给该变量一个新的物理空间。总的来说,父子进程的 val 各自存放在不同的物理地址,但是虚拟地址相同。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/50134.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

跟《经济学人》学英文:2024年07月20日这期 The Russell 2000 puts in a historic performance

Why investors have fallen in love with small American firms The Russell 2000 puts in a historic performance 罗素2000指数&#xff1a; 罗素2000指数&#xff08;英语&#xff1a;Russell 2000 Index&#xff09;为罗素3000指数中收录市值最小的2000家&#xff08;排序…

学习笔记 韩顺平 零基础30天学会Java(2024.7.25)

P425 枚举类引出 举了一个例子&#xff0c;季节类创建对象&#xff0c;但是根据Java的规则&#xff0c;可以设置春夏秋冬以外的对象&#xff0c;而且可以修改&#xff0c;这样就会不符合实际&#xff0c;因此引出枚举 P426 自定义枚举类 1.构造器私有化&#xff0c;使外面没有办…

深入解析 GPT-4o mini:强大功能与创新应用

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文由 JohnKi 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f4e2;未来很长&#…

C++ 列式内存布局数据存储格式 Arrow

Apache Arrow 优点 : 高性能数据处理&#xff1a; Arrow 使用列式内存布局&#xff0c;这特别适合于数据分析和查询操作&#xff0c;因为它允许对数据进行高效批量处理&#xff0c;减少CPU缓存未命中&#xff0c;从而提升处理速度。 零拷贝数据共享&#xff1a; Arrow …

【YashanDB知识库】yasdb jdbc驱动集成druid连接池,业务(java)日志中有token IDENTIFIER start异常

问题现象 客户的java日志中有如下异常信息&#xff1a; 问题的风险及影响 对正常的业务流程无影响&#xff0c;但是影响druid的merge sql功能&#xff08;此功能会将sql语句中的字面量替换为绑定变量&#xff0c;然后将替换以后的sql视为同一个&#xff0c;然后用做执行性能统…

Vue3扁平化Tree组件的前端分页实现

大家好&#xff0c;我是小卷。得益于JuanTree的扁平化设计&#xff0c;在数据量很大的情况下除了懒加载&#xff0c;使用前端分页也是一种解决渲染性能问题的可选方案。 用法 要实现的文档&#xff1a; 分页效果&#xff1a; 实现 新增属性&#xff1a; 组件setup方法中新增…

程序员加班现象:成因、影响与应对策略

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;日常聊聊 ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 加班的成因 加班的影响 应对策略 结语 我的其他博客 前言 在现代科技行业中&#xff0c;加班现象已成为一个普遍存在的问题…

配置sublime的中的C++编译器(.sublime-build),实现C++20

GCC 4.8: 支持 C11 (部分) GCC 4.9: 支持 C11 和 C14 (部分) GCC 5: 完全支持 C14 GCC 6: 支持 C14 和 C17 (部分) GCC 7: 支持 C17 (大部分) GCC 8: 完全支持 C17&#xff0c;部分支持 C20 GCC 9: 支持更多的 C20 特性 GCC 10: 支持大部分 C20 特性 GCC 11: 更全面地支持 C20 …

ES中的数据类型学习之ARRAY

Arrays | Elasticsearch Guide [7.17] | Elastic 中文翻译 &#xff1a;Array Elasticsearch 5.4 中文文档 看云 Arrays In Elasticsearch, there is no dedicated array data type. Any field can contain zero or more values by default, however, all values in the a…

SpringBoot 自动配置原理

一、Condition Condition 是在 Spring 4.0 增加的条件判断功能&#xff0c;通过这个可以功能可以实现选择性的创建 Bean 操 作。 思考&#xff1a; SpringBoot 是如何知道要创建哪个 Bean 的&#xff1f;比如 SpringBoot 是如何知道要创建 RedisTemplate 的&#xff1f; …

mysql的B+树索引结构介绍

一、B树 特性&#xff1a; 所有的叶子结点中包含了全部关键字的信息&#xff0c;非叶子节点只存储键值信息&#xff0c;及指向含有这些关键字记录的指针&#xff0c;且叶子结点本身依关键字的大小自小而大的顺序链接&#xff0c;所有的非终端结点可以看成是索引部分&#xff0…

MySQL数据库基本用法

了解数据库基本概念 什么是数据库&#xff1f; • 长期存放在计算机内&#xff0c;有组织、可共享的大量数据的集合&#xff0c;是一个数据“仓库” MySQL数据库的特点 • 开源免费&#xff0c;小巧但功能齐全 • 可在Windows和Linux系统上运行 • 操作方便&#xff0c;…

昇思25天学习打卡营第22天|munger85

LSTMCRF序列标注 我们希望得到这个模型来对词进行标注&#xff0c;B是开始&#xff0c;I是实体词的非开始&#xff0c;O是非实体词。 我们首先需要lstm对序列里token的记忆&#xff0c;和计算每个token发到crf的分数&#xff0c;发完了再退出来&#xff0c;最后形成1模型。那么…

免费可视化工具大显身手:公司财务报表一键生成

面对海量的财务数据&#xff0c;如何快速、准确地提炼出有价值的信息&#xff0c;并以直观易懂的方式呈现给管理层及利益相关者&#xff0c;成为了每一家企业面临的重大挑战。 传统财务报表编制过程繁琐&#xff0c;不仅耗时耗力&#xff0c;还容易出错。而一些可视化工具&…

Java学习笔记(四)控制流程语句、循环、跳转控制语句

Hi i,m JinXiang ⭐ 前言 ⭐ 本篇文章主要介绍Java控制流程语句、循环、跳转控制语句使用以及部分理论知识 &#x1f349;欢迎点赞 &#x1f44d; 收藏 ⭐留言评论 &#x1f4dd;私信必回哟&#x1f601; &#x1f349;博主收将持续更新学习记录获&#xff0c;友友们有任何问题…

Java多线线程-----等待唤醒机制(wait notify)

目录 一.等待唤醒机制简介&#xff1a; 二.synchronized,wait(),notify(): 三.等待唤醒机制案例: 例题一&#xff1a; 例题二&#xff1a; 四.什么时候释放锁—wait&#xff08;&#xff09;、notify&#xff08;&#xff09; 一.等待唤醒机制简介&#xff1a; 由于线程之…

pyqt5制作音乐播放器(第三版)

这次接入了数据库&#xff0c;增加了翻页模式&#xff0c;更新了功能跳转之间的细节 数据设计&#xff1a; 收藏 like1时表示被收藏&#xff0c;展示show0的时候表示表数据被搜索 from peewee import Model, PrimaryKeyField, CharField, BooleanField, MySQLDatabase,Integer…

【区块链+绿色低碳】基于区块链的碳排放管理系统 | FISCO BCOS应用案例

目前业内的碳排放核查方式主要依靠于第三方人工核查、手动填报数据&#xff0c;然后由具备有认证资质的机构进行核验 盖章。但在此过程中存在数据造假的情况&#xff0c;给碳排放量核算的准确性、可靠性带来挑战。 中科易云采用国产开源联盟链 FISCO BCOS&#xff0c;推出基于…

搭建博客系统#Golang

WANLI 博客系统 项目介绍 基于vue3和gin框架开发的前后端分离个人博客系统&#xff0c;包含md格式的文本编辑展示&#xff0c;点赞评论收藏&#xff0c;新闻热点&#xff0c;匿名聊天室&#xff0c;文章搜索等功能。 项目已经部署并运行&#xff0c;快速开发可以查看博客&am…

培训第十一天(nfs与samba共享文件)

上午 1、环境准备 &#xff08;1&#xff09;yum源 &#xff08;一个云仓库pepl仓库&#xff09; [rootweb ~]# vim /etc/yum.repos.d/hh.repo [a]nameabaseurlfile:///mntgpgcheck0[rootweb ~]# vim /etc/fstab /dev/cdrom /mnt iso9660 defaults 0 0[rootweb ~]# mount -a[…