内存地址

1. 理解内存地址

1.内存地址的分类

  • 逻辑地址
  • 线性地址(虚拟地址)
  • 物理地址

2.三种内存地址之间的转换
内存控制单元(MMU)通过一种称为分段单元的硬件电路将一个逻辑地址转换为线性地址;紧接着通过第二个分页单元把线性地址转换为物理地址。
在这里插入图片描述

分段可以为每个进程分配不同的线性地址空间,而分页可以把同一线性地址空间映射到不同的物理空间

2. 地址转换

2.1 硬件分段

2.1.1 分段概念

Intel微处理器以两种不同的地址模式运行,分别是实模式保护模式,实模式一般是为了兼容早期的模型(在实模式下通过物理地址直接访问内存),现在我们从保护模式来看硬件中的分段问题

2.1.1.2 段标识符

一个逻辑地址由两部分组成,如下:
在这里插入图片描述

  • 段选择符
    段选择符又称段标识符,是一个16位的字段,如下图:
    在这里插入图片描述

    索引号(index):指定了存放在GDT(全局描述符表)或LDT(局部描述符表)中段描述符的入口,GDT可以看作是一个连续数组,index用作查找段描述符索引,存在段描述符表中最大的项为2^13 - 1,8191。
    表指示器(TI):指定段是存放GDT还是LDT中。
    请求特权级(RPL):cpu特权级。

  • 段内偏移
    段内偏移量是一个32位长的字段。

2.1.1.3 段寄存器

为了方便快速定位段选择符,处理器提供了专门的寄存器去来存放段选择符:
cs:代码段寄存器(含有一个两位的段,指明当前CPU的特权级)
ss:栈段寄存器
ds:数据段寄存器
es、fs、gs:可指向任意数据

2.1.1.4 段描述符

每个段由一个八字节的段描述符表示,它描述了段的特征。段描述符放在全局描述符表(GDT)或局部描述符表(LDT)中
通常只定义一个GDT,但是每个进程在创建了附加段后,可以拥有自己的LDT。GDT在主存的地址和大小存放在gdtr控制寄存器中。当前正在被使用的LDT的地址和大小被存放在ldtr控制寄存器中。

段描述符分类:

  • 代码段描述符
  • 数据段描述符
  • 任务状态段描述符
2.1.1.5 快速访问段描述符

为了加快逻辑地址到线性地址的转换,除了存放段选择符的段寄存器外,80x86处理器还提供了6个专门存放段描述符的非编程寄存器,并由段选择符来指定。每当一个段选择符被装入段寄存器中,相应的段描述符就由内存装入到对应的非编程寄存器中。这样对于访问的段来说,处理器只需要直接应用存放段描述符的寄存器,而无需访问主存中的GDT或LDT了。只有当段寄存器改变时,才需要访问主存去GDT寻找对应的段描述符。
在这里插入图片描述

2.1.2 分段单元

分段单元执行操作:

  1. 检查段选择符的的TI字段,决定使用的是GDT(起始地址在gdtr寄存器中)还是LDT(ldtr)。
  2. 从段选择符中读取index字段,将(index*8 + GDT起始地址)就可以获取段描述符的地址(乘以8是因为段描述符是8字节)。
  3. 段选择符中的偏移量+段描述符中的base值,即为线性地址。

注意:在存在非编程寄存器情况下,只有段寄存器中的内容改变时,才需要进行(1)(2)操作。

2.1.3 linux下使用分段

linux对分段的使用非常有限,只有在特定的架构下才会使用分段(例如linux2.6 80x86结构),相较之下,linux更喜欢使用分页方式,因为:
(1)所有进程可以使用相同的段寄存器,也就是说能共享同样一组线性地址空间,内存管理更简单。
(2)linux方便移植到某些不支持分段处理器平台。

运行在用户态的所有linux进程都使用一对相同的段来对指令和数据寻址,分别是用户代码段和用户数据段;相应的运行在内核态的也是如此,内核代码段和内核数据段。其段描述符的base字段都是0x00000000,这意味着所有与段相关的线性地址是从0开始(同时也可以看出来linux下逻辑地址(偏移量)和线性地址总是相等的),寻址范围0至2^32 - 1,也就解释了为什么linux下每个进程都认为自己在独享4g主存。

2.1.4 linux下的GDT

在单处理器系统中,只有一个GDT,而在多处理系统中,每个CPU都对应一个GDT,每个GDT中包含14个空的或保留的段描述、18个有数据的段描述符:

  • 用户和内核数据段代码段共四个
  • 任务状态段TSS
  • 缺省局部描述符表的段
  • 局部线程存储段三个
  • 高级电源管理AMP段3个
  • PnP功能和BIOS服务程序相关段5个
  • 内核处理双重错误段TSS

2.2 硬件中的分页

分页单元将线性地址转化为物理地址。它的一个关键任务就是确定请求的线性地址是否有效,无效将会产生一个缺页异常。

为了效率,线性地址被分为固定长度的组,称为页;页内连续的地址与物理内存中连续地址相映射。

分页单元把RAM分成固定长度的页框,每个页框包含一个页。把线性地址映射到物理地址的数据结构称为页表,页表放在主存中,并在分页单元启动前由内核进行适当的初始化。

2.2.1 常规分页

从80386起,所有的80x86处理器都支持分页,通过设置cr0寄存器的PG标志开启,分页单元处理4kb的页。

32位的线性地址被分为3个域:
在这里插入图片描述

(1)最高10位,目录(Directory)
(2)中间10位,页表(Table)
(3)最低12位,偏移量(Offset)

常规从线性地址到物理地址的转换分为两步(二级模式),第一步是基于页目录表,第二步是基于页表。使用这种二级模式的优势是减少页表对RAM的使用,如果使用简单的一级页表,每个进程将需要高达2^20个表项(4G/4K),也就说每个表项4字节,需要4MB RAM。

每个活动进程必须要有一个页目录,没必要马上为进程所有页表都分配RAM,实际上在进程需要使用该页表项中的内容时才分配。正在使用的页目录的物理地址存放在控制寄存器cr3中,线性地址中的Directory字段决定页目录中的目录项(目录项指向指定的页表,10位决定了最多拥有1024项);地址中的Table字段决定页表中的表项(页表项含有页所在页框的物理地址);地址中的Offset决定离页框起始地址的偏移(12位决定了页框最大偏移为2^12,4kb)。因为一个页面的寻址范围是 2 ^ 10 x 2 ^ 10 x 2 ^ 12 = 4G

在这里插入图片描述

页目录项和页表项有同样的结构,每项的大小为四个字节(因此页目录和页表的大小也正好等于4kb)。

理解:页目录中目录项和页表中的页表项指向的都是主存中的一个页,与页表项不同的是,目录项页存放的数据是页表,而页表项指向物理内存中具体页框的位置。

2.2.2 扩展分页

从Pentium模型开始,80x86微处理器引入了扩展分页,它允许页框大小为4MB而不是4KB。内核不用中间页表进行地址转换。
在这里插入图片描述

2.2.3 Linux中的分页

linux采用了一种适用于32位和64位系统的普通分页模型。在2.6.10版本,linux采用三级分页模型。从2.6.11版本开始,采用四级分页模型,分别为:

  • 页全局目录(Global Dir)
    包含若干页上级目录地址。
  • 页上级目录(Upper Dir)
    包含若干页中间目录地址。
  • 页中间目录(Middle Dir)
    包含若干页表地址。
  • 页表
    每个页表项指向一个页框。
    线性地址被划分为5个部分,每个部分的大小与具体的计算机体系结构有关。

对于没有启动物理地址扩展的32位系统,两级页表已经足够了,linux通过使页上级目录和页中间目录全为0,从根本上取消了页上级目录和页中间目录。不过为了代码能在32位系统和64位系统中兼容,保留了页上级目录和页中间目录在指针序列中的位置(通过把它们的页目录项数设为1,并将他们映射到页全局目录中一个适当的目录项)。
在这里插入图片描述

2.3 物理内存布局(略)

在初始化阶段,内核必须建立一个物理地址映射来指定那些主存中那些物理地址可用,那些不可用(它们映射了硬件设备IO的共享内存,或是页框中含有BIOS数据等)。

内核将下列页框标记为保留:

  • 不可用物理地址范围内的页框。
  • 页框中含有内核代码和已初始化的数据结构。

保留页框中的页绝不能动态分配或交换到磁盘上去。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐