Linux:程序地址空间详解

目录

一、堆、栈、环境参数所在位置

二、进程地址空间底层实现原理

​编辑

三、什么是地址空间

四、为什么要有进程地址空间

五、细谈写实拷贝的实现及意义


e5b3d563b2d74e598930bb9339ca987d.png

在C/C++学习中,都学习过如上图所示的一套存储结构,我们大致知道一般存储空间分为堆区,栈区,静态区等,可我们对其构成和实现原理并不理解,而今天博主将通过Linux基于xshell平台来对其进行深度刨析。

一、堆、栈、环境参数所在位置

5ef52bd098f8440cacc9b022fb60dcfd.png

我们可以通过一段代码来验证上图所划分区域的准确性进行验证:使用makefile编译myenv然后运行

536bd40f6f524931becd80e2e9fab28f.png

78f59c2fdf0c4ac98b14b70c8ff7558f.png

 在Linux环境下,我们可以很清晰的看到不同类型的数据的存储位置,而在VS下却不一定遵守此套规则,因为在Windows环境下,考虑到安全性会对地址进行随机化,而且每次打印出的地址都不一样,为了防止代码和数据被固定编译到某个地方。

二、进程地址空间底层实现原理

6a4eeba7ef99424792a8ac6e765ebad7.png

34c7e36c643d4eb59df1221055529447.png

父子进程内容不一样,地址却一样?从物理层面上来讲这是不可能的,同一块地址却有两个值,此时只能说明,此地址不是物理地址而是虚拟地址。所以我们日常编写代码过程中使用的地址也都是虚拟地址!!

所以上图展示出的所谓的内存分布结构,也不是物理层面的结构,而是由虚拟地址所构建出的结构,而它正确的叫法叫做进程地址空间

通过之前的了解我们可以知道,在进行fork创建子进程时,子进程会继承父进程的代码 数据和属性信息,所以子进程和父进程中都会存在一个名为g_val的一个全局变量,而在子进程对其进行修改之前父子进程中的g_val在进程地址空间中都是同一块空间即他们的虚拟地址都是一样的,而此时每个进程PCB即task_struct中都会有一张表(页表)来将代码中数据的虚拟地址通过该表来映射到真实的物理地址,可以理解为一个数组,g_val的虚拟地址为数组下标,而下标所对应的元素中存的值就是真实的物理地址。

而子进程在创建时也会继承父进程中的这张表,在子进程要对g_val的值进程更改时,遵循进程之间相互独立互不影响的原则,此时就会进行写时拷贝,操作系统会在真实的物理内存中新开辟一块空间然后将原本在哈希表中g_val下标所存的地址所指向的物理内存中的值进行拷贝然后修改为200,然后再将g_val虚拟地址为下标的元素中的值更换为这个新开辟的物理内存的地址,此时就完美实现了父子进程中同一变量却存着不一样的值的效果!

所以父子进程对g_val进行访问时通过不同的映射关系找到不同的物理内存。

所以这也是fork指令后,父子进程明明是同一段代码,却能使用getpid赋值给同一个变量不同值的原因。这也使得我们可以getpid后通过获取到的id值来对父子进程进行分流从而时父子进程实现不同的功能。以此为切入点,我们详细刨析地址空间。

三、什么是地址空间

每一个进程都会存在一个进程地址空间。操作系统要对其进行管理依旧是遵循先描述再组织的原则。

所以进程地址空间的本质是数据结构,具体到进程中,就是特定数据结构的对象。

 53dc3a539c4a40859e9659acc3e1b4e0.png

所以操作系统每创建一个进程就创建一个地址空间,内部用next指针链接起来,所以对进程地址空间的管理就变成了对链表的增删查改。

b127ed3400db44daafa6c50f068aa91e.png

每个进程创建的时候都会存在一个struct task_struct,而每个task_struct中都存在一个指针指向对应的进程地址空间。所以进程和地址空间的关系就是数据结构之间的关系。 

所以在设计进程的地址空间时,如何将地址空间进行区域划分呢?

f3505af53df64be8a4d6560d877b211a.png

直接上源码:

cb28dc2e359d4cc8a0a27870c2ec3676.png

e6717654e3ac4d7fb64df89bd1553985.png

可以很清晰的看到,在linux内核中存在一个struct,里面存放着各个区域在内存中开始和结束的位置 ,即大量的start/end。所以对应的地址空间本质上是进程的一种数据结构。

所以当操作系统创建进程时,下图的数据结构就会被进程创建出来,然后将里面的字段进行初始化。所以每个进程都有自己的mm_struct.。里面有着该进程的各种信息。

e5b3d563b2d74e598930bb9339ca987d.png

而空间划分本质就是区域内的各个地址都可以使用。而结构体中的地址空间是虚拟的,本身并不具有代码和数据保存能力,代码和数据必须存放在物理内存中,所以就必须将虚拟地址(线性地址)转化成为物理内存中,这时就需要用到我们上文中所说的那张表来进行映射,这张表就叫做页表。

048cd30d97b148899c1b8200fe2e31e0.png

一个进程在启动时,先创建PCB再创建mm_struct即进程地址空间,然后将磁盘中的可执行程序(代码和数据)加载到物理内存当。随后mm_struct中的正文代码数据等都会通过页表转换到物理地址。

 而完成页表映射,并去页表进行查找的工作都是由CPU完成的,在CPU中有个mmu集成硬件专门用来转换工作,其中有一个名为CR3的寄存器,寄存器指向页表的起始位置,当CPU执行代码需要去内存中查找变量或者存储变量时,mmu会通过CR3会去页表中进行查找,然后找到真实物理地址,注意:CR3中保存的地址就是页表的物理地址,因为虚拟地址需要CR3来进行页表查找,它内部存的如果是页表的虚拟地址,就无法转化出页表的物理地址,经典的蛋生鸡,鸡生蛋的问题。

虚拟地址是给进程的/给用户的!

经过页表的映射,就成功的将进程的管理和内存的管理成功的分离开,这样操作系统在对进程进行管理时,就直接给予虚拟地址所构建出的上图的数据结构进行管理就可以,而对物理内存,也不需要担心存储不连续导致的一系列问题,即使一串代码分开存储,在页表中页可以通过映射来将它们放到一起映射到同一虚拟地址中。

再回头看父子进程创建的整个过程就显得透明清晰了。

四、为什么要有进程地址空间

1.将物理内存从无序变有序,让进程以统一的视角,看待内存

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

3地址空间+页表是保护内存安全的重要手段,如果访问内存的请求合理就通过页表去访问物理内存,如果访问请求非法就及时拦截。

所以在日常我们越界访问或者出现野指针时,就会出现报错,但这种非法访问并不会导致操作系统和程序崩溃,因为拦截此此次访问操作的是地址空间。以此保护了内存。

上面的图就足矣说名问题,同一个变量,地址相同,其实是虚拟地址相同,内容不同其实是被映射到了不同的物理地址!

 

4.地址空间的存在可以优化内存申请分配的方式

操作系统,一定要为效率和资源使用率负责。

比如我们在new或者malloc的时候,操作系统并不会直接去物理内存开辟新的空间,而是在进程的虚拟地址空间中申请,所以,申请内存的本质是在地址空间中申请

因为申请空间并不代表要马上使用空间,当空间申请时,操作系统先在虚拟地址空间中分配对应大小的空间,当要实际去进行存储和使用时,再去通过页表建立映射关系在物理内存中开辟空间。开辟虚拟地址空间时去建立映射和使用时再去建立映射本质上没有太大差别,使用时再去建立映射反而有以下两点好处:

1、充分保证内存的使用率

2、提升new或malloc的速度

五、细谈写实拷贝的实现及意义

 同样遵循上述规律,子进程在刚开始被创建时,虽然有自己的页表mm_struct和pcb,但在物理内存上并没有创建新的内存空间来拷贝父进程的数据而是和父进程指向相同的空间,当子进程要去修改数据时再进行写实拷贝。

而写实拷贝的应用场景不止是对数据进行更改,还有可能进行增删查等工作。

而页表也并不只有虚拟地址和物理地址两个部分,还有一个权限位,所以对以上代码对str进行更改时就需要去页表进行映射,而“hello linux”作为常量字符串具有常性,不能被修改,而编译器是怎么知道不能修改的呢?不加const的问题也存在与此,加了const以后编译时直接会在编译时报错,而不加是在运行时报错,加了const相当于将运行时的报错进行提前,而运行时的报错藏的是比较深的有时甚至不会被发现。所以加cosnt属于防御性编程。

所以根据示例可以知道,页表也是有权限的,当加const时权限位就变成只读权限。

所以如上图,我们fork创建子进程时,默认的代码段和数据段都是r只读权限,所以在读的时候操作系统就不会触发任何错误,直接通过页表来对内存进行读取,而当我们尝试修改时,因为此时页表中的权限位是r权限,此时就会引发操作系统来进行处理,而我们也将此情况称为缺页中断。

此时引发缺页中断时,操作系统作为管理者就会进行写实拷贝。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据结构:归并排序

归并排序 时间复杂度O(N*logN) 如果两个序列有序,通过归并,可以让两个序列合并后也有序,变成一个有序的新数组 对于一个数组,如果他的左右区间都有序,就可以进行归并了 归并的方法 将数组的左右两个有序区间比较,每次都取出一个最小的,然后放入临时数组(不能在原数组上修改…

纯小白蓝桥杯备赛笔记--DAY8(必备排序算法)

冒泡排序 算法思想 每次将最大的一下一下地运到最右边&#xff0c;然后确定这个最大的&#xff0c;接着可以发现该问题变成一个更小的子问题。具体操作&#xff1a;从左向右扫描&#xff0c;如a[i]>a[i1]&#xff0c;执行swap操作。代码格式 #include<bits/stdc.h> …

Mamba: Linear-Time Sequence Modeling with Selective State Spaces(论文笔记)

What can I say? 2024年我还能说什么&#xff1f; Mamba out! 曼巴出来了&#xff01; 原文链接&#xff1a; [2312.00752] Mamba: Linear-Time Sequence Modeling with Selective State Spaces (arxiv.org) 原文笔记&#xff1a; What&#xff1a; Mamba: Linear-Time …

双非本,拿到美团测开实习了——经验分享

前言 最近是春招、暑期实习的高峰期&#xff0c;自己也凭借着持续的准备和一部分运气&#xff0c;较早拿到了美团的测开暑期实习。 以前接到美团的短信&#xff0c;都是外卖送达的通知&#xff0c;没想到自己有一天&#xff0c;也能收到offer录用的通知。虽然是测试开发的岗位…

考研数学|《1800》+《660》精华搭配混合用(经验分享)

肯定不行&#xff0c;考研数学哪有这么容易的&#xff01; 先说说这两本习题册&#xff0c;李永乐老师推出的新版660题&#xff0c;相较于18年前的版本&#xff0c;难度略有降低&#xff0c;更加适合初学者。因此&#xff0c;对于处于基础阶段的学习者来说&#xff0c;新版660…

2、Cocos Creator 下载安装

Cocos Creator 从 v2.3.2 开始接入了全新的 Dashboard 系统&#xff0c;能够同时对多版本引擎和项目进行统一升级和管理&#xff01;Cocos Dashboard 将做为 Creator 各引擎统一的下载器和启动入口&#xff0c;方便升级和管理多个版本的 Creator。还集成了统一的项目管理及创建…

算法之并查集

并查集&#xff08;Union-find Data Structure&#xff09;是一种树型的数据结构。它的特点是由子结点找到父亲结点&#xff0c;用于处理一些不交集&#xff08;Disjoint Sets&#xff09;的合并及查询问题。 Find&#xff1a;确定元素属于哪一个子集。它可以被用来确定两个元…

2024年AI大模型基础设施栈市场地图

2024年大模型(LLM)基础架构的组件和工具,最近看到国外的一篇深度分析,技术负责人可以重点关注:附带图谱: 一、现代LLM基础设施栈定义 根据Menlo Ventures的数据,2023年企业在现代AI基础设施栈上的支出超过11亿美元,成为生成式AI中最大的新市场,为初创企业提供了巨大的…

[OpenCV学习笔记]Qt+OpenCV实现图像灰度反转、对数变换和伽马变换

目录 1、介绍1.1 灰度反转1.2 图像对数变换1.3 图像伽马变换 2、效果图3、代码实现4、源码展示 1、介绍 1.1 灰度反转 灰度反转是一种线性变换&#xff0c;是将某个范围的灰度值映射到另一个范围内&#xff0c;一般是通过灰度的对调&#xff0c;突出想要查看的灰度区间。 S …

基于Springboot旅游网站管理系统设计和实现

基于Springboot旅游网站管理系统设计和实现 博主介绍&#xff1a;多年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐留言 文末获取源码联系…

下拉选中搜索angularjs-dropdown-multiselect.js

需要引入angularjs-dropdown-multiselect.js 页面 <div ng-dropdown-multiselect"" options"supplierList_data" selected-model"supplierList_select" events"changSelValue_supplierList" extra-settings"mucommonsetti…

python中pow()函数的使用

在Python中&#xff0c;pow() 函数用于计算指定数字的幂。它的语法如下&#xff1a; pow(x, y) 这个函数返回 x 的 y 次方。相当于 x**y。 pow() 函数也可以接受一个可选的第三个参数&#xff0c;用于指定一个取模值&#xff0c;即计算结果与该模值的余数。其语法如下&#…

ping的基础指令

-t Ping 指定的主机&#xff0c;直到停止。 若要查看统计信息并继续操作&#xff0c;请键入 CtrlBreak&#xff1b; 若要停止&#xff0c;请键入 CtrlC。 -a 将地址解析为主机名。 -n count 要发送的回显请求数。 -l size 发送缓冲…

[已解决]Vue3+Element-plus使用el-dialog对话框无法显示

文章目录 问题发现原因分析解决方法 问题发现 点击按钮&#xff0c;没有想要的弹框 代码如下 修改 el-dialog到body中&#xff0c;还是不能显示 原因分析 使用devtool中vue工具进行查看组件结构 原因在于&#xff0c;在一个局部组件(Detail->ElTabPane->…)中使用…

动态规划相关题目

文章目录 1.动态规划理论基础2.斐波那契数3.爬楼梯4.使用最小花费爬楼梯5.不同路径6.不同路径 II7. 整数拆分8. 不同的二叉搜索树 1.动态规划理论基础 1.1 什么是动态规划? 动态规划&#xff0c;英文&#xff1a;Dynamic Programming&#xff0c;简称DP&#xff0c;如果某一…

c语言中的联合体和枚举

这篇文章总结一下c语言中的联合体和枚举。看看这两个东西到底是什么。大家一起学习。 文章目录 一、联合体1.联合体类型的声明。2.联合体的大小。3.相同成员的结构体和联合体对比4.联合体大小的计算。 二、枚举类型1.枚举类型的声明。2.枚举类型的优点。枚举类型的使用。 一、联…

gitee规范团队 代码提交

1.团队开会规范 使用 插件 &#xff1a; git Commit Message Helper 插件进行代码提交前规范 2.gitee代码仓库断控制&#xff0c;上面只是规范了程序员开发端&#xff1b;但是gitee也要管理控制&#xff1b;正则根据每个公司的不同来进行。

民航电子数据库:CAEMigrator迁移数据库时总是卡死

目录 一、场景二、异常情况三、排查四、应急方案 一、场景 1、对接民航电子数据库 2、将mysql数据库迁移到cae数据库 3、使用CAEMigrator迁移工具进行数据库迁移时&#xff0c;该工具会卡死&#xff08;不清楚是否是部署cae服务的服务器资源导致&#xff09; 二、异常情况 …

iOS - Runloop介绍

文章目录 iOS - Runloop介绍1. 简介1.1 顾名思义1.2. 应用范畴1.3. 如果没有runloop1.4. 如果有了runloop 2. Runloop对象3. Runloop与线程4. 获取Runloop对象4.1 Foundation4.2 Core Foundation4.3 示例 5. Runloop相关的类5.1 Core Foundation中关于RunLoop的5个类5.2 CFRunL…

报错[Vue warn]: $listeners is readonly. $attrs is readonly.怎么解决?

代码也没有逻辑错误&#xff0c;但是报错 [Vue warn]: $listeners is readonly. $attrs is readonly. 情况1&#xff1a;多处声明了new Vue&#xff0c;解决方案&#xff1a;删除一个&#xff0c;用全局变量引用同一个Vue 情况2&#xff1a;import Vue from Vue;第二个Vue首字…