这文章讲述了计算机内存分段的历史和原理,解释了为什么需要分段以及如何使用段基址和段内偏移来访问内存。分段解决了早期计算机内存寻址的限制,使不同程序能够并存于内存中,同时避免了地址冲突。分段概念对计算机体系结构至关重要。
内存结构
首先先来看一下内存的结构吧,如下图所示
这是一段内存,从下向上内存地址依次升高。由于内存是随机读写设备,在进行内存访问时,可以访问任意的内存地址,不需要从头开始访问。比如 CPU 想要访问 0x03 这个内存地址中的数据,只需要将 0x03 这个内存地址写入总线即可送入内存,访问数据送回 CPU。
这种访问方式本来没什么问题,那么好端端的内存为啥要搞个分段呢?
分段是从 8086 CPU 开始的,由于在 8086 那个年代,CPU 和寄存器都是 16 位的,所以计算机还是很昂贵的设备,16 位的寄存器意味着只能寻址 2 ^ 16 次方的,即 65536 字节,64 KB 。那时的计算机还没有虚拟地址的概念,任何内存访问都访问的是实实在在的物理地址。
若要加载程序时,不论是内核程序还是用户程序,程序中的地址都是物理地址,那么这个程序必须固定在内存中的某个地方不会动,这样就可能会存在编译出来两个相同的内存地址,程序只能运行其中一个。针对这种情况,计算机设计人员提出了分段的概念。
分段的本质是让 CPU 采用了段基址 + 段内偏移的方式来访问任意位置的内存。每个应用程序访问的物理地址都会经过重定位,虽然都是访问物理地址,但是不同应用程序编译出来访问物理地址却是不同的。
假设有两个程序,每个大小各为 16 KB
从图上可以看出,这是两个不同的 16KB 程序的装载过程,a 程序首先会跳转到地址 24,那里是一条 MOV 指令,然而 b 程序会首先跳转到地址 28,地址 28 是一条 CMP 指令。这是两个程序被先后加载到内存中的情况,假如这两个程序被同时加载到内存中并且从 0 地址处开始执行,内存的状态就如上面 c 图所示,程序装载完成开始运行,第一个程序首先从 0 地址处开始运行,执行 JMP 24 指令,然后依次执行后面的指令(许多指令没有画出),一段时间后第一个程序执行完毕,然后开始执行第二个程序。第二个程序的第一条指令是 28,这条指令会使程序跳转到第一个程序的 ADD 处,而不是事先设定好的跳转指令 CMP,由于这种不正确访问,可能会造成程序崩溃。
重定位就是把每个应用程序的相对物理地址合成为绝对物理地址的过程,这个描述大家能理解吗?
既然 CPU 要使用段基址 + 段内偏移这种方式来寻址,就必须提供相应的寄存器,段基址用 cs、ds、ss 来表示。程序中需要用到哪部分内存,就直接用段基址定位所在的段,然后给出段内偏移即可找到物理地址。
如上图所示,假如想访问 0x04 地址,那么各应用程序可以给出不同的内存访问方式:段地址为 0x00 ,偏移地址为 0x04,段地址为 0x01,偏移地址为 0x03,段地址为 0x02,偏移地址为 0x02,段地址为 0x03 ,偏移地址为 0x01 ,段地址为 0x04,偏移地址为 0 ,所以,分段后的程序如果想要访问任意内存位置,只需给出段地址 + 偏移地址即可,这是很重要的一个概念。