Linux 内核学习 3 - 虚拟内存和物理内存

虚拟内存其实是 CPU 和操作系统使用的一个障眼法,联手给进程编织了一个假象,让进程误以为自己独占了全部的内存空间

  • 在 32 位系统中,进程以为自己独占了 3G 的内存空间。

  • 在 64 位系统中,进程以为自己独占了 128T 的内存空间。

这么做的好处是,操作系统为每个进程营造出一片独立的虚拟地址空间,使得进程与进程之间相互隔离,互不干扰的,解决了多进程同时运行时产生的内存地址冲突问题。

之前一直纠结,如果好多个进程,那么内存如何分配?不同进程的堆栈代码空间等是放在一起还是单独放(进程1 栈空间 进程2 栈空间。。。进程N栈空间, 进程1堆空间, 进程2堆空间。。。进程N堆空间 还是进程1 栈空间堆空间代码空间进程2栈空间堆空间代码空间。。。)。所以说,对于每一个进程来说,都觉得自己可以把所有的内存占用掉,然后通过内核的一些机制把它们映射到物理空间。

3.1. 虚拟内存如何与物理内存映射起来

内核会将整个物理内存空间划分为一页一页大小相同的的内存块,每个内存块大小为 4K,称为一个物理内存页。

一页大小的内存块在内核中用 struct page 结构体来进行管理,struct page 中封装了每页内存块的状态信息,比如:组织结构,使用信息,统计信息,以及与其他内核结构的关联映射信息等。

内核会为每个物理内存页 page 进行统一编号。这个编号称之为 PFNPage Frame Number),PFN struct page 是一一对应的关系并且全局唯一。然后内核会将划分出来的这些一页一页的内存块统一组织在一个全局数组 mem_map 中管理。后续虚拟内存与物理内存的映射以及调度均是以页为单位进行的。

然后内核会将划分出来的这些一页一页的内存块统一组织在一个全局数组 mem_map 中管理。后续虚拟内存与物理内存的映射以及调度均是以页为单位进行的

3.2. 内核如何通过页表来管理内存映射关系

内核会从物理内存空间中拿出一个物理内存页来专门存储进程里的这些内存映射关系,而这种物理内存页我们将其称之为页表,从这里可以看出页表的本质其实就是一个物理内存页。

而内核会在页表中划分出来一个个大小相等的小内存块,这些小内存块我们称之为页表项 PTEPage Table Entry),正是这个 PTE 保存了进程虚拟内存空间中的虚拟页与物理内存页的映射关系,以及控制物理内存访问的相关权限位。

32 位系统中页表中的 PTE 占用 4 个字节,64 位系统中页表的 PTE 占用 8 个字节。

因为内存映射的粒度是按照页为单位进行的,所以进程虚拟内存空间中的每个虚拟页在页表中都会有一个 PTE 与之对应,而虚拟页背后映射的物理内存页的起始地址就保存在 PTE 中。

而进程虚拟内存空间中的每一个字节都有一个虚拟内存地址来表示,格式为:页表内偏移 + 物理内存页内偏

这样一来,给定一个虚拟内存地址,内核会先从这个虚拟内存地址中提取出 页表内偏移 ,然后根据 页表起始地址 + 页表内偏移 * sizeof(PTE) 就能获取到该虚拟内存地址所在虚拟页在页表中对应的 PTE

 cr3 寄存器中保存的就是当前进程顶级页表的起始物理内存地址了(区分不同进程的变量,使得不同进程映射到不同的物理地址,虽然虚拟地址可以一样),当 CPU 通过下图所示的虚拟内存地址访问进程的虚拟内存时,CPU 首先会从 cr3 寄存器中获取到当前进程的顶级页表起始地址,然后从虚拟内存地址中提取出虚拟内存页对应 PTE 在页表内的偏移,通过 页表起始地址 + 页表内偏移 * sizeof(PTE) 这个公式定位到虚拟内存页在页表中所对应的 PTE

3.3 多级页表

32 位系统中,一个 PTE 占用 4 个字节,可以映射 4K 的物理内存,一张页表本身占用 4K 的物理内存(1024 entry),可以映射 4M 的物理内存。我们必须要为进程额外分配 4M 的连续物理内存来存放 1024 张页表(4G)。

如果现在我们在拿出一个 4K 的物理内存页作为页表,然后将这个页表放在单级页表的前面,组成一个二级页表的体系,情况会变成什么样呢

当前系统中,进程只有一张页目录表,页目录表里的 PDE 没有映射任何东西,这时进程需要访问一个物理内存页,而对物理内存页的映射任务主要是在一级页表的 PTE 中,所以现在首要的任务就是建立一张一级页表出来,并用页目录表索引起

在二级页表的情况下,内核只需要一张 4K 的页目录表和一张 4K 的一级页表总共 8K 的内存就可以支持进程访问一个 4K 物理页面了,而根据程序的空间局部性原理,在不久的将来,进程只会访问与该物理内存页临近的页面,所以事实上,即使进程访问 4M 的内存,依然只需要一张 4K 的页目录表和一张 4K 的一级页表就可以满足了。

当进程需要访问下一个 4M 的物理内存时,这时候第一个一级页表已经映射满了,那就需要再创建第二张页表用来映射下一个 4M 的物理内存,当然了,第二张页表依然需要索引在页目录表的 PDE

这时候内核就需要一张页目录表和两张一级页表共 12K 额外的物理内存来映射,这依然比单级页表的 4M 连续物理内存开销小很多。

同理,随着进程一个 4M 接着一个 4M 物理内存的访问,在极端的情况下整个页目录表都被映射满了,这时候内核就需要 4K(页目录表)+ 4M1024张一级页表)的额外内存来保存映射关系了,这种情况下看起来会比单级页表下的 4M 内存开销大了那么一点点,但这种属于极端情况,非常少见,极大部分情况下还是比单级页表开销少很多很多的。

那么如何二级页表寻址呢?

同理, 64位操作系统因为虚拟内存空间很大,一般需要四级页表体系。

32 位系统中的页目录表,页表和 64 位系统中的页目录表,页表在内核中都是使用一个普通 4K 的物理内存页存储映射关系的,不同的是 64 位系统中的页表中的 PTE 以及页目录表(PGD,PUD,PMD)中的 PDE 都是占用 8 个字节

3.4 CPU的寻址过程

经过本文前边内容的介绍,上图中的这个四级页表的遍历过程,我们已经非常的清楚了,我们可以明显的体会到整个地址翻译的过程需要的步骤还是比较多的,而 CPU 访问内存的操作是非常非常频繁的,如果我们采用内核这种软件的方式对页表进行遍历,效率会非常的差。

而采用一种专门的硬件来对软件进行加速,无疑是一种最简单,最直接有效的优化手段,于是在 CPU 中引入了一个专门对页表进行遍历的地址翻译硬件 MMUMemory Management Unit),有了 MMU 硬件的加持整个地址翻译的过程就非常的快了。

事实上,上图中展示的四级页表的整个遍历操作均是在 MMU 中进行的:

经过前边的内容我们知道,这些页目录,页表的本质其实在内核看来都是一张普通的 4K 大小的物理内存页,而物理内存页中经常被访问到的内存数据都是缓存在 CPU 的高速缓存 L1 L2L3 CACHE 中的,这样可以利用局部性原理加速 CPU 对内存的访问

所以页目录表和页表中那些经常被 MMU 遍历到的页目录项 PDE,页表项 PTE 均会缓存在 CPU CACHE 中,这样 MMU 就可以直接从 CPU 高速缓存中获取 PDE , PTE 了,近一步加速了整个地址翻译的过程。

MMU 拿到一个 CPU 正在访问的虚拟内存地址之后, MMU 首先会从 CR3 寄存器中获取顶级页目录表 PGD 的起始内存地址,然后从虚拟内存地址中提取出 pgd_index,从而定位到 PGD 中的一个页目录项 pdg_tMMU 首先会从 CPU 的高速缓存中去获取这个 pgd_t,如果 pgd_t 经常被访问到,那么此时它已经存在于高速缓存中了,MMU 直接可以进行下一级页目录的地址翻译操作,避免了慢速的内存访问。

同样的道理,在 MMU 经过层层的页目录遍历之后,终于定位到了一级页表中的 PTEMMU 也是先会从 CPU 高速缓存中去获取 PTE,如果 PTE 不在高速缓存中,MMU 才会去内存中去获取。获取到 PTE 之后,MMU 就得到了虚拟内存地址背后映射的物理内存地址了。

在我们引入 MMU 之后,虽然加快了整个页表遍历的过程,但是 CPU  每访问一个虚拟内存地址,MMU 还是需要查找一次 PTE,即便是最好的情况,MMU 也还是需要到 CPU 高速缓存中去找一下的,即便这样开销已经很小了,但是我们还是想近一步降低这个访问 CPU CACHE 的开销,让 CPU 访存性能达到极致,那么该怎么办呢?

既然 MMU 每次都需要查找一次 PTE,那么我们能不能在 MMU 中引入一层硬件缓存,这样 MMU 可以把查找到的 PTE 缓存在硬件中,下次再需要的时候直接到硬件缓存中拿现成的 PTE 就可以了,这样一来,CPU 的访存效率又被近一步加快了。

这个 MMU 中的硬件缓存就叫做 TLB(Translation Lookaside Buffer)TLB 是一个非常小的,虚拟寻址的硬件缓存,专门用来缓存被 MMU 翻译之后的热点 PTE。当我们引入 TLB 之后,整个寻址过程就又有了一些新的变化:

CPU 将要访问的虚拟内存地址送到 MMU 中翻译时,MMU 首先会在 TLB 中通过虚拟内存寻址查找其对应的 PTE 是否缓存在 TLB 中,如果 cache hit ,那么 MMU 就可以直接获得现成的 PTE,避免了漫长的地址翻译过程。

如果 cache miss,那么 MMU 就需要重新遍历页表,然后获取 PTE 的内存地址,从 CPU 高速缓存或者内存中去查找 PTE,慢速路径下获取到 PTE 之后,MMU 会将 PTE 缓存到 TLB 中,加快下一次获取 PTE 的速度。

MMU 获取到 PTE 之后,就可以从 PTE 中拿到物理内存页的起始地址了,在加上虚拟内存地址的低 12 位(物理内存页内偏移)这样就获取到了虚拟内存地址映射的物理内存地址了。

那么当 MMU 拿到我们最终要访问的物理内存地址之后,又该怎么办呢

  • MMU 获取到最终的物理内存地址,首先会根据物理内存地址到 CPU 高速缓存中去查找数据,如果 cache hit,整个访存操作快速结束。
  • 如果 cache miss,那么 MMU 会将物理内存地址放到系统总线上传输,随后 IO bridge 会将系统总线上传输的地址信号传递到存储总线上。
  • 内存中的存储控制器感受到存储总线上的地址信号之后,会将物理内存地址从存储总线上读取出来。并根据物理内存地址定位到具体的存储器模块,随后解析物理内存地址从  DRAM 芯片中取出对应物理内存地址里的数据。

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

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

相关文章

杨中科 EFCORE 第四部分 命令详解56-61

Migrations 深入研究Migrations 1、使用迁移脚本,可以对当前连接的数据库执行编号更高的迁移,这个操作叫做“向上迁移” (Up),也可以执行把数据库回退到旧的迁移,这个操作叫“向下迁移(Down) 2、除非有特殊需要&…

机器人行业概况(2)

上篇已经介绍过关于机器人的定义以及分类,下面来看看机器人产业市场规模。 二、国内机器人产业市场规模 中国机器人产业在国家智能制造相关政策的引导下蓬勃发展。在新冠肺炎疫情防控期间,消毒、配送、测温、巡检等各类机器人的“火线上岗”&#xff0…

spring-boot2.7.8添加swagger

一、新建项目swaggerdemo 二、修改pom.xml 注意修改&#xff1a;spring-boot-starter-parent版本为&#xff1a;2.7.8 添加依赖&#xff1a; springfox-swagger2 springfox-swagger-ui springfox-boot-starter <?xml version"1.0" encoding"UTF-8"…

QT第3天

如上图界面&#xff0c;需求如下&#xff1a; 1、根据名字添加水果&#xff0c;并设置好单价 2、切换文件查看模式 3、点击任意水果可以显示单价 4、重量改变时&#xff0c;总价自动显示 //widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <Q…

RH850P1X芯片学习笔记-Flash Memory

文章目录 FeaturesClock Supply Block DiagramFlash SizeMemory ConfigurationRegistersRegister Base AddressList of RegistersRegister Reset Condition 与Flash Memory相关的操作模式Functional OverviewOption BytesOPBT0 — Option Byte 0OPBT1 — Option Byte 1OPBT2 —…

【CSS】保持元素宽高比

保持元素的宽高比&#xff0c;在视频或图片展示类页面是一个重要功能。 本文介绍其常规的实现方法。 实现效果 当浏览器视口发生变化时&#xff0c;元素的尺寸随之变化&#xff0c;且宽高比不变。 代码实现 我们用最简单的元素结构来演示&#xff0c;实现宽高比为4&#xf…

鸿蒙Harmony是如何影响Android工程师的呢?

其实鸿蒙在2019就已经出来了&#xff0c;那时候还是套壳Android的。从2023年9月的发布会上&#xff0c;华为宣布鸿蒙原生应用全面启动、HarmonyOS NEXT亮相以后&#xff0c;围绕着纯血鸿蒙展开的鸿蒙应用生态发展迅猛&#xff0c;目前已经有包括社交、金融、影音、游戏、资讯、…

docker-compose和docker compose的区别

在docker实际使用中&#xff0c;经常会搭配Compose&#xff0c;用来定义和运行多个 Docker 容器。使用时会发现&#xff0c;有时候的指令是docker-compose&#xff0c;有时候是docker compose&#xff0c;下面给出解释。 docker官方文档&#xff1a;https://docs.docker.com/c…

ArrayBlockingQueue的使用

异步日志打印模型概述 在高并发、高流量并且响应时间要求比较小的系统中同步打印日志已经满足不了需求了&#xff0c;这是因为打印日志本身是需要写磁盘的&#xff0c;写磁盘的操作会暂时阻塞调用打印日志的业务线程&#xff0c;这会造成调用线程的rt增加。 如图所示为同步日…

WorkPlus领先企业即时通信软件,提升团队沟通效率的利器

在企业工作中&#xff0c;高效沟通是推动团队协作和工作效率的关键。而企业即时通信软件成为了实现高效沟通的利器。作为一款领先的企业即时通信软件&#xff0c;WorkPlus以其卓越的性能和独特的功能&#xff0c;提升团队沟通效率&#xff0c;助力企业实现高效协作。 为什么选择…

豆包ai介绍

豆包是字节跳动基于云雀模型开发的AI工具&#xff0c;具有强大的语言处理能力和广泛的应用场景&#xff0c;无论是在学习、工作、生活中&#xff0c;都能派上用场。 豆包可以帮助打工人和创作者提升效率&#xff0c;完成各种工作任务&#xff0c;又能扮演各类AI角色进行高情商…

[学习笔记]刘知远团队大模型技术与交叉应用L1-NLPBig Model Basics

本节主要介绍NLP和大模型的基础知识。提及了词表示如何从one-hot发展到Word Embedding。语言模型如何从N-gram发展成预训练语言模型PLMs。然后介绍了大模型在NLP任务上的表现&#xff0c;以及它遵循的基本范式。最后介绍了本课程需要用到的编程环境和GPU服务器。 一篇NLP方向的…

从零开始做题:逆向wdb_2018_2nd_easyfmt

1.题目信息 2.解题分析 格式化字符串漏洞 如何确定偏移 Do you know repeater? 输入AAAA.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p. 输出AAAA.0xffffd658.0x64.0xf7ffdc08.0xf7ffcd00.0xffffd77c.0x41414141.0x2e70252e.0x252e7025.0x70252e70.0x2e70252e.0x252e7025.0x70252…

City Terrace Pack

“城市与露台资源包” 的主要特点:• 属于系列的一部分。• 极为逼真和现代化的城市。• 高度优化的低多边形和逼真资源。• 可用于 Oculus、GearVR、Vive、Daydream。• 可用于低端和高端移动设备。• 灵感来自于现代建筑和设计。• 36 种不同的摩天大楼和建筑物。• 其中每个…

【2023 我的编程之旅】

前言 转眼 2024 年都过去 14 天了。回顾 2023 有太多技术上的思考以及人生的感悟&#xff0c;接下来趁着 CSDN 官方活动&#xff0c;顺便记录下来。 技术的价值 与现在的年轻人一心只想搞钱不同&#xff0c;刚毕业的时候&#xff0c;我的梦想是进入一家有实力的科技企业&…

如何创建并格式化硬盘分区?

一般将新硬盘连接到计算机后&#xff0c;需先创建并格式化硬盘分区。否则在磁盘管理中会显示为“未分配空间”&#xff0c;并在文件资源管理器中不可见。那我们如何在硬盘上创建新分区&#xff0c;并对新分区进行格式化&#xff1f; 方法1. 通过命令提示符 首先&#xff0c;我…

两周掌握Vue3(三):全局组件、局部组件、Props

文章目录 一、全局组件1.创建全局组件2.在main.js中注册全局组件3.使用全局组件 二、局部组件1.创建局部组件2.在另一个组件中注册、使用局部组件 三、Props1.定义一个子组件2.定义一个父组件3.效果 代码仓库&#xff1a;跳转 本博客对应分支&#xff1a;03 一、全局组件 Vue…

计算机缺失mfu140u.dll的5种解决方法,亲测有效

在计算机系统运行过程中&#xff0c;mfu140u.dll文件的丢失是一个较为常见的问题场景。这个动态链接库文件(mfu140u.dll)对于系统的正常运行具有关键作用&#xff0c;它的缺失可能导致相关应用程序无法启动或执行功能异常。具体来说&#xff0c;mfu140u.dll丢失的场景可能出现在…

Arm LDM和STM的寻址方式

A32指令集中包含多数据传输指令LDM和STM&#xff0c;也就是单条指令可以传输多个寄存器的值与内存交互&#xff0c;这对于数据块传输以及寄存器的压入栈很有帮助。LDM和STM指令可分别用于实现堆栈的pop和push操作。对于堆栈操作&#xff0c;基寄存器通常是堆栈指针(SP)。 LDM和…

LeetCode讲解篇之2280. 表示一个折线图的最少线段数

文章目录 题目描述题解思路题解代码 题目描述 题解思路 折线图中如果连续的线段共线&#xff0c;那么我们可以可以将其合并成一条线段 首先将坐标点按照横坐标升序排序 然后遍历数组 我们可以通过计算前一个线段的斜率和当前线段的斜率来判断是否共线 如果二者相等&#x…