【Linux】进程地址空间(带你认清内存的本质)

news2024/11/27 18:31:54

       🔥🔥 欢迎来到小林的博客!!
      🛰️博客主页:✈️小林爱敲代码
      🛰️博客专栏:✈️Linux之路
      🛰️社区 :✈️ 进步学堂
      🛰️欢迎关注:👍点赞🙌收藏✍️留言

文章目录

  • 💖进程地址空间
  • 💖进程地址空间是什么?
    • 进程地址空间的划分
    • 虚拟内存转换成物理内存
  • 💖为什么要有进程地址空间?

💖进程地址空间

我们在学习C语言的时候,应该都知道这个内存空间图。

在这里插入图片描述

但其实我们对它并不了解,为什么呢?我们用一段代码来感受一下!

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

int g_val = 100;

int main()
{
  int pid = fork(); //创建子进程
  if(pid == 0)
  {
    //child
    int count = 5;
    while(count)
    {
      printf("i am child , g_val = %d, &g_val = %p\n",g_val,&g_val);

      if(count == 3)
      {
        //修改数据
        printf("******开始修改数据*******\n");
        printf("i am child , g_val = %d, &g_val = %p\n",g_val,&g_val);
        g_val = 200;
        printf("******修改数据done*******\n");
      }
      count--;
      sleep(1);
    } 
    
  }
  else if(pid > 0)
  {
    //parent
    while(1)
    {
      printf("i am father , g_val = %d, &g_val = %p\n",g_val,&g_val);
      sleep(1);
    }
  }
  else 
  {
    //erro
    perror("fork:");
  }
  return 0;
}

我们这个代码的主体逻辑是,创建一个全局变量。然后再创建一个子进程,随后打印全局变量的值和地址,在子进程特定的时候修改这个全局变量。

那么我们来看看运行结果吧!

在这里插入图片描述

我们可以发现,g_val的值被修改了! 但是它们的地址还是一样的。这是怎么回事!???

我们都知道,父进程和子进程如果没有发生数据修改,那么会共用同一份数据。如果有一方的数据发生了修改,那么就会写实拷贝一份。所以此时的父进程和子进程各有一份属于自己的数据,既然有2份数据那么就说明有2个g_val。2个独立进程的g_val变量用了同一块内存空间,这合理吗?完全不合理!!!2个进程用同一块空间,这不就起冲突了。这是为什么呢??

再探索这个问题之前,先给大家讲个小故事,方便大家理解。

在美国有一个大富翁,他有三个私生子,这三个私生子互相不认识。而这个大富翁有100亿美金。

在这里插入图片描述

然后这个时候,大富翁对私生子小A说:等我老了,就由你来继承我100亿财产吧。这时小A就以为这100亿是他的了。然后大富翁又对私生子小B说:等我老了,你来继承我的100亿吧。 小B听了高兴坏了,也以为这100亿是他的了。然后大富翁又对私生子小C说了同样的话。 所以,大富翁给他的所有私生子都花了一张大饼。告诉他们,他们未来都会继承这100亿。所以大富翁的私生子,都认为自己有100亿可以花,就可以按照这100亿来为自己分配生活。

在这里插入图片描述

而这里面的大富翁,就是操作系统,私生子就是进程,大饼就是进程地址空间,那么这100亿美金,就是我们的物理内存。而我在之前的篇章里说过,进程的本质其实就是 描述进程的结构体(PCB)+代码数据。那么进程地址空间是否在PCB里呢?答案当然是的。也就是说每个进程都会有一个进程地址空间,每个私生子都认为自己独占了大富翁的100亿美金,所以每个进程都认为自己独占了物理内存。 所以,我们的**进程地址空间,也被我们称之为虚拟内存。**那么为什么会打印相同地址?我们先了解一些东西,再最侯为大家总结结论。

💖进程地址空间是什么?

那么进程地址空间是什么呢?地址空间本质是内核中的一种数据类型,在Linux内核中,它是一个struct mm_struct的结构体。

在这里插入图片描述

也就说,我们程序的内存划分,本质上是一个区域!!

在这里插入图片描述

进程地址空间的划分

那么进程地址空间是怎么划分的?

打个比方:

假如你现在是一名小学生,你的同桌是一名爱干净的小女孩。而你一名爱流鼻涕不讲卫生的小男孩,这时侯,你的同桌嫌弃你。假设你俩的桌子长100cm,此时你的同桌在50cm的地方画了一根三八线,跟你划清界限。那么此时你还能不能把东西放到你同桌所在的区域?当时是不能了!假设你的区域是 0 - 50cm的地方,那么你的东西只能放在0-50区间。假设这时候有一把尺子,你想把你的橡皮擦放在第38cm的地方,于是你就拿尺子量出了38cm,把橡皮放在这个位置上。这里面呢,你和你同桌,充当的是一块区域,而这把尺子,是进程地址空间,橡皮擦,则是你的数据。你要把数据放在指定的地方,那么就需要进程地址空间充当尺子。为什么你知道桌子是100cm?因为有尺子,所以你才知道桌子是100cm。所以你要划分区域,也需要进程地址空间充当尺子来划分区域。

struct mm_struct
{
	unsigned int code_start; //代码段起始地址
	unsigned int code_end;  //代码段结束地址
	
	unsigned int init_data_start;//初始化变量区起始地址.
	unsigned int init_data_end;//初始化变量区结束地址
	
	unsigned int uninit_data_start;//未初始化变量区起始地址.
	unsigned int uninit_data_end;//未初始化变量区结束地址
	
	unsigned int heap_start;//堆区起始地址.
	unsigned int heap_end;//堆区结束地址
	
	.....
	unsigned int stack_start;//堆区起始地址.
	unsigned int stack_end;//堆区结束地址
}

每个进程都认为地址空间的划分是按照4GB的空间划分的,而地址空间上进行区域划分的位置,是虚拟地址!虽然这里只要start和end,但是每个进程都可以认为mm_struct 代表整个内存,且所有的地址为0x00000000 -> 0xFFFFFFFF。

虚拟内存转换成物理内存

既然每个进程都有一块地址空间,而程序里面的数据和代码都是根据进程地址空间存放。那么我们的系统调用它时是如何为它分配地址的呢?如何把对应的数据放到物理内存的呢?

那是因为物理内存和虚拟内存之间,有一张页表。

在这里插入图片描述

而页表的本质就是哈希表。通过虚拟内存来映射物理内存。也就说,每一个进程地址空间,都会有一张对应的页表。意思就是每一个进程都会有一张页表,通过页表的虚拟地址,就可以找到对应的物理内存。从而操作系统对物理内存进行操作。

💖为什么要有进程地址空间?

1. 通过添加一层软件层,完成有效的对进程操作内存进行风险管理(权限管理),本质的目的是为了,保护物理内存以及各个进程的数据安全。

比如:

先给大家放一段代码。

int main()
{
	const char* str = "hello world";
    str = "HW";
	return 0;
}

这段代码会报错,为什么呢?因为 str它所处的内存空间是常量区。通过虚拟内存映射到真实的物理地址之后,它的权限是只读权限。当你修改它时,因为你不具备写权限,所以操作系统会直接把你干掉。这也是为什么要有进程地址空间的原因。如果没有进程地址空间,那么就无法进行权限管理,那么即使是常量也可以被修改!这是非常严重的!而有了进程地址空间之后,你能不能修改,全部取决于操作系统让不让你修改!

2. 将内存申请和内存使用的概念在时间上划分清除,通过虚拟地址空间,来屏蔽底层申请的过程,达到进程读写内存和OS进行内存管理操作,进行软件上面的分离!

先抛出一个问题:假如我们申请5000个字节,我们立马能使用这5000字节吗??

答案是:不一定,可能会存在暂时不会全部使用,甚至暂时不使用的情况。

因为,在OS(操作系统)的角度上,如果空间立马就给你的话,是不是就意味着,整个系统会有一部分空间,本来可以先给其他进程立马使用,现在却被你闲置着?说简单点就是这个进程现在正茅坑,但丝毫没有要拉屎的意思。这种做法是人人恨之的,所以操作系统不一定会立马给你使用。

打个比方:比如你要开学了,你和你老爹要8000块钱的学费。但是你还有一星期才开学呢,于是你老爹说:好,我知道了,开学前一天给你。 你像你老爸要了8000块钱,你老爸对应给你了。这就就相当于你申请了8000字节的空间。但是你还有一星期才开学,也就是你这8000块钱暂时用不上,你这8000字节也暂时用不上。所以你爸说等你开学前一天的时候给你,而操作系统也在进程要使用的时候,给进程真实的物理内存。

在这里插入图片描述

而这和我们的写实拷贝非常的像,数据不改变就共用同一份数据,改变就拷贝一份。

3.站在CPU和应用层的角度,进程统一可以看做统一使用4GB空间,而且每个空间区域的相对位置,是比较确定的!OS最终这样设计的目的,达到了一个目标:每个进程都认为自己是独占系统资源的!进程具有独立性的!

这种情况也就是我们开头演示的那样,为什么2个进程的g_val的地址是相同,而值是不同的。这是因为**子进程创建是以父进程为模板创建的,所以子进程也会继承父进程的页表。**在子进程没有对g_val的值进行修改时,父子进程共享一份数据。而一旦子进程对g_val的值进行修改,那么在OS会对g_val的数据进行一份拷贝(写实拷贝)。且让子进程页表映射到g_val的值映射至新拷贝后的物理地址。这样子,即使它们的g_val的地址是相同的,但是在它们在页表 g_val的数据 是映射到不同的物理地址。

在这里插入图片描述

最后,为什么下面str1和str2的地址是相等的?

#include<stdio.h>
int main()
{
	const char* str1 = "hello world";
	const char* str2 = "hello world";
	printf("str1 的地址是: %p",str1);
	printf("str2 的地址是: %p",str2);
}

如上代码,我们会发现str1和str2的地址是一样的,可是它们不是同一个变量啊,为什么?因为str1和str2都在常量区。也就是说操作系统只给了这个区域可读权限,所以操作系统认为,对于只有可读的数据,操作系统只需要维护一份即可。

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

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

相关文章

Linux:chmod chown 权限管理

基础权限有以下三个 r 读 4 w 写 2 x 执行 1 - 无此权限 0 开头的第一个字母是这个的类型 d 目录 - 普通文件 l 链接文件 常见的三种 只不过今天不讲这个 从第二个字母开始看起 三个字母为一组 一共…

【EfficientDet】《EfficientDet:Scalable and Efficient Object Detection》

CVPR-2020 文章目录 1 Background and Motivation2 Related Work3 Advantages / Contributions4 Method4.1 BiFPN4.2 EfficientDet 5 Experiments5.1 Datasets5.2 EfficientDet for Object Detection5.3 EfficientDet for Semantic Segmentation5.4 Ablation Study 6 Conclusio…

RocketMQ你不得不了解的 Rebalance机制源码分析

这里是weihubeats,觉得文章不错可以关注公众号小奏技术&#xff0c;文章首发。拒绝营销号&#xff0c;拒绝标题党 RocketMQ版本 version: 5.1.0 RocketMQ中consumer消费模型 在了解RocketMQ的Rebalance机制之前&#xff0c;我们必须先简单了解下rocketmq的消费模型 我们知道…

chatgpt赋能Python-left函数_python

Left 函数在Python中的使用及其优点 在Python编程语言中&#xff0c;字符串处理是不可避免的任务。Python提供了许多内置函数来处理字符串&#xff0c;其中left()函数是其中一个非常重要的函数。本文将介绍left()函数的用法、优点和一些实例&#xff0c;以便更好的理解该函数。…

redis高级篇(2)---主从

一)搭建主从架构: 单节点Redis的并发能力是有限的&#xff0c;所以说要想进一步提高Redis的并发能力&#xff0c;就需要搭建主从集群&#xff0c;实现读写分离&#xff0c;因为对于Redis来说大部分都是读多写少的场景&#xff0c;更多的要进行读的压力&#xff0c;最基本都要是…

【Android学习专题】java基本语法和概念(学习记录)

学习记录来自菜鸟教程 Java 变量 Java 中主要有如下几种类型的变量 局部变量 在方法、构造方法或者语句块中定义的变量被称为局部变量。变量声明和初始化都是在方法中&#xff0c;方法结束后&#xff0c;变量就会自动销毁类变量&#xff08;静态变量&#xff09; 类变量也声…

chatgpt赋能Python-libreoffice_python宏

介绍 LibreOffice是一套免费开源的办公软件&#xff0c;其中包含一个强大的Python宏系统&#xff0c;可以使用Python编写脚本来增强办公软件的功能。本文将介绍LibreOffice Python宏是什么&#xff0c;如何使用Python编写宏&#xff0c;并提供一些示例&#xff0c;以便读者可以…

去付款--支付宝沙箱的简单测试

alipay-demo 进入开发者中心–开发工具–沙箱–设置公钥 搜索电脑网上支付–查看Demo–查看配置类–查看业务逻辑 我们的基础配置类主要是初始化我们的alipay客户端 真正去付款的时候是提交了一个form表单达到一个真正的支付jsp,java代码首先初始化我买的Alipay客户端&#xf…

瑞吉外卖 - 新增分类功能(11)

某马瑞吉外卖单体架构项目完整开发文档&#xff0c;基于 Spring Boot 2.7.11 JDK 11。预计 5 月 20 日前更新完成&#xff0c;有需要的胖友记得一键三连&#xff0c;关注主页 “瑞吉外卖” 专栏获取最新文章。 相关资料&#xff1a;https://pan.baidu.com/s/1rO1Vytcp67mcw-PD…

网安学习踩坑经验篇

回想学习网络安全一年来&#xff0c;踩了不少坑走了不少弯路&#xff0c;在此稍作总结&#xff0c;希望可以帮助那些想要入门 web 安全或者是想打CTF的同学们一些建议 坑点 先总结一下&#xff0c;我在学习中遇到的坑点 只看视频&#xff0c;眼高手低&#xff0c;不练习&…

【嵌入式Linux】设备树基本语法

设备树基本语法 1_总领-本期设备树视频要怎么讲&#xff1f;讲什么&#xff1f;_哔哩哔哩_bilibili 基本的 特殊的 中断控制 描述GIC控制器 时钟 CPU GPIO 个数&#xff0c;保留范围&#xff08;起始、长度&#xff09;&#xff0c;个数对应的名字 GPIO映射-这个脚被用了换一…

chatgpt赋能Python-numpy_归一化

NumPy归一化&#xff1a;理解数据规范化的重要性 什么是归一化&#xff1f; 在数据科学和机器学习中&#xff0c;归一化是预处理数据的一种常用技术。归一化是指将数据缩放到一个特定的范围内&#xff0c;通常是0到1或-1到1之间。 例如&#xff0c;我们可能比较一家医院的三…

渗透测试--5.3.使用john破解密码

前言 由于Linux是Internet最流行的服务器操作系统&#xff0c;因此它的安全性备受关注。这种安全主要靠口令实现。 Linux使用一个单向函数crypt&#xff08;&#xff09;来加密用户口令。单向函数crypt&#xff08;&#xff09;从数学原理上保证了从加密的密文得到加密前的明…

Java笔记_22(反射和动态代理)

Java笔记_22 一、反射1.1、反射的概述1.2、获取class对象的三种方式1.3、反射获取构造方法1.4、反射获取成员变量1.5、反射获取成员方法1.6、综合练习1.6.1、保存信息1.6.2、跟配置文件结合动态创建 一、反射 1.1、反射的概述 什么是反射? 反射允许对成员变量&#xff0c;成…

基于IC5000烧录器使用winIDEA烧写+调试程序(S32K324的软件烧写与调试)

目录 一、iSYSTEM简介二、如何使用iSYSTEM winIDEA烧写调试程序2.1 打开winIDEA&#xff1a;2.2 新建一个Workspace;2.3 硬件配置:2.4 选择CPU芯片型号&#xff1a;2.5 加载烧写文件&#xff1a;2.6 开始烧录程序&#xff1a;2.7 程序调试Debug&#xff1a;2.7.1 运行程序&…

PCL点云处理之单点选择的交互操作(一百六十七)

PCL点云处理之单点选择的交互操作(一百六十七) 一、效果展示二、实现代码一、效果展示 交互选择点,输出点信息,具体如下图所示 二、实现代码 #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/visualization/pcl_visu

Spring Cloud Alibaba(二)Nacos统一配置管理

目录 一、为什么需要配置中心 二、常用的配置中心 Nacos 的几个概念 三、Nacos配置中心的使用 &#xff08;一&#xff09;properties格式 1、导入依赖 2、在配置中心新建配置 3、修改配置文件名为bootstrap.yml 4、在微服务中添加nacos config服务地址的配置 5、测试…

chatgpt赋能Python-numpy精度

Numpy精度介绍 Numpy是一个用于进行科学计算的Python库&#xff0c;它提供了多维数组对象以及一系列用于操作数组的函数。Numpy的广泛使用使其成为数据科学中的重要组成部分。然而&#xff0c;Numpy中的精度问题却常常被忽视。 浮点数精度问题 在Numpy中&#xff0c;浮点数是…

STL与string类的认识及简单使用

STL与string类的认识及简单使用 一、STL二、string类构造函数容量操作访问及遍历操作迭代器 修改操作非成员函数重载关系运算符重载getline 三、总结 一、STL STL(standard template libaray-标准模板库)&#xff1a;是C标准库的重要组成部分&#xff0c;不仅是一个可复用的组…

【计算机毕设】基于SringBoot+Vue的校园二手交易平台(含支付)

在导师的严格指导下&#xff0c;我的毕业设计终于完成了&#xff0c;毕设被推优算是给大学生活画上了圆满的句号&#xff0c;几个月的努力也没白费。在开发的过程中收获了很多&#xff0c;也遇到很多问题&#xff0c;但因怕时间来不及&#xff08;根本不知道截止时间TvT&#x…