虚幻or现实?堆区、栈区真实存在吗?是操作系统在骗你罢了...

news2024/11/27 18:29:09

在这里插入图片描述

文章目录

  • 💐专栏导读
  • 💐文章导读
    • 🐧引例
  • 🐦进程地址空间
  • 🐦虚拟地址与物理内存的联系
      • 🔔回答引例中的问题
        • 🔓写时拷贝
  • 🐦虚拟地址存在的意义
        • 🔓malloc的本质

💐专栏导读

🌸作者简介:花想云 ,在读本科生一枚,C/C++领域新星创作者,新星计划导师,阿里云专家博主,CSDN内容合伙人…致力于 C/C++、Linux 学习。

🌸专栏简介:本文收录于 Linux从入门到精通,本专栏主要内容为本专栏主要内容为Linux的系统性学习,专为小白打造的文章专栏。

🌸相关专栏推荐:C语言初阶系列C语言进阶系列C++系列数据结构与算法

💐文章导读

本章我们将对程序地址空间进行讲解,理解虚拟地址的运作逻辑,认识虚拟地址与物理地址如何建立联系以及虚拟地址存在的意义~

在这里插入图片描述

🐧引例

——为什么一个变量拥有两个不同的值?

之前在第一次认识fork时,我们就知道fork是用来创建子进程的。先来感受一段代码,我们定义一个全局变量g_val,在父进程与子进程中分别查看g_val的值以及它的地址。

#include <stdio.h>
#include <unistd.h>

int g_val = 100;

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

  if(id < 0)
  {
    perror("fork");
    return 0;
  }
  else if(id == 0)
  {
    // 子进程
    while(1)
    {
       printf("我是子进程,pid=%d,ppid=%d,g_val=%d,&g_val=%d\n",getpid(),getppid(),g_val,&g_val);
       sleep(1);
    }
  }
  else 
  {
    // 父进程
    while(1)
    {
      printf("我是父进程,pid=%d,ppid=%d,g_val=%d,&g_val=%d\n",getpid(),getppid(),g_val,&g_val);
      sleep(1);
    }
  }

  return 0;
}

在这里插入图片描述

如图所示,我们发现父子进程打印的值与地址完全相同。接着再来做测试,倘若在子进程中改变g_val的值,那么父进程中的g_val会不会一起跟着变化呢?

#include <stdio.h>
#include <unistd.h>

int g_val = 100;

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

  if(id < 0)
  {
    perror("fork");
    return 0;
  }
  else if(id == 0)
  {
    // 子进程
    while(1)
    {
       printf("我是子进程,pid=%d,ppid=%d,g_val=%d,&g_val=%d\n",getpid(),getppid(),g_val,&g_val);
       sleep(1);
       g_val = 200;
    }
  }
  else 
  {
    // 父进程
    while(1)
    {
      printf("我是父进程,pid=%d,ppid=%d,g_val=%d,&g_val=%d\n",getpid(),getppid(),g_val,&g_val);
      sleep(1);
    }
  }
  return 0;
}

在这里插入图片描述

结果如图所示,我们发现在子进程中修改变量的值,并不会影响父进程。我们知道进程具有独立性,父子进程之间互不影响好像也能说得过去,但是两个进程中g_val的地址都是相同的,一个地址怎么能存两个不同的值呢只能说明这两个地址其实并不是真正的物理地址。

对此我们可以得出结论:

  • 变量内容不一样,所以父子进程输出的变量绝对不是同一个变量;
  • 但它们地址值是一样的,说明该地址绝对不是物理地址!
  • 在Linux地址中,这种地址叫做 虚拟地址
  • 我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理。

接下来我们就来看看何为虚拟地址,它又为什么而存在。

🐦进程地址空间

或许我们之前都是这么看待内存的——
在这里插入图片描述
如图所示,我们以前经常把内存区域作划分,并强调哪些变量应该存储在内存的哪些区域,其实这些区域的划分都是虚拟内存。

那么这些不同的区域(栈区、堆区、常量区等)又是如何进行划分的呢?

  • 进程地址空间本质上就是一个内核数据结构——struct mm_struct,由操作系统所管理。

我们知道内存的地址是连续的,也就意味着内存空间是一种线性结构。OS通过对内核数据结构的划分完成对内存区域的划分,例如:

struct mm_struct
{
	// 代码区
	long code_start; // [0,100]
	long code_end;
	// 初始化区域
	long init_start; // [200,500]
	long init_end;
	// 栈区
	long stack_start; //[x,x+n]
	long start_end;
	//...
}

🐦虚拟地址与物理内存的联系

虚拟地址是虚拟的、不存在的。可是我们所写的那些变量常量等等总得找个地方存起来吧。它们可都是实实在在的存储在物理内存上的。难道虚拟内存所有的内容都与物理内存一一对应吗?那当然是不可能的。

其实在虚拟内存与物理地址之间还存在这一个媒介——页表。它负责将虚拟地址与物理地址形成一种映射关系。

在这里插入图片描述

🔔回答引例中的问题

有了上面的概念,我们现在可以清楚。子进程是被父进程创建出来的,所以子进程没有自己的代码和数据,只能继承自父进程。所以子进程与父进程中的g_val地址相同,现在我们知道这两个地址其实都是虚拟地址。

  • 父进程与子进程中的g_val会被页表映射到同一个物理地址。

有的小伙伴可能会疑问,不对呀,那这样刚才岂不是白讲了。这样的话我们改变子进程中的g_val,父进程不也会跟着变吗?

答案是,还差非常重要的一点没有提出来——写时拷贝

🔓写时拷贝

由于父进程和子进程的g_val一开始内容是完全相同的,没必要再新开一块空间存储一个重复的数据,避免浪费。所以,只要我们不修改g_val的值,两个g_val存在共用一块内存区域是合理的。

但是,一旦有一方需要修改g_val的值,为了避免一方修改会影响到另一方,此时OS会在内存中重新找到一块区域,将修改后的值放进去,并让页表将虚拟地址从旧的物理地址,映射到新的虚拟地址。这就叫做写时拷贝

在这里插入图片描述

  • 写时拷贝概念图

在这里插入图片描述

🐦虚拟地址存在的意义

举个例子,假设现在有100个班级,需要去一个水果商店免费水果。现在校长把任务交给了这100个班主任,让他们自行组织安排。班主任们并不需要考虑所有学生的人数或者男女比例或者强壮程度等等,只需要安排好自己班级的每个学生应该完成什么任务。这样,每个班主任各自拟好了一个任务安排表,并把它交给商店老板。商店老板会根据任务安排表,分配好每个班级应领取的水果数量、种类等等,到时候直接同时学生们过来取走即可。

在这个小例子中,学生就相当于代码和数据任务安排表相当于虚拟地址商店老板就相当于页表商店的水果相当于物理内存

假设不存在虚拟地址,也就是任务安排表不存在,商店老板就不能对着安排表做出规划,同学们与领取水果时,势必会杂乱无章甚至可能起了冲突。例如几个同学一起盯上了为数不多的榴莲…况且,同学们不知道商店中哪些水果可以拿。哪些已经被顾客预定了,不能拿…

对比这个例子,虚拟地址的作用有:

  • 防止地址随意访问,保护物理内存与其他进程(防止争抢水果)
  • 将进程管理与内存管理进行解耦合(班主任管学生,商店老板管水果)
  • 可以让进程以统一的视角看待自己的代码和数据(每个班主任只管自己的学生)

🔓malloc的本质

站在一个进程的角度

在很多时候,我们malloc申请了一块内存空间(现在我们知道这其实是虚拟内存),并没有立即对它进行使用,但是这块空间已经在某个变量的名下了,此时这块空间是不能再被其他变量所占用的,这是合理的。

站在OS的角度

那么当一个进程向OS申请内存时,请问OS是直接给它呢?还是在进程需要用的时候再给它呢?答案是在需要时再给它。

因为OS不允许有任何的浪费或者不高效的行为。假设进程一申请内存,OS就给它分配内存,但是进程又暂时不用。那么它自己不用也就算了,别的进程也用不了。一个进程这样做也就罢了,当大量的进程都这样做时就会造成严重的浪费与效率低下。

  • 其实,当我们的程序在编译完成后,就已经按照虚拟地址对代码和数据进行编制了

本章的内容就到这里了,如果觉得对你有帮助的话就支持一下博主吧~

在这里插入图片描述
在这里插入图片描述

点击下方个人名片,交流会更方便哦~
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓

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

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

相关文章

Lift, Splat, Shoot 论文学习

1. 解决了什么问题&#xff1f; LSS 在工业界具有非常重要的地位。自从 Tesla AI Day 上提出了 BEV 感知后&#xff0c;不少公司都进行了 BEV 工程化的探索。当前 BEV 下的感知方法大致分为两类&#xff1a; 自下而上&#xff1a;利用 transformer 的 query 机制&#xff0c;…

软考 软件设计师上午题设计模式概念类

设计模式分类 创建型设计模式 简单工厂模式 不符合开闭原则&#xff0c;因此没有列入23类模式里 对扩展开放对修改关闭 工厂方法模式 说穿了&#xff1a;系统开放一个接口&#xff08;拓展开放&#xff09;、不提供修改的接口&#xff08;修改关闭&#xff09;&#xff0c;…

基于html+css的图展示83

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

使用贝壳物联控制led灯

1、完成esp8266 01S的固件刷机 1.1 使用刷机软件刷原生固件 esp8266 01S要使用标准固件1M&#xff08;主要是01S是8M&#xff09; 1.2 刷机接线方式 ttl直接连esp8266 接线&#xff1a; tts esp8266 3v3 ---》面包板高----》3.3 tx--------------…

BEVFormer 论文学习

1. 解决了什么问题&#xff1f; 3D 视觉感知任务&#xff0c;包括基于多相机图像的 3D 目标检测和分割&#xff0c;对于自动驾驶系统非常重要。与基于 LiDAR 的方法相比&#xff0c;基于相机图像的方法能够检测到更远距离的目标&#xff0c;识别交通信号灯、交通标识等信息。有…

[CTF/网络安全] 攻防世界 command_execution 解题详析

[CTF/网络安全] 攻防世界 command_execution 解题详析 ping命令ping命令的应用格式ping命令执行漏洞 ls命令cat命令姿势ping本地回环地址ping目录ping文件夹ping文件 Tips总结 题目描述&#xff1a;小宁写了个ping功能,但没有写waf,X老师告诉她这是非常危险的&#xff0c;你知道…

图【无向图】的创建与遍历

树&#xff1a;无回路 图&#xff1a;有回路 代码在最下面 邻接矩阵&#xff1a;重点&#xff1a;矩阵 &#xff08;一&#xff09;图的创建 存储方式 如图&#xff1a; 代码截图分析&#xff1a;顶点用一维数组存&#xff0c;边用两个点之间的值为0或1来表…

异地研发团队都使用哪些研发协同工具?盘点7类最主流的研发管理协同软件

产品研发场景下好用的协同办公软件有哪些&#xff1f;分享7类研发过程中主流的协同办公软件&#xff0c;比如项目管理协作与问题跟踪工具PingCode、代码托管与版本控制平台github、持续集成与持续部署&#xff08;CI/CD&#xff09;工具jinkens、文档协作与知识管理工具conflue…

【力扣周赛】第346场周赛

【力扣周赛】第346场周赛 6439. 删除子串后的字符串最小长度题目描述解题思路 6454. 字典序最小回文串题目描述解题思路 6441. 求一个整数的惩罚数题目描述解题思路 6439. 删除子串后的字符串最小长度 题目描述 描述&#xff1a;给你一个仅由 大写 英文字符组成的字符串 s 。…

Gradle ——Gradle安装与配置

目录 一、简介 二、功能和特点 三、安装 参考&#xff1a; Gradle_百度百科 Gradle 学习 ----Gradle 入门_你若不离不弃&#xff0c;我必生死相依的博客-CSDN博客 还有硬石科技的开源wifiAPP程序,没找到他们的码仓 一、简介 Gradle是一个基于Apache Ant和Apache Maven概念…

BPMN2.0 -条件序列流和默认序列流

序列流是流程中两个元素或者活动的连接器。在流程执行过程中访问一个元素之后,将继续执行素有的序列流,默认是并行的。传出的序列流将创建独立的并行执行路径。 顺序流需要有流程唯一的id,并引用存在的源与目标元素。 <sequenceFlow id="flow1" sourceRef=&qu…

vxe-table 对合并列的理解

行列对应关系 删除数据&#xff0c;只对一条数据进行分析 合并列代码&#xff08;_rowspan移除不影响&#xff0c;但是函数会eslint报红&#xff0c;正常运行&#xff09; 合并之后&#xff0c;从第一列开始&#xff0c;内容整体右移动&#xff0c;标题内容不对应 解决内容右移…

第三章 openEuler 文件系统

系列文章目录 第一章 openEuler 安装指南 第二章 openEuler 网络配置 第三章 openEuler 文件系统 文章目录 系列文章目录前言一、openEuler中的文件系统1.整体架构2.文件系统层次结构3. FHS定义的一级目录结构 二、常用命令1. pwd 查看当前所在的目录路径2. ls 查看当前目录下…

算法——各排序算法效率对比和总结

1. 算法效率对比 在此我们使用如下代码来测试各个算法的效率 void TestOP() {srand(time(0));const int N 100000;int* a1 (int*)malloc(sizeof(int) * N);int* a2 (int*)malloc(sizeof(int) * N);int* a3 (int*)malloc(sizeof(int) * N);int* a4 (int*)malloc(sizeof(i…

【TOP生物信息】CNS图表复现,单细胞marker基因展示的另一种方式——蜂巢图

扫码关注下方公粽号&#xff0c;回复推文合集&#xff0c;获取400页单细胞学习资源&#xff01; 本文共计1359字&#xff0c;阅读大约需要4分钟。 Sten Linnarsson大神的单细胞绘图堪称极致美学&#xff0c;在这里&#xff0c;小编选择了发表在nature上展示marker基因的绘图进…

找不到msvcp140.dll无法继续执行代码,怎么解决?

MSVCP140.dll是一个Windows操作系统中的动态链接库文件&#xff0c;它是Microsoft Visual C Redistributable for Visual Studio 2015的一部分。这些库提供了一些C程序所需的基本功能&#xff0c;例如内存管理、文件操作、字符串处理等等。丢失或者损坏会导致很多软件跟游戏无法…

由浅入深Netty简易实现RPC框架

目录 1 准备工作2 服务器 handler3 客户端代码第一版4 客户端 handler 第一版5 客户端代码 第二版6 客户端 handler 第二版 1 准备工作 这些代码可以认为是现成的&#xff0c;无需从头编写练习 为了简化起见&#xff0c;在原来聊天项目的基础上新增 Rpc 请求和响应消息 Data …

由浅入深Netty代码调优

目录 1. 优化1.1 扩展序列化算法 2 参数调优2.1 CONNECT_TIMEOUT_MILLIS2.2 SO_BACKLOG2.3 ulimit -n2.4 TCP_NODELAY2.5 SO_SNDBUF & SO_RCVBUF2.6 ALLOCATOR2.7 RCVBUF_ALLOCATOR 1. 优化 1.1 扩展序列化算法 序列化&#xff0c;反序列化主要用在消息正文的转换上 序列…

Windows11部署WSL2以及迁移操作系统位置

1 缘起 笔记本电脑Windows 10内存紧张&#xff1a;16 G&#xff0c; 但是&#xff0c;开发需要一些组件&#xff0c;如Redis&#xff08;Redisearch、ReJson&#xff09;、MySQL等&#xff0c; 在Linux容器化中部署更方便&#xff0c;易用&#xff0c; 在Windows中通过虚拟机安…

安卓与串口通信-modbus篇

前言 在之前的两篇文章中&#xff0c;我们讲解了串口的基础知识和在安卓中使用串口通信的方法&#xff0c;如果还没看过之前文章的同学们&#xff0c;建议先看一遍&#xff0c;不然可能会不理解这篇文章讲的某些内容。 事实上&#xff0c;在实际应用中&#xff0c;我们很少会…