【精通内核】计算机内存寻址原理与物理地址的缺陷

186 阅读5分钟

前言

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

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

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

本文导读

本文导读

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

一、内存寻址

1、内存在编译链路的作用

计算机内存,这里面我们说的是 main memory(DRAM)主内存,语言通过 IDE(Integrated Development Environment )中编写,通过编译器变成 CPU 能工作执行的格式来执行。这些编译好的数据就保存在磁盘上(local disks) ,然后我们用过系统调用告诉操作系统(OS),由操作系统来获取编译好的数据并进行解析,将这些数据从磁盘加载到内存(DRAM) 。编译器就是一个具有前端和后端的整套,将一个语言(源语言)编译为目标语言的工具。

计算机组成原理的一些知识,总线控制总线(CB)、地址总线(AB)、数据总线(DB)CPU位数:CB控制的种类的2^nAB 地址数量的2^nBD一次传输的位数,CPU通过总线来操作内存

2、内存地址

内存要有地址内存的最小寻址单元 1 byte,采用16进制编码地址信息为了避免表示二进制的位数过多。由于对地址进行了编码,所以内存地址存在高低之分(高地址、低地址)。使用16进制给内存中的每个字节编号,这个编号是内存地址。

编译器将代码翻译为目标语言(汇编语言),此时汇编语言的指令,保存在内存上,内存地址是16进制的,每个汇编指令都对应一个16进制的内存地址编号,汇编指令操作外部内存(例如OS内存中的栈内存、堆内存、数据内存、代码段内存,其中数据、代码段内存是静态分配的,堆栈内存是动态分配的,如下图所示,指令流存在于代码段内存中,数据就存在代码段内存中),这个时候CPU控制操作系统分配内存,将指令流通过静态分配保存在内存中。

二、物理地址的缺陷

读者如果对于地址空间的概念感到模糊,可以假定地址空间是Java语言中的一个数组,数组的每一项大小都是一个byte,即8位。用Java语言来描述的话,就是定义

byte memory[] = new byte[4*1024*1024]。

前面我们看到了在8086时代的内存寻址,其实就是通过段寄存器生成20位的地址进行访问,此时访问的地址就是物理地址,这是真实的地址。但是随着时间的推移,到后面我们可以使用的真实物理地址范围从20位到了32位,即4GB的地址空间,再往后面的48位(intel在64位机上的地址总线最大只有48位),我们来看 4GB 的内存空间。

假设有A、B、C,3个程序,各需要占用内存512MB,所以总占用内存就是 3*512=1546MB。同时,操作系统还需要一定占用内存,假定要占用1GB,那么问题来了,如果我们要直接操作物理内存且开发A、B、C程序的是不同开发人员,我们该怎么做?首先,需要清楚A、B、C各占用了哪一段地址空间。假设A占用0512MB,B占用512MB1024MB,C占用1024MB1546MB,操作系统占用最高的3GB4GB,好像直接操作物理地址就可以完成程序的执行了。但是,这样会有很大的问题,具体如下。

1、A、B、C程序和操作系统占用的地址空间不可能一开始就商量好;而且这只是3个程序,如果有更多程序呢,显然这个设想不现实。

2、需要把程序中所有的代码和数据都加载到内存中,比如ELF文件的所有段内容都会被加载(即使应用程序可能根本不需要这些数据,或者写了一个方法不被调用,也需要被加载)

3、如何保证A、B、C程序和操作系统中的程序、数据不会彼此踩踏呢?假设A是个黑客程序,它要修改OS或者其他程序的数据和代码,该如何保证系统安全呢?

正是因为以上问题的存在,人们发现,直接操作物理地址根本是不现实的。

所以,只有在计算机启动时,为了兼容8086,OS 会直接操作物理地址。这种开机直接操作物理地址的模式,被称为实模式,即直接操作8086的那几个段寄存器来生成20位真实物理地址,以完成访存操作。OS完成初始化后,就会进入保护模式下。在保护模式下,引入了虚拟地址的概念。