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.hqwc.cn/news/579000.html

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

相关文章

社交网络的未来:Facebook如何塑造数字社交的下一章

引言 社交网络已成为我们生活中不可或缺的一部分,而Facebook作为其领军者,一直在塑造着数字社交的未来。本文将深入探讨Facebook在未来如何塑造数字社交的下一章,并对社交网络的发展趋势进行展望和分析。 1. 引领虚拟社交的潮流 Facebook将…

建立一个简单的网页音乐盒模型效果#css#h5

“音乐盒”可以看做一个大盒子&#xff0c;用<div>标签进行定义。大盒子的上面为文本内容&#xff0c;可以在<div>标签中嵌套<h2>和<p>标签来实现&#xff1b;大盒子下面为图像&#xff0c;通过在<div>标签中嵌套<img/>标签来实现。 样式…

R语言实现——网状 Meta 分析

近来年&#xff0c;网状 Meta 分析相关研究不断涌现&#xff0c;此类研究不但能发表在国内各大核心期刊上&#xff0c;还能在SCI期刊甚至医学4大刊上看到其身影。随手在pubmed上面一搜索&#xff0c;就能得到一万多篇相关文献。俨然成为医学文献研究的“大杀器”&#xff01; P…

2024批量下载微博内容导出excel,数据包含微博链接,内容,点赞数,转发数,评论数,话题等

以歌手李健这个号为例&#xff0c;共抓取727条微博&#xff0c;导出的excel微博数据包含微博链接,微博正文,原始图片链接,被转发微博原始图片链接,是否为原创微博,微博视频链接,发布位置,发布时间,发布工具,点赞数,转发数,评论数,话题等 第一条微博发布于2010年5月31 。 再根据…

C++进阶,手把手带你学继承

&#x1fa90;&#x1fa90;&#x1fa90;欢迎来到程序员餐厅&#x1f4ab;&#x1f4ab;&#x1f4ab; 主厨&#xff1a;邪王真眼 主厨的主页&#xff1a;Chef‘s blog 所属专栏&#xff1a;c大冒险 总有光环在陨落&#xff0c;总有新星在闪烁 【本节目标】 1.继…

《HelloGitHub》第 96 期

兴趣是最好的老师&#xff0c;HelloGitHub 让你对编程感兴趣&#xff01; 简介 HelloGitHub 分享 GitHub 上有趣、入门级的开源项目。 https://github.com/521xueweihan/HelloGitHub 这里有实战项目、入门教程、黑科技、开源书籍、大厂开源项目等&#xff0c;涵盖多种编程语言 …

vue中使用图片url直接下载图片

vue中使用图片url直接下载图片 // 下载图片downloadByBlob(url, name) {let image new Image()image.setAttribute(crossOrigin, anonymous)image.src urlimage.onload () > {let canvas document.createElement(canvas)canvas.width image.widthcanvas.height image…

如何在 Linux 中查找命令的执行时间

在 Linux 操作系统中&#xff0c;查找命令的执行时间对于优化系统性能、调试程序以及评估脚本效率至关重要。本文将介绍几种方法来准确地测量命令的执行时间。 使用时间命令 时间命令&#xff08;time&#xff09;是一个内置的 shell 命令&#xff0c;用于测量其他命令或程序的…

谷歌浏览器如何查看HTTP版本

在谷歌浏览器中查看HTTP版本&#xff0c;你可以按照以下步骤操作&#xff1a; 首先&#xff0c;你需要打开谷歌浏览器&#xff0c;然后访问你想要查看HTTP版本的网页&#xff1b; 在页面上&#xff0c;按下F12键或右键点击页面任意位置&#xff0c;选择 “检查”&#xff08;I…

Leetcode 剑指 Offer II 071.按权重随机选择

题目难度: 中等 原题链接 今天继续更新 Leetcode 的剑指 Offer&#xff08;专项突击版&#xff09;系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~ 题目描述 给定一个正整数数组 w &#xff0c;其中 w[i] 代表下标 i 的权重…

基于java+springboot+vue实现的电商个性化推荐系统(文末源码+Lw+ppt)23-389

摘 要 伴随着我国社会的发展&#xff0c;人民生活质量日益提高。于是对电商个性化推荐进行规范而严格是十分有必要的&#xff0c;所以许许多多的信息管理系统应运而生。此时单靠人力应对这些事务就显得有些力不从心了。所以本论文将设计一套电商个性化推荐系统&#xff0c;帮…

nvidia-smi查看无进程,但GPU占用率100%问题解决

问题&#xff1a;nvidia-smi查看无进程&#xff0c;但GPU占用率100%问题解决 原因&#xff1a;记住记住记住CtrlZ是把当前运行程序挂起&#xff0c;并不是终止运行&#xff0c;终止用CtrlC,前段时间跑代码测性能和看部分结果一直用的CtrlZ&#xff0c;导致程序都处于挂起状态&…