Linux进程虚拟地址空间

news2024/12/25 9:25:02

文章目录

  • 1. 进程地址空间
    • 1.1 存在
    • 1.2 初步了解
    • 1.2 虚拟地址空间的划分
    • 1.3 页表
  • 2. 虚拟地址如何转化成物理地址
    • 2.1 二级页表
    • 2.2 总结
  • 3. 写时拷贝
    • 3.1 原理
    • 3.2 解释为什么 pid_t ret = fork() 中,ret 会有两个不同的值
  • 4. 为何需要虚拟地址空间

1. 进程地址空间

1.1 存在

本文分享一下进程地址空间的解析,实验环境:CentOS 7.6

首先看一下如下代码,逻辑很简单:定义一个全局变量,然后创建子进程,随后父子进程分别打印这个变量的地址

int foo = 10;

int main()
{
    int pid = fork();
    if (pid == 0)                       // 子进程 
    {
        cout << "子进程中 foo 变量的地址: [" << &foo << "]  |  值为 [" << foo << "] " << endl;
    }
    else if (pid > 0)                   // 父进程
    {
        cout << "父进程中 foo 变量的地址: [" << &foo << "]  |  值为 [" << foo << "] " << endl;
    }
    sleep(1);                           // 睡眠一秒, 保证打印出来的格式正常
    return 0;
}

程序执行结果如下,两者地址一样

在这里插入图片描述

现在稍微修改一下代码:在子进程中对 foo 变量做修改

int foo = 10;

int main()
{
    int pid = fork();
    if (pid == 0)                       // 子进程 
    {
        foo = 10000000;                 // 仅仅在这里让子进程中的 foo 的值发生变化
        cout << "子进程中 foo 变量的地址: [" << &foo << "]  |  值为 [" << foo << "] " << endl;
    }
    else if (pid > 0)                   // 父进程
    {
        cout << "父进程中 foo 变量的地址: [" << &foo << "]  |  值为 [" << foo << "] " << endl;
    }
    sleep(1);                           // 睡眠一秒, 保证打印出来的格式正常
    return 0;
}

现在执行结果如下:

在这里插入图片描述
发现不对劲了吗?「两个变量地址一样,但是该地址存储的值是不一样的」,如果说这里变量的地址是物理地址,那必然不存在这种情况,就像我家房子既是游泳池又是核电站一样诡异。

故而这里变量的地址并不是实际的物理地址,其实是进程的虚拟地址

1.2 初步了解

在每一个进程被创建启动的时候,操作系统都会为这些进程赋予虚拟地址空间的概念(在task_struct 中利用数据结构维护起来),并且在 32 位系统下,虚拟地址空间的分布就是由全 0 到 全 F,也就是 4GB,并且这些进程可以将虚拟地址空间视为内存

也就是说:站在任何一个进程的视角下,自己都拥有访问整个内存空间的能力,当进程需要内存的时候,如果条件允许,那么操作系统都会为其分配内存资源

在进程自己的视角下,虽然自己认为拥有物理内存,但是并不是随时都在使用所有资源。更多的表示的是对内存的划分区域后的使用,这里画个草图方便大家理解虚拟地址空间的大致,但是这里并不直接指向物理内存

在这里插入图片描述
如果说当前进程某一块资源太小了不够用,那么就可以调整 startend 指针来实现区域划分的调整

在这里插入图片描述

1.2 虚拟地址空间的划分

进程的虚拟地址空间被划分成了若干个区域,画图如下:

在这里插入图片描述

Linux中,使用struct task_struct 来描述一个进程,而这个结构体中还有一个数据结构,就是mm_struct,也就是对进程虚拟空间相关的描述

struct task_struct {
	volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
	...
	struct mm_struct *mm, *active_mm;
	...
};

mm_struct 也不并不会去直接描述这么多区域的虚拟地址空间,它里边还有一个结构体 vm_area_struct 来描述这些分区(正文代码区,初始化数据区…)的区域划分

在这里插入图片描述

也就是说,进程task_struct 中使用 mm_struct 来描述虚拟地址空间,而虚拟地址空间中被划分成了多个区域,所以mm_struct 中还有vm_area_struct 结构体来描述一个个连续的虚拟地址范围,还会保存该区域的权限等相关属性,比如用来描述堆栈区的vm_area_struct 对象,并且最终 mm_struct 中会将这些 vm_area_struct 通过链表形式连接起来,从而做到虚拟地址空间对堆,栈等空间区域的维护管理

那么现在就又有了一个新的理解,我们再画个草图

在这里插入图片描述

1.3 页表

虚拟地址空间和物理内存中还有一层用于映射的软件,可以将虚拟地址通过映射关系映射到物理内存上,最终可以实现通过虚拟地址而拿到物理内存中的真实值,这层软件就是页表。

当程序被加载到内存中的时候,就拥有了加载到内存的物理地址,操作系统会将虚拟地址和物理内存在页表中建立映射关系。并且在程序运行过程中,可能还会动态申请空间,这时候页表仍然会进行虚拟地址和物理地址映射关系的构建

在这里插入图片描述

并且每一个进程都拥有自己的页表,页表中还会存储映射的权限等其他复杂的属性(ps:这里的页表经过了简化,实际上可能存在多级页表)

而且多个进程中虚拟地址是允许相等的,因为不同的进程有各自的页表,最终会通过页表来映射到物理内存中的不同位置。有了页表,就可以根据虚拟地址来进行页表映射,从而找到物理内存中的数据了

2. 虚拟地址如何转化成物理地址

页表可以将进程的虚拟地址转化成物理地址,那么这个过程发生了什么?(页表经过简化,但是过程大致一样)

虚拟地址空间有 232 个地址,也就是 4G,而通常页表中的一项(条目)会代表 4KB 的虚拟内存,然后我们称虚拟内存中的 4KB( 212 ) 为一个单位,称为页,因此虚拟地址空间中就会有 232 ÷ ÷ ÷ 212 = = = 220 个页

对应的,4KB 的物理内存也为一个单位,称为页框,而完整的物理内存会被划分成一个个页框

2.1 二级页表

但是实际上,在 Linux 中,一般会使用多级页表。可以通过一级页表来得到二级页表,一层一层往下,最终根据最后一层页表来获取页框的起始物理地址。

现在我们以二级页表为例,说说虚拟地址转化成物理地址的过程
物理内存中,4KB 为一个页框,而物理内存的划分就是按照页框为基本单位进行划分的

在这里插入图片描述

假设现在有个32位的虚拟地址,现在按照如下格式来划分地址
AAAAAAAAAA BBBBBBBBBB CCCCCCCCCCCC,也就是 10 个 A | 10 个 B | 12 个 C,为啥最后一个划分是 12 位?

前面说了物理内存会按照 页框 来一个个划分,页框是 4KB,就是 212,而表示页框里面的地址,刚好就需要 12 个比特位,所以虚拟地址最后 12 位就是偏移量 —— 页框的偏移量

现在将上述地址取个名字,方便讲述:

在这里插入图片描述
映射过程如下:

  • 首先根据前 10 位,也就是 x 可以去一级页表(页目录)中进行映射,然后可以得到二级页表
  • 再根据中间 10 位,也就是 y 就可以获取到页框(物理块)的起始地址,前面说过物理内存已经被以页框的形式一个个划分好了,这里得到的就是具体某个页框的起始地址
  • 再根据最后 12 位,也就是 z,就可以得到页框的偏移量
  • 根据第二步中得到的页框起始地址 + 第三部的页框偏移量就可以得到最终的物理内存地址

最终画图如下:

在这里插入图片描述

然而实际可能会有多级页表以及更复杂的中间过程,感兴趣的可以深入了解

2.2 总结

再总结一下:

  • 最终的查找还是在寻找目标页框,所以页表的作用也就是定位物理内存的目标页框的起始位置
  • 然后再通过虚拟地址的最后 12 位页框偏移量来获取目标数据的物理地址

3. 写时拷贝

3.1 原理

在现在的 Linux 中,父进程在 fork 子进程的时候,操作系统会创建子进程的数据结构,包括task_structmm_struct(虚拟地址空间),页表,文件描述符表…,并且拷贝父进程数据结构中的大部分数据,少部分数据根据子进程实际情况作修改,然后共享父类代码

然后这时候父子进程各自的页表上的映射关系权限都会改成只读权限,如果有一方尝试修改,操作系统就会开辟一段空间,将原数据拷贝过去并修改,修改后再调整页表的映射关系,这个数据修改完成之后,父子进程中,该数据在页表中的映射权限就会都改成普通权限

在这里插入图片描述

因此,在一开始演示的代码中,如下

在这里插入图片描述

  • 在执行foo = 10000000 之前,父子进程的虚拟地址空间和页表一样(大部分拷贝自父类),子进程也还暂时没有修改数据
  • 而在执行 foo = 10000000 之后,foo 变量被修改,子进程就会发生写实拷贝,重新在内存中开辟一个int 空间,并且赋值
  • 然后再在页表中让这个虚拟地址(不变)和新的物理地址重新建立映射关系
  • 所以才有打印的地址一样,而地址的值不一样的情况。
  • 根本原因就是两个进程的页表将相同的虚拟地址映射到了不同的物理地址上
    原因就是子进程发生了写时拷贝,为int 开辟了个新空间

3.2 解释为什么 pid_t ret = fork() 中,ret 会有两个不同的值

在上面那段程序中,同一个变量 pid 来接收 fork 的返回值,但是有两个不同的值?

  • 父进程创建子进程后,子进程会以写时拷贝的方式拷贝父类的数据和数据结构,包括程序的代码。在fork 函数中的 return 语句之前,函数的主要任务已经完成,也就是完成了子进程创建,并放入到了运行队列中(一般子进程不会对代码做修改,所以父子进程执行的代码是一样的)
  • 那么既然子进程都创建成功了,那么return 语句也就会被父子进程各执行一次
  • return 语句实际上会将返回值先写入到寄存器中,然后寄存器再将返回值写入到 ret 中,ret也就是接收 fork 函数返回值的变量
  • ret 是父进程中栈空间的变量,哪个进程先接收返回值,就会导致 ret 变量被修改,从而发生写实拷贝,重新建立页表映射关系
  • 所以在这两个进程的执行逻辑中,该变量虚拟地址一致,但是物理内存不同,最终值自然不同。这个说法和上面很类似了

4. 为何需要虚拟地址空间

  1. 物理地址需要被保护
    ① 为物理内存的直接访问提供了一层软件层,可以在访问内存之前进行审核
    如果是非法访问操作,比如空指针,或者指针越界,那么就会因为页表中不存在这份映射导致请求直接被拦截;
    ② 如果是对某些文件进行修改,但是没有修改权限,那么也会被页表所拦截,页表中存在相关的权限管理,存储了对映射关系的权限
  2. 让进程管理和内存管理之间进行解耦
    ① 如果没有虚拟内存空间,那么如果一个进程正在malloc 来申请内存空间,就会去调用 malloc 的底层代码,进行内存管理要进行的工作
    ② 而现在,如果 malloc 来申请空间,那么操作系统会将进程中虚拟空间中对应的堆区扩大相应的大小,允许用户访问这段空间,但是不会马上进行内存的分配。等到用户真正要使用这块内存空间的时候,才会触发内存管理,申请物理空间,建立映射关系,就可以进行两者的解耦
  3. 虚拟地址空间的存在允许多个进程的相同虚拟地址映射到相同的物理地址,可以高效地进行进程间通信,
  4. 保证进程的独立性,让进程拥有自己独立的运行环境,可以防止进程异常或者崩溃影响到其他进程,提高了稳定性

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

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

相关文章

基于python的企业资产管理系统vue+django+flask

开发语言&#xff1a;Python 框架&#xff1a;django/flask Python版本&#xff1a;python3.7.7 数据库&#xff1a;mysql 数据库工具&#xff1a;Navicat 开发软件&#xff1a;PyCharm 文章介绍了企业资产管理系统的系统分析部分&#xff0c;包括可行性分析等&#xff0c;系…

基于SpringBoot的CRM客户管理销售团队管理系统(含源码+数据库)

1&#xff09;环境准备 JDK 1.8 以上 MySql 5.7 以上 Tomcat 7.0 以上 maven 3.5.0 Idea 2&#xff09;建立PowerTeam数据库 打开Mysql管理工具(推荐使用Navicat Premium) 执行db.sql脚本 可选操作执行demo_data.sql演示数据脚本 3)将项目导入Idea开发工具中 ​4&#xff09;修…

UE5使用Advanced-VR-Framework开发VR介绍

插件地址&#xff1a;https://www.unrealengine.com/marketplace/zh-CN/product/advanced-vr-framework 一、UE5开发VR的优势&#xff1a; UE5在三维的表现力上非常优秀&#xff1b;有基于Twinmotion等三维工具支持&#xff0c;制作流程上比较顺畅&#xff1b;UE5场景素材也非…

W13电力线载波通信技术

CK_Label_W13 CK_Label_W13&#xff08;外接供电版&#xff09; 产品型号 CK_Label_W13 尺寸 70*34.7*13.6mm 屏幕尺寸 2.1 inch 分辨率 250*122 像素密度 130dpi 显示技术 电子墨水屏显示 显示颜色 黑/白 外观颜色 白色 按键 1 指示灯 1 RGB灯 灯光颜…

Vue.js 中的混入是什么?如何使用混入?

Vue.js 中的混入是什么&#xff1f;如何使用混入&#xff1f; 在 Vue.js 中&#xff0c;混入&#xff08;Mixin&#xff09;是一种可复用的对象&#xff0c;可以包含任意组件选项。通过混入&#xff0c;我们可以将一些相同的代码逻辑抽离出来&#xff0c;使组件更加简洁和可维…

录音如何转换成mp3格式

录音怎如何转换成mp3格式&#xff1f;因为我们知道录音的格式有很多种&#xff0c;其中常见的有WAV格式&#xff08;Waveform Audio File Format&#xff09;&#xff1a;是一种无损音频文件格式&#xff0c;音质高保真&#xff0c;通常用于专业录音和音乐制作。WMA格式&#x…

qt三个窗口基本类

qt是一个gui框架&#xff0c;做图形用户界面的&#xff0c;本地可以跑&#xff0c;跟一些web框架那种做好点击网址跳转的不一样&#xff0c;python的web常用的flask和django&#xff0c;python也有gui框架比如pytqt和tkinter&#xff0c;这个专栏讲qt5&#xff0c;用c写gui qt…

WOWWEE ROVIO小车改造思路

23年5月接触到这个车&#xff0c;大约是07年的玩具&#xff0c;初看到外观&#xff0c;真的是惊艳&#xff0c;三爪着地&#xff0c;长长的脖子&#xff0c;有些科幻电影中外星生物的味道。这个ID设计&#xff0c;放到今天&#xff0c;也是能镇住国内一众玩具厂商的。惊讶之余&…

浅谈 ByteHouse Projection 优化实践

预聚合是 OLAP 系统中常用的一种优化手段&#xff0c;在通过在加载数据时就进行部分聚合计算&#xff0c;生成聚合后的中间表或视图&#xff0c;从而在查询时直接使用这些预先计算好的聚合结果&#xff0c;提高查询性能&#xff0c;实现这种预聚合方法大多都使用物化视图来实现…

基于javaweb jsp+SSM 简易版教务管理系统的设计与实现

目录 一.项目介绍 二.环境需要 三.技术栈 四.使用说明 五. 运行截图 六. 视频演示 一.项目介绍 本系统分为管理员、老师、学生三类 管理员&#xff1a;维护课程信息、维护老师信息、维护学生信息、密码重置&#xff08;其他账户&#xff09;、修改密码、退出系统 老…

macOS Sonoma 14.0 Beta 1 (23A5257q) ISO、IPSW、PKG 下载

macOS Sonoma 14.0 Beta 1 (23A5257q) ISO、IPSW、PKG 下载 本站下载的 macOS 软件包&#xff0c;既可以拖拽到 Applications&#xff08;应用程序&#xff09;下直接安装&#xff0c;也可以制作启动 U 盘安装&#xff0c;或者在虚拟机中启动安装。另外也支持在 Windows 和 Li…

JVM暂时私有

Jvm: 一、类加载器分类 引导类加载器&#xff1a;BootStrapClassLoader&#xff08;出于安全考虑&#xff0c;Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类&#xff09;自定义类加载器&#xff1a;Extension ClassLoader AppClassLoader &#xff08;Tomcat也…

DBeaver 23.0.5发布

导读SQL 编辑器是一款功能强大的数据库管理工具&#xff0c;为用户提供了便捷的 SQL 编写和编辑功能。最近&#xff0c;该编辑器进行了多项更新&#xff0c;提升了使用体验。 首先&#xff0c;WHERE 和 SELECT 等自动完成功能现在显示与别名表的别名。用户可以使用键盘快捷键或…

https通信加密原理

为什么要用https HTTP 由于是超文本传输协议&#xff0c;是一个简单的请求-响应协议&#xff0c;它通常运行在TCP之上&#xff0c;它是明文传输&#xff0c;不能保证数据的完整性&#xff0c;不能保证是否被窃听&#xff0c;不能保证数据是否被篡改 https采用了一些加解密&am…

安装银河麒麟操作系统

文章目录 一、安装银河麒麟操作系统1.1、简介1.2、银河麒麟高级服务器操作系统V101.3、下载银河麒麟镜像1.4、安装银河麒麟操作系统兼容版 一、安装银河麒麟操作系统 1.1、简介 银河麒麟&#xff08;KylinOS&#xff09;原是在863计划和国家核高基科技重大专项支持下&#xf…

HFish蜜罐部署教程(windows版)—HW蓝队主动防御利器

文章目录 前言基础环境蜜罐部署管理端部署添加节点&开启服务 攻击展示端口扫描测试目录扫描测试POC测试 && ssh测试失陷测试 酷炫大屏后记Tips 前言 一年一度的HW马上又要来了&#xff0c;【不过听说今年推迟了一些】 &#xff0c;各位师傅应该都按耐不住了&#…

Tcl-12. 数组

TCL支持通过数组的形式存储多个元素。 Tcl中的数组和其他高级语言的数组有些不同&#xff1a;Tcl 数组元素的索引&#xff0c;或称键值&#xff0c;可以是任意的字符串&#xff0c;而且其本身没有所谓多维数组的概念。数组的存取速度要比列表有优势&#xff0c;数组在内部使用散…

从理论上理解SQL注入、XSS、中间件解析漏洞、挖矿马

目录 1、SQL注入 &#xff08;1&#xff09;原理 &#xff08;2&#xff09;分类 &#xff08;3&#xff09;防御 2、XSS &#xff08;1&#xff09;原理 &#xff08;2&#xff09;分类 3、中间件&#xff08;解析漏洞&#xff09; &#xff08;1&#xff09;IIS6.X …

JDK安装教程

jdk 链接&#xff1a;https://pan.baidu.com/s/1xAFaR7AQdy_hPVFHc1CVnA 提取码&#xff1a;cypz 环境配置 1、我的电脑–》右键属性–》高级系统设置–》环境变量 2、系统变量–》新建系统变量–》完成后点击确定 变量名&#xff1a;JAVA_HOME 变量值&#xff1a;&#xff0…

chatgpt赋能python:Python中的分词技术及其应用

Python中的分词技术及其应用 什么是分词&#xff1f; 分词是自然语言处理&#xff08;Natural Language Processing&#xff0c;NLP&#xff09;中的一个重要环节&#xff0c;指将一段文本切分成若干个单词或词组。在中文分词中&#xff0c;由于中文没有明显的词汇边界&#…