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 芯片中取出对应物理内存地址里的数据。

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

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

相关文章

【MySQL】数据处理之增删改

文章目录 一、增加(插入)INSERT INTO...VALUES(...,...)VALUES的方式添加情况一:为表的所有字段按默认顺序插入数据情况二:为表的指定字段插入数据情况三:同时插入多条记录 将查询结果插入到表中 二、修改(…

开源知识库zyplayer-doc部署指南

1.前置条件 docker已经安装 mysql已经安装且数据库zyplayer-doc存在 服务器ip:192.168.168.99/ 数据库账户:root,密码:123456 2.拉取镜像 docker pull zyplayer/zyplayer-doc:latest 3.启动 docker run -d \--restart unless-stopped \--name zyplayer-doc \-p 8083:8083 …

C语言常用库函数

1.C语言标准库函数 C语言标准库函数由15个头文件组成。 1.数学函数 数学计算是计算机最擅长的运算,计算机大部分运算方法都是基于数学计算执行的。C语言提供了很多用于数学计算的库函数,合理利用这些库函数,将对程序的编写和运行起到事半功…

Python相对导入和绝对导入

目录结构: 在 en_de_model_CDDD.py 文件有两种导入方式可以导入utils.py,分别是 相对导入: from ...public_utils.utils import canonicalize_smiles 绝对导入: from public_utils.utils import canonicalize_smiles 这里推荐使…

大量的视频如何批量随机分割的方法:批量剪辑不求人

在处理大量视频文件时,经常要进行随机分割,满足不同的需求。制作短视频、片段集锦等,批量随机分割视频都是一个高效的方法。下面来看云炫AI智剪如何操作的吧。 分割后的视频缩略图展示,被分割的视频自动分类保存在对应的文件夹中。…

软件测试|Pydantic BaseModel使用详解

简介 当我们在Python中编写应用程序时,通常需要处理和验证数据。Pydantic 是一个流行的库,它可以帮助我们定义数据模型并自动进行数据验证。在Pydantic中,BaseModel是一个核心概念,它用于定义数据模型和验证输入数据。在这篇文章…

第六站:C++面向对象关键字解释说明

this指针: 是一个特殊的指针,放回这个对象本身,this指针是属于实例对象,不能访问静态方法(不属于某一个实例对象,属于共有的,大众的,由类直接调用) 第一种用法: void Human::setName(string name1) {this->name name1; } void Human::setAge(int age1) {this->age a…

拯救者y9000p安装linux、windows双系统。

首先需要准备启动盘 我用的是Win32DiskImager来做的。资源使用的是ubuntu-20.04.6-desktop-amd64.iso。别用低版本,失败很多次之后的教训。 磁盘管理-磁盘分区-右键-压缩卷 这边分区出来之后,不要分配。安装时候会自动分配的。 重启之后F2进去BIOS设置…

张载为往圣继绝学,唯一的错是不够强大

“自古雄才多磨难,从来纨绔少伟男。” 张载,人称“横渠先生”。他在横渠镇,授徒讲学,恢复古礼,试验井田,写书《正蒙》。张载讲学关中,弟子多为关中人,其学派被称作关学。 张载自学…

git-生成证书、公钥、私钥、error setting certificate verify locations解决方法

解决方法 方法1-配置证书、公钥、私钥打开Git Bash设置名称和邮箱执行,~/.ssh执行,ssh-keygen -t rsa -C "这是你的邮箱",如图:进入文件夹可以看到用记事本之类的软件打开id_rsa.pub文件,并且复制全部内容。…

Python自动化测试框架【生成测试报告】

如何才能让用例自动运行完之后,生成一张直观可看易懂的测试报告呢? 小编使用的是unittest的一个扩展HTMLTestRunner 环境准备 使用之前,我们需要下载HTMLTestRunner.py文件 点击HTMLTestRunner后进入的是一个写满代码的网页,小…

并发编程之并发容器

目录 并发容器 CopyOnWriteArrayList 应用场景 常用方法 读多写少场景使用CopyOnWriteArrayList举例 CopyOnWriteArrayList原理 CopyOnWriteArrayList 的缺陷 扩展迭代器fail-fast与fail-safe机制 ConcurrentHashMap 应用场景 常用方法 并发场景下线程安全举例 Con…

软件测试|教你使用Python下载图片

前言 我一直觉得Windows系统默认的桌面背景不好看,但是自己又没有好的资源可以进行替换,突然我一个朋友提醒了我,网络上的图片这么多,你甚至可以每天换很多个好看的背景,但是如果让我手动去设置的话,我觉得…

编程高手必备:Python字典操作与示例全解析

编程高手必备:Python字典操作与示例全解析 引言Python字典基础字典操作技巧代码示例实际应用案例实际应用案例结语 引言 在编程世界中,高效地处理和组织数据是每位程序员必备的技能。Python,作为一种广受欢迎的编程语言,不仅因其…

mysql原理--undo日志1

1.事务回滚的需求 我们说过 事务 需要保证 原子性 ,也就是事务中的操作要么全部完成,要么什么也不做。但是偏偏有时候事务执行到一半会出现一些情况,比如: (1). 事务执行过程中可能遇到各种错误,比如服务器本身的错误&…

Jenkins-用户管理

用户管理 1 安装插件 2 选择安全策略为刚刚安装的插件 3 这个是安装插件以后会有的选项 4 增加一个角色 5 根据需要赋值角色的权限,并分配给用户

橘子学Mybatis07之Mybatis关于缓存的设计

很逆天的一件事是,我上一次发mybatis是在2022年10月15号,然后直到今天才开始总结下一篇Mybatis的东西。一年里面忙成那啥了,而且重心都投入在了Elasticsearch的学习上面,基本一年下来都在搞ES,并且考下了ECE认证&#…

【Docker】Dockerfile构建最小镜像

🥳🥳Welcome 的Huihuis Code World ! !🥳🥳 接下来看看由辉辉所写的关于Docker的相关操作吧 目录 🥳🥳Welcome 的Huihuis Code World ! !🥳🥳 前言 一.Dockerfile是什么 二.Dock…

lenovo联想笔记本电脑拯救者Legion Y7000 2019 PG0(81T0)原装出厂Windows10系统

链接:https://pan.baidu.com/s/1fn0aStc4sfAfgyOKtMiCCA?pwdas1l 提取码:as1l 联想拯救者原厂Win10系统自带所有驱动、出厂主题壁纸、系统属性联机支持标志、系统属性专属LOGO标志、Office办公软件、联想电脑管家等预装程序 所需要工具:…

模型索引:QModelIndex

一、为什么要使用模型索引? 从名字可以看出,他是模型的索引,只要对模型实体(各种xxxModel的实体)施加这个索引,model就会返回数据集中对应的值,或者通过这个索引修改对应数据集中的值。 类比数…