【精通内核】计算机虚拟内存

59 阅读5分钟

前言

📫作者简介小明java问道之路,专注于研究计算机底层/Java/Liunx 内核,就职于大型金融公司后端高级工程师,擅长交易领域的高安全/可用/并发/性能的架构设计📫 

🏆InfoQ签约博主、CSDN专家博主/Java领域优质创作者、阿里云专家/签约博主、华为云专家、51CTO专家🏆

🔥如果此文还不错的话,还请👍关注 、点赞 、收藏三连支持👍一下博主~

本文导读

本文导读

内存在程序、Linux已经计算机中占有重要地位,本文深度解析计算机内存地址的原理,通过编译时的内存原理,深入浅出逐步讲解物理地址、虚拟内存、分段分页原理、线性地址,以及intel 对内存操作和原理解析。

一、虚拟内存

1、虚拟内存映射关系

读者可以尝试通过gcc(GNU Compiler Collection)编译后的 ELF 文件的虚拟地址都是一样的,这里面有个问题,这些地址都一样,难道不会导致多个程序的地址冲突吗,答案肯定是不会的,本节就会解析虚拟地址、线程地址、物理地址之间的关系和区别。程序员面向的是虚拟地址编程,而线性地址和物理地址是接触不到。

仍然以4GB内存,以及A、B、C,3个需要512MB的进程和操作系统OS为例,地址空间上面说就是一个byte数组,大小是4GB,需要访问这个数组时怎么做?

很明显,通过索引下标i即可。如果要访问一个区间呢?可以给出两个索引,即 start和end。程序A、B、C都拥有这样一个start=0GB、end=4GB,的索引下标,都认为自己拥有了整个4GB内存。它们可以随便使用start、end下标,但当它们真实访问物理内存时,CPU会将start、end转变为空闲的物理内存区间。比如,程序要访问start=0MB-end=512MB的地址空间,CPU可把它们映射为物理地址,即start=512MB-end=1024MB。程序自己用的这一段start和end的索引下标空间,被称为虚拟地址空间;而真实的start和end的索引下标空间,就是物理地址空间。

将虚拟地址空间映射到物理地址空间的工作由 CPU完成,它是怎么做的呢?读者可以想想,什么结构能满足映射关系呢?当然是表结构。毕竟程序员常说查表。我们称保存这种映射关系的表叫作段描述符表

2、内存内碎片

问题又来了,查表需要一个 key,没有key 怎么查 value 呢?再来看看这个key是怎么保存的。在实模式操作物理地址时,我们有几个段寄存器,可以通过 CS:IP 来获取需要执行的指令。进入保护模式后,就不再使用 CS 左移4位+IP地址 来获取真实物理地址了,CPU会改变寻址方式。怎么做呢?以 CS寄存器作为段选择子,也就是上述的 key查段描述符表,这时找到的表项里就包含了这个映射地址和范围了。

现在我们知道,之前实模式使用的段寄存器,由于保护模式的引入,变成了用于查表的段选择子。现在又出现一个问题,key是有了,表在哪里呢?很明显表保存在内存中,因此需要一个寄存器来保存表的首地址和长度,这个寄存器被称为 GDTR,即 全局段描述符表寄存器

现在,有了 key和表,就可以查表了。好像也没解决什么问题?其实问题已经解决了。

试想,如果把程序分成一段一段的,同样映射到的物理地址也是一段一段的,不需要的不映射,是不是可以节约很多空间?由于建立段描述符表是OS来操作的,因此也会保证程序之间的段不会发生踩踏,由 OS来保证。

又有了一个新问题,这个段多大合适?1MB?10MB?

假如分得太大了,如10MB,将会发生这样一种情况。例如在当前地址空间050MB中包含了4个占用10MB内存的程序,地址被划分为010MB(A程序)、10MB20MB(B程序)、20MB30MB(C程序)、30MB40MB(D程序)、40MB50MB(空闲)。

如果B程序用完这段内存,释放了它,则10MB20MB处于空闲状态。如果这时有一个20M的 E程序 申请内存,虽然内存中包含 10MB20MB(空闲)和40MB~50MB(空闲),共 20MB 的空闲空间,但由于它们不连续,因此不能进行分配。这种两个程序之间造成的内存空洞,叫作内存外碎片。继续分析,如果新申请内存的 F程序 数据不满10MB,也只能一次性申请 10MB 内存。例如需要用5MB,但却分了10MB,其中的5MB就浪费了。这种程序内申请的内存和没有用完的空间,叫作内存内碎片

总结

本文的主题虚拟地址、线性地址、物理地址到底是什么,通过intel开发手册了解其原理,顺着这个思路,已足够让读者了解计算机的内存空间了,相信读者对于更深层次的内容也能自己探究完成。