【Linux从入门到放弃】进程地址空间

🧑‍💻作者: @情话0.0
📝专栏:《Linux从入门到放弃》
👦个人简介:一名双非编程菜鸟,在这里分享自己的编程学习笔记,欢迎大家的指正与点赞,谢谢!

在这里插入图片描述

进程地址空间

  • 前言
  • 一、地址空间
    • 1.1 地址空间的问题引入
    • 1.2 什么是地址空间?
    • 1.3 地址空间、物理内存的关系
  • 二、地址空间的意义
    • 2.1 防止地址随意访问,保护物理内存与其他进程
    • 2.2 将进程管理和内存管理进行解耦合
    • 2.3 让进程以统一的视角看待自己的代码和数据
    • 扩展1 malloc的本质
    • 扩展2 重新理解地址空间
  • 总结


前言

  在之前的学习当中,我们肯定关于数据的存储有一定的了解,比如全局变量应该存储在什么位置,动态申请的空间又在什么位置等等。此篇博客就关于在Linux环境中对地址空间作以详细讲解。


下图我们之前学习C语言应该了解的空间布局图,那么有一个问题:这块空间是不是我们所说的内存呢?带着这个问题进入到此篇博客中。
在这里插入图片描述

一、地址空间

1.1 地址空间的问题引入

  关于进程地址空间的学习我们先看一段代码:有一个全局变量num,通过fork创建子进程,在父子进程打印全局变量以及它的地址,在父进程里5秒之后对这个全局变量进行修改。

int num=10;
int main()
{int id=fork();assert(id>=0);if(id==0){//子进程while(1){printf("i am child,num:%d,&num:%p\n",num,&num);sleep(1);}}else{//父进程int cnt=5;while(1){printf("i am father,num:%d,&num:%p\n",num,&num);if(--cnt==0)num=20;sleep(1);}}return 0;
}

在这里插入图片描述

父进程对全局数据做修改,并不会影响子进程,这是因为进程具有独立性,而进程的概念是内核数据结构+代码和数据,其实在数据修改的时候发生了写时拷贝。

  通过这段代码的运行结果可以发现:五秒之前,父子进程的num值都是10,当然对应的数据地址也是一样的,但是在5秒之后,父进程的num值发生了改变,可是奇怪的是num对应的地址怎么还是一样的,怎么在同一份地址空间里存放着不同的数据呢?这些显然不可能。
  这也就说明了这里打印出来的地址并不是物理地址,它的名字而是虚拟地址或者线性地址。

1.2 什么是地址空间?

  先讲个小故事,从前有一个大富豪,可谓是富甲一方,假设说有RMB十个亿,一个人有了钱就容易花天酒地,于是就有了好几个私生子,但是这个孩子都是生活在不同的地方,彼此并不知道对方的存在。这个富豪就给这几个孩子说等我到时候没了,我的这些资产都是你的。如果这几个孩子给他老爸要上个几千几万,这个富豪肯定就毫不犹豫地给了,但要是说:“老爸,给我五个亿,我想自己创业办个公司”,这种情况富豪肯定是不会将钱给他孩子的。
  可以这么说,这个富豪给他每个孩子的承诺将相当于是在画饼,而这个饼就是进程地址空间,大富豪是操作系统,资产是内存,那这几个私生子是进程

对于大富豪来说,他给每个孩子画着不同的饼,那么他就应该将这些“饼”管理起来,以便以后更好的去给孩子画饼。而管理的方式就是先描述,再组织。
而饼的本质就是一个内核数据结构,是一个struct mm_struct{}这样的一个结构体。
每当创建了一个进程,相应也会有自己的独一份内核数据结构,进程就会通过一个指针指向这块结构体,对于我们来说看到的“内存地址”其实是这个进程地址空间的某一位的地址—虚拟地址。

  1. 说到三八线我想每一个人都不陌生,可能我们每个人小的时候都和同桌在课桌上画过这样的线,我们当时画三八线的初衷肯定就是限定对方的活动区域,其本质就是区域划分。那在计算机里对一线性区域进行划分指定好区域空间的起始处和终止处即可完成区域的划分。
  2. 地址空间的本质就是线性区域,我们考虑在32位计算机环境下,地址空间的地址大小是从全0到全F(16进制)的,整个地址空间是以一个字节为最小单位划分的。 之所以称这段空间为线性区域是因为地址是线性的,若是十进制的话就是从0到42亿多。

如果说我们定义一个int类型的数据,在地址空间就会占四个字节,按道理来说我们应该有四个地址,但是当对这个变量取地址时就会发现拿到的只是这段空间的起始地址,那仅仅通过这一个地址怎能将本来四个字节所存放的数据拿到呢?其实除了起始地址之外,还有就是这个数据的类型起到关键的作用。

  1. 从最上面的图可以看到,地址空间被划分成了一个个小的区域,这些区域的起始地址和终止地址会存放到mm_struct结构体中,通过这一对一对的地址将地址空间进行了划分。
  2. 如果通过一对对的地址限定了区域,那么这段区域中间存放的是什么东西呢?虚拟地址
  3. 区域的大小调整的本质就是对起始地址和终止地址大小的调整。

1.3 地址空间、物理内存的关系

  我们所看到的地址其实是虚拟地址,而真正的数据是存放在物理内存中的,既然我们能够从这个虚拟地址拿到我们所需要的数据,那就说明了在这两者之间肯定存在这一种映射关系,能够将虚拟地址转化为物理地址,从而拿到所需要的数据,这种映射关系存放在一个叫页表的空间当中,而虚拟地址和物理地址的映射关系相当于K-V关系。
在这里插入图片描述

通过地址空间中的虚拟地址找到页表对应的位置,然后根据页表的映射关系获取到物理地址,从而拿到想要的数据。

  接下来再对那段代码所遗留下来的问题作以解释:每创建一个进程,操作系统都会为每个进程创建所对应的地址空间和页表,而在fork之后,同样会和父进程一样创建内容一样的地址空间和页表,也就是相同的地址和相同的数据,如下图。
在这里插入图片描述
  然而当父进程对全局变量修改之后,就会发生写时拷贝,是怎么回事呢?

  1. 因为进程之间具有独立性,父进程修改数据肯定是不会影响到子进程的,当父进程修改数据就会在物理内存中重新找一块空间将改变后的数据存放其中,然后父进程对应的页表的映射关系就会发生改变。
  2. 当然虚拟地址不会发生变化,变化的只是改变后的数据的物理地址需要在页表中重新映射。
    在这里插入图片描述

从这幅图就能明白为什么相同的地址对应的数据竟然不一样,原因就是我们所看到的地址并不是真正存放数据的地址,是将这个我们所看到虚拟地址经过父子进程不同的页表映射拿到了不同的物理地址,进而获取的数据不同。

  1. 同样,在父子进程的返回值当中也发生了写时拷贝。fork在返回时,父子进程都被创建出来了,而fork函数会return两次,因此表现出了会有两个执行流。而返回的本质就是写入,谁先返回,谁就让操作系统发生写时拷贝。

二、地址空间的意义

2.1 防止地址随意访问,保护物理内存与其他进程

如果没有地址空间的存在,进程就会直接与物理内存打交道,假设磁盘中自己写的A、B代码拿到了物理内存中,那么当执行A代码时因为代码的问题而产生了野指针问题,恰好会对B代码产生影响,显然这样是不安全的,而正是因为地址空间的存在,在与物理内存沟通时得先经过页表的映射,如果有野指针问题或者其他一些会影响其他进程的问题操作系统就会直接终止你的行为,保证你不会影响到其他进程。

举个例子: 一个小孩子过年领了好多压岁钱,但是他对于钱的概念并不是头脑很清晰,有一个良心不好的商家看他是一个孩子,故意高价卖一些东西或者骗这个孩子买了一些他根本不需要的东西,最后他父母知道这件事后认为孩子拿钱不行,于是就给他孩子说:”你的压岁钱妈妈替你保管,你想要买啥你给我妈妈要“,但这个孩子说我要买棒棒糖,那么他妈妈就给了他两块钱去买,但是当他说我想要两千块买个手机玩一玩,那么家长肯定不同意。

在没有家长替孩子保管钱之前就相当于没有地址空间的存在,孩子就可能会乱花钱,而家长的存在就起到了保护的作用。

  1. 除此之外,通过页表进行地址转换的时候从CPU拿到的不仅仅是虚拟地址,还包括这次操作是读还是写。这样就可以在页表转换前在mm_struct中进行操作验证,假设你要访问的是代码区的地址,但是发现其配套的地址是栈区,所以操作系统就会直接拦截你的操作。
  2. 如果在mm_struct这里的虚拟地址是正确的,在页表里的除了进行地址转换,页表中还包含一项为权限标志位,表示该数据是读还是写。
    char *str="hello"; *str='H';这两行的代码目的是想将str对应字符串的’h’改成’H’,但是这样的做法是不可以的,原因就在于通过指针访问的时候会有权限的约束,只读而不可写。

2.2 将进程管理和内存管理进行解耦合

先看扩展1

  1. 在整个地址空间和物理内存的框架中,以页表中间为分隔线,以地址空间为主的为进程管理,以物理内存为主的为内存管理。
  2. 因为有地址空间以及页表的存在,所以进程是不会关心访问物理内存的哪个位置。也就是说无论数据被保存在物理内存哪个位置都是可以的。作为进程来说,我只需要知道虚拟地址即可,具体数据在物理内存哪个位置我不关心,通过页表的地址转换我都是可以在物理内存中找到的。
  3. 如果说没有地址空间,那么数据每次被加载到物理空间的地址会发生改变。因为有了地址空间和页表,无论物理内存的地址怎么变,变得只是页表的映射关系,进程能拿到得地址是不会发生变化的。

2.3 让进程以统一的视角看待自己的代码和数据

先看扩展2

  1. 进程的代码和数据被编译后被加载到物理内存时,每条指令的内容都包含着下一条指令对应的虚拟地址,每当CPU执行完一条指令后都会拿到下条指令的虚拟地址,通过虚拟地址的转换找到对应的物理地址,拿到对应的指令在CPU执行,周而复始。
  2. 可以看出来每次CPU拿到的都是虚拟地址,保证进程每次都通过地址空间来执行下一步的任务。

扩展1 malloc的本质

 通过malloc这样的方式动态申请空间,操作系统会在申请之后立马在物理内存中开辟一段空间给你使用吗?

  1. 在你申请了一段空间之后,站在计算机的角度可以认为你是无法做到立马使用的。如果说在申请之后立马在物理内存中开辟出相应的空间那么必然会导致在你使用这段空间之前会有一段时间窗口,在这段时间里这段空间是无法被别的进程使用,处于闲置状态。
  2. 但是在操作系统的世界里,它是不允许不高效的使用和任何浪费,因此物理内存不会在你申请的时候而是在你需要的时候给你。
  3. 作为操作系统,它真实的做法是在你申请了空间之后,会在地址空间开辟出相应大小的空间,然后再将这块空间的首地址存放在页表的相应位置处,但是其对应的物理地址此时并不在页表中给出,操作系统会在你需要的时候在物理内存中开辟相应的空间,再将对应的物理地址填在页表中。

扩展2 重新理解地址空间

 我们的程序在被编译的时候,没有被加载到内存,那么我们的程序内部有没有地址呢?

答案是有的。

  1. 源代码被编译的时候,就是按照虚拟地址空间的方式对代码和数据早就已经编好了对应的编址。
  2. 当程序被编译后还没有被加载到内存,它会被放在磁盘中。假设有两条指令,其中一条为call指令(call 0x1122),另一条为被call指令( 0x1122 xxxx)。先将这两条指令加载到内存中,之后进程通过虚拟地址、页表转换拿到call指令对应的物理地址的内容至CPU执行,此时进程就拿到了地址0x1122,这个地址是虚拟地址,再次通过页表转换拿到被call指令的物理地址处的内容(xxxx)再去执行。
  3. 每次CPU读到的地址都是虚拟地址。

总结

  虚拟地址空间是一种在操作系统内部为进程创建出来的一种具体的数据结构对象,用来让进程统一的视角看待物理内存,因为有了虚拟地址空间的存在,可以让内存管理和进程管理独立开来,并且因为有地址空间的存在,我们在磁盘中编译程序时,就可以把编译好的程序以地址空间的方式排布好,这样当程序加载到内存时,CPU在进行读取识别时拿到的地址都是虚拟地址,再通过页表地址转换找到对应程序并且拿到下条指令的虚拟地址。

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

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

相关文章

如何更换OpenHarmony SDK API 10

OpenHarmony社区已经发布OpenHarmony SDK API 10 beta版本,有些 Sample案例 也有需要API10。那么如何替换使用新的OpenHarmony SDK API 10呢?本文做个记录。 1、如何获取OpenHarmony SDK 1.1 每日构建流水线 可以从OpenHarmony每日构建站点获取最新的…

【网络安全的神秘世界】已解决Failed to start proxy service on 127.0.0.1:8080

🌝博客主页:泥菩萨 💖专栏:Linux探索之旅 | 网络安全的神秘世界 | 专接本 | 每天学会一个渗透测试工具 解决burpsuite无法在 127.0.0.1:8080 上启动代理服务端口被占用以及抓不到本地包的问题 Burpsuite无法启动proxy…

定个小目标之刷LeetCode热题(25)

这道题采用的解法是桶排序&#xff0c;画草图如下 代码如下 //基于桶排序求解「前 K 个高频元素」 class Solution {public int[] topKFrequent(int[] nums, int k) {HashMap<Integer, Integer> map new HashMap();for (int num : nums) {if (map.containsKey(num)) {m…

【安防天下】模拟视频监控系统——模拟监控系统的构成视频采集设备

文章目录 1 模拟监控系统的构成2 视频采集设备2.1 摄像机相关技术2.1.1 摄像机的工作原理2.1.2 摄像机的分类2.1.3 摄像机的主要参数 2.2 镜头相关介绍2.2.1 镜头的主要分类2.2.2 镜头的主要参数 1 模拟监控系统的构成 模拟视频监控系统又称闭路电视监控系统&#xff0c; 一般…

htb_Blurry

端口扫描 80 按照教程注册安装clear ml 加载configuration的时候会报错 将json里的API&#xff0c;File Store的host都添加到/etc/hosts中 即可成功初始化 查找clear ml漏洞 发现一个cve-2024-24590 下面是一个利用脚本&#xff0c;但不能直接用 ClearML-vulnerability-…

好用的linux一键换源脚本

最近发现一个好用的linux一键换源脚本&#xff0c;记录一下 官方链接 大陆使用 bash <(curl -sSL https://linuxmirrors.cn/main.sh)# github地址 bash <(curl -sSL https://raw.githubusercontent.com/SuperManito/LinuxMirrors/main/ChangeMirrors.sh) # gitee地址 …

Linux基础命令大全(详解版)

Linux基础命令&#xff08;详解版&#xff09; 文章目录 Linux基础命令&#xff08;详解版&#xff09;1.Linux的目录结构**2.Linux路径的描述方式**3.Linux命令基础格式4.ls命令 隐藏文件、文件夹5.pwd命令6.cd命令 特殊路径符7.mkdir命令 文件操作命令8.touch命令9.cat命令10…

英伟达中国特供芯片降价背后:巨头与市场的较量

英伟达&#xff0c;这家曾经在人工智能芯片领域独领风骚的巨头&#xff0c;近期在中国市场遭遇了一些挑战。为了应对来自华为等中国本土企业的竞争&#xff0c;英伟达不得不采取降价策略&#xff0c;调整其专为中国市场打造的H20芯片价格&#xff0c;甚至低于华为的同类产品。这…

S级猫主食冻干测评出来了:希喂、K9、朗诺实测分享

对于许多宠物主人来说&#xff0c;一到挑选主食冻干就头疼。尽管主食冻干为猫咪带来的益处远超过普通猫粮&#xff0c;但其价格也相对较高。因此&#xff0c;许多宠物主人担心高价购买的主食冻干营养价值并不高。实际上&#xff0c;除了营养&#xff0c;安全性和配方也是选购时…

【K8s】专题五(5):Kubernetes 配置之热更新工具 Reloader

以下内容均来自个人笔记并重新梳理&#xff0c;如有错误欢迎指正&#xff01;如果对您有帮助&#xff0c;烦请点赞、关注、转发&#xff01;欢迎扫码关注个人公众号&#xff01; 目录 一、基本介绍 二、工作原理 三、部署方法 四、使用方法 一、基本介绍 Reloader 是一个用…

clickhouse学习笔记(四)库、表、分区相关DDL操作

目录 一、数据库操作 1、创建数据库 2、查询及选择数据库 3、删除数据库 二、数据表操作 1、创建表 2、删除表 3、基本操作 ①追加新字段 ②修改字段类型或默认值 ③修改字段注释 ④删除已有字段 ⑤移动数据表&#xff08;重命名&#xff09; ⑥清空表 三、默认值…

双层循环和循环语句

echo 打印 echo -n 表示不换行输出 echo -e 表示输出转义字符 echo \b 相当于退格键&#xff08;backspace&#xff09; echo \n 换行&#xff0c;相当于回车 echo \f 换行&#xff0c;换行后的新行的开头连着上一行的行尾 echo \t 相当于tab健 &#xff08;…

华测监测预警系统2.2 UserEdit.aspx SQL注入致RCE漏洞复现(CVE-2023-5827)

0x01 产品简介 华测监测预警系统2.2是一套针对地质灾害监测预警的科学、完善平台,实现了地质灾害防治管理的科学化、信息化、标准化和可视化。该系统由上海华测导航技术有限公司开发,主要服务于山体滑坡、地裂缝等地质灾害的自动化预警。 0x02 漏洞概述 华测监测预警系统2…

【IDEA】扩大虚拟机栈空间操作

输入命令参数-Xss 要更改的空间大小

查询mysql库表的几个语句

1、查询某个数据库的所有表 SELECTtable_name FROMinformation_schema.TABLES WHEREtable_schema database_namedatabase_name替换成你需要查询的数据库名称 2、查询某张表的所有字段名称 SELECTCOLUMN_NAME,column_comment FROMinformation_schema.COLUMNS WHEREtable…

LeetCode 1164, 125, 94

目录 1164. 指定日期的产品价格题目链接表要求知识点思路代码 125. 验证回文串题目链接标签简单版思路代码 复杂版思路代码 94. 二叉树的中序遍历题目链接标签递归思路代码 迭代思路代码 1164. 指定日期的产品价格 题目链接 1164. 指定日期的产品价格 表 表Products的字段为…

AI 编译器技术分享会:上海交大/中科院计算所/微软亚研/智源,他们来了!

4 场 Meetup、3 个城市、19 位嘉宾、1k 行业从业者、累计 100w 曝光&#xff0c; 2023 年 AI 编译器社区小小刷新了一下存在感&#xff0c;我们在非常细分的领域里找到了最为垂直的开发者和工程师&#xff0c;从 0 到 1 建立起一个个小据点&#xff0c;搭建交流平台、促成企内合…

MySQL功能测试-之应用工程

MySQL功能测试-之应用工程 前言pom.xmlapplication.yml 文件common.vo 包ResultVO config 包properties 包DruidConfigPropertyDruidMonitorProperty AutoFillMetaObjectHandlerDruidConfigFluxConfigurationMyBatisPlusConfig controller 包ClientControllerDruidControllerWe…

操作系统之如何使用C语言完成SFJ和SRTJ,并完成他的甘特图

目录 前言 SFJ SRTJ 结束语 前言 不知不觉已经写博客一个月了&#xff0c;前段时间因为学业上的一些原因咕咕咕了&#xff0c;今天我又回来了。今天我给大家带来的是C语言代码完成的SFJ和SRTJ&#xff0c;并且带大家描述他的甘特图。如果有对SFJ和SRTJ不了解的小伙伴可以翻…

Flink Sql Redis Connector

经常做开发的小伙伴肯定知道用flink连接redis的时候比较麻烦&#xff0c;更麻烦的是解析redis数据&#xff0c;如果rdis可以普通数据库那样用flink sql连接并且数据可以像表格那样展示出来就会非常方便。 历时多天&#xff0c;我终于把flink sql redis connector写出来了&…