存储管理方式
分段式内存管理
- 原有的 16 位作系统是通过分段式内存管理,在只有四个段寄存器的情况下,每个寄存器里面所存的是基地址。
-
32 位操作系统发生了改变:
- 变化一:
在 32 位时代,段寄存器又增加了两个:fs、gs,用来指向这两个表,分别是gdtr和ldtr,即全局描述符表GDT,也有可能是局部描述符表LDT。
- 变化 2:
段寄存器里面存放的不再是段基地址,而是一个叫段选择子的东西。
-
段选择子:
- 在保护模式下时,由于段基址已经存入了段描述符中,所以段寄存器中再存放段基址是没有意义的,在段寄 存器中存入的是一个叫作选择子的东西。选择子“基本上”是个索引值,虽然它还有其他内容,不过作为初学者暂时忽略也没太大关系。由于段寄存器是 16 位,所以选择子也是 16 位,在其低 2 位即第 0~1 位, 用来存储 RPL,即请求特权级(有兴趣的可以了解一下,不想了解的忽略即可,跟用户态和内核态相关的),可以表示 0、 1、 2、 3 四种特权级。在选择子的第 2 位是 TI 位,即 Table Indicator,用来指示选择子是在 GDT 中,还是 LDT 中索引描述符。 TI 为 0 表示在 GDT 中索引描述符, TI 为 1 表示在 LDT 中索引描述符。选择子的高 13 位,即第 3~15 位是 描述符的索引值,用此值在 GDT 中索引描述符。前面说过 GDT 相当于一个描述符数组,所以此选择子中的索引值就是 GDT 中的下标。
- 在保护模式下时,由于段基址已经存入了段描述符中,所以段寄存器中再存放段基址是没有意义的,在段寄 存器中存入的是一个叫作选择子的东西。选择子“基本上”是个索引值,虽然它还有其他内容,不过作为初学者暂时忽略也没太大关系。由于段寄存器是 16 位,所以选择子也是 16 位,在其低 2 位即第 0~1 位, 用来存储 RPL,即请求特权级(有兴趣的可以了解一下,不想了解的忽略即可,跟用户态和内核态相关的),可以表示 0、 1、 2、 3 四种特权级。在选择子的第 2 位是 TI 位,即 Table Indicator,用来指示选择子是在 GDT 中,还是 LDT 中索引描述符。 TI 为 0 表示在 GDT 中索引描述符, TI 为 1 表示在 LDT 中索引描述符。选择子的高 13 位,即第 3~15 位是 描述符的索引值,用此值在 GDT 中索引描述符。前面说过 GDT 相当于一个描述符数组,所以此选择子中的索引值就是 GDT 中的下标。
-
x86 寻址逻辑:
- 通过指示位找到是全局描述符表还是局部描述符表
- 在描述符当中找到段基地址
- 根据段基地址和偏移地址找到最后的逻辑地址
-
全局描述符 GDT 介绍:
-
GDT全称Global Descriptor Table,是x86保护模式下的一个重要数据结构,在保护模式下,GDT在内存中有且只有一个。GDT的数据结构是一个描述符数组,每个描述符8个字节,可以存放在内存当中任意位置。
-
一个GDT段描述符占用8个字节,包含三个部分:
- 段基址(32位),占据描述符的第16~39位和第55位~63位,前者存储低16位,后者存储高16位。
- 段界限(20位),占据描述符的第0~15位和第48~51位,前者存储低16位,后者存储高4位。
- 段属性(12位),占据描述符的第39~47位和第49~55位,段属性可以细分为8种:TYPE属性、S属性、DPL属性、P属性、AVL属性、L属性、D/B属性和G属性。
-
-
实模式和保护模式:
-
实模式和保护模式都是 CPU 的工作模式,而 CPU 的工作模式是指 CPU 的寻址方式、寄存器大小等用来反应 CPU 在该环境下如何工作的概念。
-
实模式工作原理
实模式出现于早期 8088CPU 时期。当时由于 CPU 的性能有限,一共只有 20 位地址线(所以地址空间只有 1MB),以及 8 个 16 位的通用寄存器,以及 4 个 16 位的段寄存器。所以为了能够通过这些 16 位的寄存器去构成 20 位的主存地址,必须采取一种特殊的方式。当某个指令想要访问某个内存地址时,它通常需要用下面的这种格式来表示:
(段基址:段偏移量)
其中第一个字段是段基址,它的值是由段寄存器提供的(一般来说,段寄存器有 6 种,分别为 cs,ds,ss,es,fs,gs,这几种段寄存器都有自己的特殊意义,这里不做介绍)。
第二字段是段内偏移量,代表你要访问的这个内存地址距离这个段基址的偏移。它的值就是由通用寄存器来提供的,所以也是 16 位。那么两个 16 位的值如何组合成一个 20 位的地址呢?CPU 采用的方式是把段寄存器所提供的段基址先向左移 4 位。这样就变成了一个 20 位的值,然后再与段偏移量相加。
即:
物理地址 = 段基址 <<4 + 段内偏移
DS * 16 + SI
所以假设段寄存器中的值是 0xff00,段偏移量为 0x0110。则这个地址对应的真实物理地址是 0xff00<<4 + 0x0110 = 0xff110。
由上面的介绍可见,实模式的"实"更多地体现在其地址是真实的物理地址。
-
-
保护模式:
- 偏移值和实模式下是一样的,就是变成了 32 位而已,而段值仍旧是存放在原来 16 位的段寄存器中,但是这些段寄存器存放的却不再是段基址了,毕竟之前说过实模式下寻址方式不安全,我们在保护模式下需要加一些限制,而这些限制可不是一个寄存器能够容纳的,于是我们把这些关于内存段的限制信息放在一个叫做全局描述符表(GDT)的结构里。全局描述符表中含有一个个表项,每一个表项称为段描述符。而段寄存器在保护模式下存放的便是相当于一个数组索引的东西,通过这个索引,可以找到对应的表项。段描述符存放了段基址、段界限、内存段类型属性(比如是数据段还是代码段,注意一个段描述符只能用来定义一个内存段)等许多属性。
-
分页式内存管理:
-
操作系统将内存空间按照“页”为单位划分了很多页面,这个页的大小默认是4KB(当然可以改的),各进程拥有虚拟的完整的地址空间,进程中使用到的页面会映射到真实的物理内存上,程序中使用的地址是虚拟地址,CPU在运行时自动将其翻译成真实的物理地址。
-
页表是用来记录虚拟内存页面和物理内存页面之间的映射关系的,每一个页表项记录一个页面的映射关系。但进程的地址空间很大,这样算下来需要的页表项的数量也会非常多。而实际上进程地址空间中很多页面都没有真正使用,也就没有映射关系,这样是一种浪费。
为了解决这个问题,CPU引入了多级页表的机制,在32位下一般是2级页表,像下面这样:
将虚拟地址划分了三段:页目录索引、页表索引、页内偏移。
线程切换时,如果同时发生了进程切换,CPU中的CR3寄存器将会加载当前进程的页目录地址。
在寻址的时候,通过CR3,一级一级按表索页,最终找到对应的物理内存页面,再结合页面内的偏移值,实现最终的内存寻址。