聊聊虚拟内存

聊聊虚拟内存

早在上大学时,学习计算机组成原理的时候,老师就有讲过虚拟内存,但是由于当时过于年轻对于它的理解几乎是皮毛,工作多年后发现对虚拟内存的理解还是非常重要的,在分析应用内存问题时也许能够用到相关的知识,所以今天来复习一下曾经忘记掉的知识(或许上学时都没有学会😂)。

在早期的计算机是没有虚拟内存的概念的,程序都是直接操作物理内存的,不过在那个时候也不存在太多的进程,这样问题还不是很大,但是后来随着计算机的发展,出现了支持多进程的计算机,当这些进程都直接对物理内存进行读写的时候就很难管理,假如同时有 A 和 B 两个进程对同一个物理内存地址进行修改就会出现问题。为了让多进程使用内存更加安全(可能还有别的原因😄),就提出了虚拟内存的概念,每个进程都有一个独立的虚拟内存,就好像每个进程都自己独立拥有这一块内存,没有其他线程的干扰,不同进程的虚拟内存地址就算是一样的他们储存的数据也是不一样,但是真实的数据存储都是要依赖物理内存的,计算机有专门的模块来处理虚拟内存和物理内存的这种映射关系,这个模块简称 MMU,当程序把虚拟地址传给 CPU 后,CPU 需要借助 MMU 把虚拟地址翻译成物理地址,然后对物理地址进行读写操作。操作虚拟地址和物理地址的映射有两种方式内存分页(Paging)内存分段 。在 Linux 中主要使用的是内存分页,我们今天也主要讲讲内存分页。

内存分页

操作系统会把内存划分成一个个的基本单位来管理,就像是一个个内存块儿,这个块儿叫做页。(Page)在 Linux 中这个页的大小通常是 4KB,当应用想要使用内存时,就需要通过虚拟地址在在页表(记录虚拟地址和物理地址的映射)中去查询,查询到对应的物理地址后再进行操作,如果这个过程中没有查到呢?这个时候就会产生一个缺页异常,这时操作系统就会去为当前进程的虚拟地址分配一个物理内存页,当分配成功后,然后应用进程就得以继续执行。

具体虚拟内存是怎么映射的呢?如下图:

虚拟地址分为页号和页内的偏移量,在页表中查询到虚拟页号对应的物理页号后,然后把物理页号的初始地址加上偏移量就是目标的地址。

内存分页在很大的程度上解决了内存碎片化的问题,这里简单描述一下内存碎片化的问题:假如我需要启动一个程序需要 500MB 的内存,由于系统之前回收了 2 个程序,他们分别占用 200MB 和 300MB,但是这两块内存并不是连续的,所以新的应用不能加载。但是通过分页是能够解决这个问题的,分页后操作系统管理的基本单位是一个内存页。在虚拟内存中看到的自己的内存是连续的,而虚拟内存映射到物理内存页后可就不一定是连续的了。它能够很大程度上提升内存的使用率。如下图:

虚拟内存中分配的内存在没有使用前是不会映射到物理内存的,这里举一个例子:假如我想要绘制一个 1MB 的 bitmap,然后我就申请了 1MB 的内存,这个内存是在虚拟内存中的,这时并没有真正使用物理内存,当我真正想要绘制时就涉及到这块内存的写,这时就会触发缺页错误,然后才会真正地分配映射物理内存。通过这种只有在真正使用时才分配物理内存的方式,也能够提高内存的使用效率。

我们前面说到每个进程都有一个虚拟内存,不同的进程而且是互相独立的,以 32 位系统,4G 的物理内存为例,通常他们的虚拟内存大小也是 4G,每个假如这个时候其他程序占用了 3.5G 的物理内存,这时我想要再加载一个 600MB 的应用程序,能够加载吗?大部分情况下是可以加载的,首先按照前面说的 600MB 的程序页不一定立刻就要 600MB 物理内存,只有在要使用时才分配。当后续使用的内存超过了物理内存后才会出现问题,在 Linux 中设计了 Swap 空间(可以手动设置这个空间的大小),也就是把本地磁盘的一部分空间拿来当内存使用(当然不是真正的当内存,磁盘的速度太慢了不行,CPU 不能直接和磁盘交换数据)。当申请的内存超过剩余的物理内存后,操作系统就会查找到最不常用的页,然后把它的数据存储到 Swap 空间的磁盘上,然后这部分页就空闲出来了,这部分页就可以分配给申请的内存,这就被称为 Swap-out。当下次要使用这部分内存时,又会把它从磁盘中加载到内存页中去,这被称为 Swap-in。 参考下图:

那如果是这样我们不是可以把磁盘当内存,就可以拥有超大的内存了,洗洗睡吧。当出现大量的 Swap 操作时,就表示内存不足了,由于磁盘的读写速度远远小于内存,也就是出现大量 Swap 是你会感觉到电脑明显的卡顿。这些年很多 Android 手机厂商也把这个当成重要的卖点声称 8 + 4 ( 8G 物理内存 + 4G Swap 空间) 的内存,不明真相的用户还以为这是多么高级的技术,其实好多年前 Linux 就支持了,那为什么手机厂商近几年才开放呢?因为现在手机闪存技术发展,手机磁盘的读写速度越来越快了,所以可以这么玩儿了,但是当你真正地使用 Swap 空间后,你的体验应该也会很差。

简单内存分页也是存在问题的,以 32 位系统 4GB 物理内存为例,假如页的大小为 4KB,那么就需要分页 4*2^30 / 4*2^10 = 2^20 个,大概就是需要 100 万个,页表中每一项需要占用 4 Bytes 的内存,那么总共就需要 4 * 2^20 Bytes 内存,也就是 4MB4MB 的内存看上去还可以接受,但是每一个进程都需要这么大的内存来存放页表,在现在的操作系统中有 100 进程是很常见的事情,那这里就是 400MB,什么都不干就需要 400MB 的内存来存放页表,这是完全不可以接受的,然后这群地球上最聪明的人又发明了多级页表来解决这个问题。

多级页表

假如我们是 32 位系统下的二级页表,虚拟地址需要存放 一级页号 + 二级页号 + 页内偏移,开始通过一级页号在一级页表中查询到二级页表的地址,然后通过二级页表的地址读取到二级页表,然后通过二级页号在二级页表中查询到对应页的物理地址,最后通过物理地址加偏移获取到最终的物理地址,如下图:

通常这个时候可能你要说了二级页表同样的要 4MB 的空间啊,加上一级页表占用的空间不是比简单页表占用的空间还大吗?二级页表是可以动态加载的,当需要时才会去加载,大部分时间是不用全部加载所有的二级页表的,一级页表占用 4KB,这样就比简单分页占用的空间小得多了。但是这时你可能又会问简单页表如果也动态加载,不是可以更加节省内存,一级页表是不能动态加载的,一级页表必须对虚拟内存地址全覆盖。

如果是 64 位的系统,会有 4 级页表,虽然能够继续降低内存的使用,但是由于页表的层级增多,会导致地址的查询速度变慢,这也就是计算机中常见的时间和空间的问题,所以又开发了专门的页表缓存 TLB,借助它能够快速查询到读取比较频繁的物理地址,它的命中率非常高,只有当它查询失败后才会走常规的查询逻辑。

参考文章

一文带你了解,虚拟内存、内存分页、分段、段页式内存管理

相关推荐
tan77º14 分钟前
【项目】分布式Json-RPC框架 - 项目介绍与前置知识准备
linux·网络·分布式·网络协议·tcp/ip·rpc·json
正在努力的小河3 小时前
Linux设备树简介
linux·运维·服务器
荣光波比3 小时前
Linux(十一)——LVM磁盘配额整理
linux·运维·云计算
LLLLYYYRRRRRTT3 小时前
WordPress (LNMP 架构) 一键部署 Playbook
linux·架构·ansible·mariadb
轻松Ai享生活4 小时前
crash 进程分析流程图
linux
大路谈数字化5 小时前
Centos中内存CPU硬盘的查询
linux·运维·centos
luoqice6 小时前
linux下查看 UDP Server 端口的启用情况
linux
倔强的石头_7 小时前
【Linux指南】动静态库与链接机制:从原理到实践
linux
赏点剩饭7787 小时前
linux中的hostpath卷、nfs卷以及静态持久卷的区别
linux·运维·服务器
神鸟云8 小时前
DELL服务器 R系列 IPMI的配置
linux·运维·服务器·网络·边缘计算·pcdn