Linux系统编程:详解进程地址空间

news2024/12/24 0:12:28

目录

一. 进程空间的布局

二. 进程地址空间

2.1 早期CPU访问物理内存的方式

2.2 什么是虚拟地址(进程地址空间)

2.3 操作系统对地址空间的管理方法

三. 地址空间存在的意义

四. 总结


一. 进程空间的布局

在语言层面学习C/C++时,根据变量/对象类型的不同,我们画出了如图1.1的空间布局图,从高地址到低地址,每个区域所代表的意义为:内核空间(用户无法使用)、命令行参数和环境变量区、栈区、堆区、静态区(包括未初始化全局数据区和已初始化全局数据区)和全局代码区(包括代码和只读常量)。

图1.1 进程空间的布局情况

提问:图1.1所示的空间布局图,是真正的物理内存布局吗?

答案可能出乎意料,其实不是的。我们写了代码1.1来进行验证,在全局定义变量int g_val = 10,然后使用fork创建父子进程,在子进程和父进程中打印g_val的地址和值,在子进程中修改g_val的值为20,运行代码,我们发现,父子进程的g_val地址相同,但是值不同。为此,我们可以判定:父子进程中的g_val虽然在代码中看到相同的地址,但是,他们实际被保存在不同的物理内存地址。

结论:Linux下使用的是虚拟地址而不是物理地址,虚拟地址需要转换为适当的物理地址才能被正确使用。

代码1.1:验证OS是否直接使用物理内存

#include<iostream>    
#include<unistd.h>    
    
int g_val = 10;    
    
int main()    
{    
    pid_t ret = fork();    
    
    if(ret < 0)  //子进程创建失败    
    {    
        perror("fork");    
        return 1;    
    }    
    else if(ret == 0)  //子进程    
    {    
        g_val = 20;    
        while(1)    
        {    
            printf("child process, &g_val:%p, g_val:%d\n", &g_val, g_val);    
            sleep(1);    
        }    
    }    
    else  //父进程代码    
    {    
        while(1)    
        {    
            printf("child process, &g_val:%p, g_val:%d\n", &g_val, g_val);    
            sleep(1);    
        }    
    }                                                                                                                                                                                                                                                                         
}   
图1.1 代码1.1的运行结果

造成这种现象的原因是,现代的OS均不是采用直接访问物理内存的方式,而是给每个进程都分配了地址空间,通过映射,将虚拟地址对应到物理地址,从而实现对物理地址的访问。

二. 进程地址空间

2.1 早期CPU访问物理内存的方式

在早期,CPU直接通过实际内存的物理地址,访问物理内存空间,OS会为每个进程都在内存中分配一块实际的内存空间,CPU拿到的指令和数据的地址,也直接就是物理内存的地址。

图2.1 早期CPU访问物理内存的方式

这样直接访问内存的方式,存在一个严重的安全隐患:

  • 我们可以在一个进程中,通过定义指针,越界访问其他的进程,这样就很容易对其他进程进行修改,或者拿走其他进程的数据,具有很大的安全隐患。

为此,现代操作系统采用了 进程地址空间(虚拟地址) + 页表映射的方法,来间接访问物理内存。

2.2 什么是虚拟地址(进程地址空间)

如图2.2所示,OS会为每个进程分配一块虚拟地址空间,这块虚拟地址空间的分布遵循图1.1所示的分布规律,这就好像每个进程独占内存空间。但是,地址空间终究不是实际的物理内存地址,CPU在拿到虚拟地址,需要通过一定的映射关系将虚拟地址转换为实际的物理地址,然后CPU根据转换的结果去访问物理内存中的数据和代码。

虚拟内存到物理内存的转换是通过页表来实现的,页表中类似于哈希表结构,存有进程地址(虚拟地址)和物理内存的对应关系,当然页表中也存有内存的访问权限相关信息(可、可写、可执行)。

根据以上的分析结论,我们就不难理解,为什么代码1.1中,父子进程的g_val打印出不同的值,但却有相同的地址。因为我们通过&g_val看到的仅仅是OS分配给当前进程的地址空间中的虚拟地址,并不是实际的内存地址。父进程和子进程有各自的页表,通过页表将各自的&g_val进行映射转换之后,我们就能得到不同的实际地址。

结论:父子进程的g_val只是虚拟地址相同,实际存储在物理内存中的地址不同。

图2.2 虚拟地址

2.3 操作系统对地址空间的管理方法

(1)如何理解区域划分?

区域划分,类似于小时候一张桌子和同桌画的38线,它规定了每个人所享有的空间范围,一般采用[start, end]来表示区域划分。如图2.3所示,假设一张桌子厂100cm,A同学的范围为[0,50cm],B同学为[51cm, 100cm],这就是一种简单地区域划分方法。图2.2右侧展示了一种可以用于进行边界管理的结构体类型。

图2.3 区域划分

根据图1.1所示的地址空间结构,我们认为每一块区域都会被特定的[start, end]来限制,当区域的范围发生变化时,通过更正start/end的值,就可以实现区域范围的更新。

(2)地址空间的管理方法

地址空间,本质上也是一种操作系统内核的数据结构,它里面至少包括每个区域之间的划分(见代码2.1)。同时,为了保证每个进程之间的独立性,OS会为每一个进程都分配一份地址空间和一张页表,如果我们企图访问非法的地址,那么在页表映射的时候,就会将我们的非法访问请求拦截下来,这样就无法在一个进程中访问另一个进行,保证了进程的独立性和安全性。

代码2.1:地址空间的内核数据结构 

struct mm_struct    
{    
    int code_start;    
    int code_end;    
    
    int init_start;    
    int init_end;    
    
    int heap_start;    
    int heap_end;    
    
    int stack_start;    
    int stack_end;    
    
    ... ...    
    //其他属性信息    
} 

我们知道,操作系统会对给每个进程创建一个进程控制块PCB,PCB中会记录对应进程的各种属性信息,其中就包括指向管理地址空间的结构体mm_struct的指针,图2.4展示了这种管理方式。

图2.4 地址空间的管理

 (3)虚拟地址的生成

提问:编译器生成的可执行程序中,是否包括地址相关的信息?

答案是包括,可执行程序中包含每条指令的指令的虚拟地址,它们由编译器赋予。实现地址空间(虚拟地址)通过页表与物理地址的映射,并不是单纯地操作系统的工作,而是需要操作系统和编译器协同工作来实现。

如图2.5所示,我们编写的函数中有3条指令,编译器给它们的虚拟地址分别为0x10,0x100和0x200,这些虚拟地址的信息会被包含在可执行程序内,我们通常说的程序运行前要现将代码加载到内存中去,可包括要将编译器赋予每条指令的虚拟地址。

如果我们运行可执行程序,就要为它生成页表,页表中的虚拟地址填的是编译器生成的虚拟地址,通过数据和代码实际在物理内存中存放的位置,填写页表中与虚拟地址对应的物理地址。

我们假设0x10、0x100和0x200对应的物理地址分别为0xA、0xAA和0xAAA,CPU首先拿到虚拟地址0x10,通过页表映射找到0x10对应的物理地址0xA,在0xA位置不但存有需要运行的代码,还包括下一条指令的地址(虚拟地址),CPU下一条指令的虚拟地址,通过页表映射再找到物理内存的地址,从而执行了下一条指令。

图2.5 虚拟地址的生成及CPU对虚拟地址的处理

三. 地址空间存在的意义

1. 保护内存,杜绝非法访问

CPU拿到的内存是虚拟内存,要通过页表,找到虚拟内存实际对应的物理内存,从而访问到实际需要访问的数据。如果用户企图非法访问,那么就会在页表映射这一阶段被拦截,操作系统就会将这个进程杀掉,以包含物理内存中的数据不会被污染。

将地址空间和页表都置于OS的监管之下,安全性大大增强。

2. 实现进程管理和内存管理之间的解耦合

通过页表映射,我们就可以使用物理内存的任何区域来存储进程中的相关数据。这样,我们在管理进程的时候,只需要管理好PCB、地址空间、页表,而不需要再考虑对物理内存的管理。

这样,进程管理和内存管理就实现了解耦合。软件工程的一大原则就是高内聚、低耦合,这样做遵循了这一原则。

3. 提高物理内存资源的利用效率

当我们使用malloc、new申请内存动态内存空间时,如果直接在物理内存上申请,那么如果申请完空间后不立马使用,就会出现内存浪费的问题,这样资源利用效率就会变低。

但是,有了地址空间,malloc、new实际进行的操作就会是在虚拟内存上申请空间,如果申请了内存空间后不立马使用,那么OS会对动态分配的内存空间采用延时分配策略,暂时先不分配物理内存,这样就可以达到最大化利用物理内存资源的目的,OS有内置的内存分配算法,决定何时分配、分配大空间的物理内存。

同样的,CPU执行程序时,并不一定要先代码全部都加载到内存中才可以运行,OS会对代码进行分批加载。在最极端的情况下,内存中只加载了进程的PCB,而不带有任何的代码和数据。

这时,我们就可以再次深入理解进程挂起状态。挂起状态发生在内存资源接近饱和的时候,OS将某些进程的代码和数据暂时从内存换到磁盘中去,这时进程的状态就是挂起状态。然而,上文提到,一个进程的代码和数据并不是在同一时刻全部都在内存中存在,而是会分批的加载和释放内存资源以保证内存资源的最大化利用。这样我们可以认为,如果内存中只有一个进程的PCB而没有任何的代码和数据,这个进程就处于挂起状态。

通过页表,不但可以映射到物理内存的地址,也可以映射到磁盘地址。假设有一个进程即将被挂起,那么页表中就会填入该进程的代码/数据被换入的磁盘地址,当该进程再次被拿到CPU中运行时,就可以直接通过页表找到对应的磁盘空间,从而将磁盘中的相应数据拿回内存。

4. 在用户层面实现对空间的有序化使用

由于物理内存是可以被随机使用的,那么如果CPU直接读取物理内存的地址进行访问,那么就无法对地址空间实现有序化的管理。

然而,现代计算机系统通过引入地址空间,让每段进程看似独占全部内存空间,并且每段地址都存储有特定类型的数据值,这样在用户层面看来,内存的使用就是有序的。

四. 总结

  • 在语言层面,根据对象/变量的类型不同,我们认为所有的对象/变量会根据类型的不同,存储在不同的内存地址区域,早期的计算机CPU也确实是直接对物理内存进行访问的。
  • 现代计算机引入了地址空间的概念,我们在用户层面上看到的地址,都是进程地址空间中的虚拟地址,要通过页表进行映射,才能找到实际的物理内存。为了确保每个进程之间的独立性,每个进程独有一份地址空间和一张页表,OS会拦截非法的内存访问。
  • 地址空间存在的意义在于:包含内存,拦截非法访问、实现进程管理与内存管理的解耦合、提高物理内存资源的利用效率、在用户层面实现对空间的有序化使用。

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

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

相关文章

android studio 单独运行java 文件

首先&#xff0c;创建一个新的java文件。 然后&#xff0c;在Test.java文件中写上如图所示的代码。 接下来&#xff0c;我们把目录模式从Android转换成Project。 打开.idea文件夹下的gradle.xml文件。 在gradle.xml文件中添加上红色方框中的内容。 <option name"delega…

MySQL GROUP BY 多个字段的用法说明

MySQL GROUP BY 多个字段的用法说明 1. 说明2. 举例附录 1. 说明 在 mysql 中使用 group by 的意思是分组查询。如果 group by 后面跟的是单个字段&#xff0c;那么表示按照这个字段分组查询&#xff0c;如果 group by 后面跟的是多个字段&#xff0c;那么表示按照这些字段的不…

关于使用idea中遇到给Dependencies没有加入jar包,但是在war_exploded中lib有

lib文件夹无jar包 Dependencies无jar包 war_exploded中存在此jar 原因是在此有jar包 当去掉时

深入解析 css.1.5

❑ 控制选择器的优先级。 ❑ 不要混淆层叠和继承。 ❑ 某些属性会被继承&#xff0c;包括文本、列表、表格边框相关的属性。 ❑ 不要混淆initial和auto值。 initial是一个CSS属性的初始值&#xff0c;它会将属性的值重置为浏览器默认值。例如&#xff0c;如果将background-co…

其实失败才是人生常态,赢者通吃确实存在,但那不代表绝大多数人。

其实失败才是人生常态&#xff0c;赢者通吃确实存在&#xff0c;但那不代表绝大多数人。 &#x1f4e2;今年的就业难度可能是之前5年最难的一年&#xff0c;也有可能是以后5年最好的一年。 &#x1f4e2;&#x1f4e2;疫情的回落&#xff0c;仿佛只带动了旅游业的发展&#x…

在EasyCVR中调用快照接口返回404是什么原因?如何解决?

EasyCVR视频融合平台基于云边端一体化架构&#xff0c;能在复杂的网络环境中将前端设备进行统一集中接入&#xff0c;实现视频资源的汇聚管理、直播鉴权、转码处理、多端分发、智能告警、数据共享等能力与服务。此外&#xff0c;平台也提供了丰富的API接口供用户自由调用、集成…

【XKCD】XKCD 风格的图像

目录 1. XKCD 2. 实战 1. XKCD xkcd是一种风格独特的漫画风格&#xff0c;以幽默、讽刺、科学和技术为主题。这种风格通常采用简单的线条和草图&#xff0c;表达出作者的思考和观点。xkcd的图像经常涉及科学、数学、计算机科学、社会和文化问题&#xff0c;以及作者个人的生…

181_带你体验 Power BI 开发者模式 pbip

181_带你体验 Power BI 开发者模式 pbip 一、背景 如果你是一个 Power BI 重度用户&#xff0c;你是不是也有如下的情况&#xff1f; 是的&#xff0c;Power BI 的版本控制全靠复制一份重命名来实现&#xff0c;而且版本之间的特点和差异时间久了就不记得了&#xff0c;还要加…

人工智能将怎样改变未来?TVP读书会带你探索!

引言 数十年前&#xff0c;图灵抛出的时代之问“机器能思考吗&#xff1f;”&#xff0c;将 AI 从科幻拉至现实&#xff0c;随着无数计算机科学先驱的共同努力&#xff0c;人工智能已经发展为引领未来的战略性技术。 AI 无处不在&#xff0c;智能时代触手可及&#xff0c;从 NL…

小程序 view clearfix 不起作用,边距还是被折叠

问题&#xff1a; 多个同级view情况下设置最后view clearfix&#xff0c;让底部露一些空白局域&#xff0c;此时clearfix不启作用。 .wxss .clearfix:before,.clearfix:after{content: "";clear: both;display: table;border-bottom: 1px solid black; } /*边距*/…

软件测试金字塔是什么,它的目的是什么,以及它包含哪些层次?

一、测试金字塔的概念&#xff1a; 测试金字塔是2009年Mike Cohn在他的著作《Succeeding with Agile》一书正式提出的。他是一个类比的概念&#xff0c;形容每一层&#xff0c;或者说不同集成阶段测试覆盖率和知行效率之间的一个相对关系。 测试金字塔最初的原型分三层&#…

如何找回删除的文件?这些文件恢复方法,超实用!

大家快看看我&#xff01;我一不小心删除了非常重要的文件&#xff0c;突然不知道该怎么办了&#xff01;我对电脑的操作也不熟悉&#xff0c;不敢轻易进行操作&#xff01;大家有什么比较好的方式可以找回删除的文件吗&#xff1f; 在使用电脑时&#xff0c;误删文件的情况经常…

基于Python所写的五子棋设计

点击以下链接获取源码资源&#xff1a; https://download.csdn.net/download/qq_64505944/87952977 《五子棋&#xff08;控制台版&#xff09;》程序使用说明 在PyCharm中运行《五子棋&#xff08;控制台版&#xff09;》即可进入如图1所示的系统主界面。 图1 游戏主界面 具…

掌握客户参与:终极指南有效的CRM管理

随着信息技术的快速发展&#xff0c;企业面对的市场竞争日益激烈&#xff0c;客户需求也变得越来越多样化和个性化。传统的销售和营销模式已经不能适应当前的市场环境&#xff0c;企业需要更加精细化、个性化的管理客户关系。CRM管理应用解决方案应运而生&#xff0c;它能够帮助…

allure环境搭建教程

pytest的安装:&#xff08;这里着重介绍Windows&#xff09; allure是基于Java的一个程序&#xff0c;需要Java1.8的环境,所以安装之前需要配置jdk环境 pytest是python的一个第三方单元测试框架&#xff0c;在这里用于生成原始的执行结果。 一、一定别选最新的&#xff0c;3.…

新品上市:ATA-2048高压放大器技术参数、特点及应用

作为中国电子测试仪器行业的优秀民族企业之一&#xff0c;Aigtek安泰电子始终坚持国产替代的发展战略&#xff0c;将产品研发和市场需求紧密结合&#xff0c;实现高效的研发产出&#xff0c;现已拥有ATA系列功率放大器、ATA系列功率放大器模块、ATG系列功率信号源、ATS系列高精…

SpringBoot + Vue前后端分离项目实战 || 五:用户管理功能后续

系列文章&#xff1a; SpringBoot Vue前后端分离项目实战 || 一&#xff1a;Vue前端设计 SpringBoot Vue前后端分离项目实战 || 二&#xff1a;Spring Boot后端与数据库连接 SpringBoot Vue前后端分离项目实战 || 三&#xff1a;Spring Boot后端与Vue前端连接 SpringBoot V…

vscode调试python配置

{// 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。// 欲了解更多信息&#xff0c;请访问: https://go.microsoft.com/fwlink/?linkid830387"version": "0.2.0","configurations": [{"name": "Python: 当前文…

域渗透(1)

域基础信息搜集 ping 域名ipconfig /all 判断当前是否在域环境中&#xff0c;看dns后缀和dns服务器net view 查看本工作组/域环境中的设备关闭防火墙开启服务net view /domain 查询域数量net view | net view /domain:域名 查询主机数量net group /domain 域控组别信…

使用el-tree实现不同区域的拖拽功能时遇到的坑点

问题再现 利用el-tree实现在两个区域的拖拽&#xff0c;1.树上的拖拽排序&#xff0c;2.将树上节点拖拽到画布上。 在将节点拖拽到画布上的时候&#xff0c;只要在树上移动过&#xff0c;松开鼠标的时候&#xff0c;树上的拖拽排序功能也生效了&#xff0c;那么如何使这两个拖…