分段机制概念

来源:互联网 发布:干淘宝网店都需要什么 编辑:程序博客网 时间:2024/06/17 18:33

分段机制概念

0 概述

  CPU通过两种模式进行内存寻址,一种为实模式,一种为保护模式。
  当CPU工作在实模式下时,CPU地址线发出的数据流与内存地址一一对应(可理解为F(x)=x)。而现代CPU都采用保护模式寻址,为了向下兼容以前采用实模式的程序,保存了实模式寻址。当CPU启动时采用实模式并在一段时间后转换为保护模式。
  保护模式的由来是因为CPU地址总线带宽与寄存器位数不一致。产生了分段机制,最后发展而来,包括了分段和分页两种机制。
  保护模式的作用主要有两点,这也是为什么称之为保护的原因
1. 防止进程胡乱访问并修改其他内存数据。
2. 给每个进程提供一个很大的虚拟内存空间,方便程序设计。

本文之后便来介绍分页和分段两种机制。

1 内存寻址

  在分页和分段之前,来说一下内存寻址。
  内存寻址顾名思义就是访问内存。学习过汇编会知道,当我们编译好一个可执行程序时,程序文件里保存了该程序的运行指令运行所需的数据并分别存放在代码区和数据区。在运行程序时,程序数据被加载到内存中。这时CPU就需要在内存中读取该程序的指令并运行。这就涉及到了内存寻址,CPU必须访问并读取存放程序代码的那块内存区域,否则CPU没有指令可以执行,程序也不会运行起来。
  内存寻址的基本单位是Bit,即8个位。物理内存地址从低位0x00000000以每一个Bit为单位向高位递增。每增加一个Bit,物理内存地址+1,4GB的内存地址就是0xFFFFFFFF,这也叫做物理内存地址,相应的还有逻辑(虚拟)地址,线性地址,下文会介绍。通常系统寻址能力取决于CPU地址总线位数,若该位数为N,则可寻址2N Bit的物理内存。而地址总线位数通常和CPU位数一致,即我们常说的32位就可寻址2^32Bit = 4GB的物理内存,64位则可寻址2^64Bit。
  实模式下CPU直接发出物理内存地址给内存相应管理硬件并访问对应物理内存单元,尽管非常容易理解和使用但却存在很多问题。如进程可以修改系统数据威胁系统安全,和在程序设计时对内存的管理变得非常困难,而且还无法保证不同程序不会访问同一地址。因此,现代CPU都是用保护模式来进行内存寻址。
  在保护模式下,程序发出的地址被称为虚拟地址或逻辑地址,这个地址会先经过分段再分页(如果开启分页机制的话)最后变为物理内存地址。逻辑地址先经过CPU内部分段机制后变为线性地址后,再经过MMU(内存管理单元)再访问内存。若没有开启分页机制的话,线性地址就是实际的物理地址,MMU不对该线性地址作映射处理,直接将线性地址传出给内存。如果开启了分页机制,则线性地址会在MMU里被映射到物理内存地址。分页映射可以理解为y=f(x),其中y为物理内存地址,x为线性地址,f(x)为映射规则,同一个x只会产生一个y,但同一个y却可能由不同x产生。
  因而保护模式可以理解为是逻辑地址到实际物理地址的一种映射机制,而且这种映射机制对每个进程来说是不一样的。

2 分段

  前文提到,程序最初产生的地址我们称为逻辑地址,这是一个48位的数字,前16位称为段选择符,是段描述符数组的索引,后32位便是偏移地址。下文会详细介绍。在x86处理器中,分段是必须开启而分页机制是可选的。逻辑地址经过分段机制后变为线性地址,因此分段机制可以理解为一种由逻辑地址到线性地址的映射。
  分段的基本原理是线性地址 = 段基地址 + 偏移地址。
  当CPU发展到20位时,CPU的寄存器仍是16位的,这会造成本来CPU可以访问2^20Bit = 1GB的内存空间寄存器却只能传出最大2^16Bit = 64MB的物理内存地址(在访问内存20位时需要通过寄存器)。为此,CPU使用了分段机制,即让一个寄存器的地址作为段基地址,一个寄存器的地址作为偏移地址。让16位段基地址乘以16(即右移4位变成20位)加上偏移地址。这便组成了20位的线性地址,从而能够访问全部1GB物理内存。这就是分段的基本原理。
  
  了解了基本原理,之后就来看分段的实现。分段的实现中,首先就需要确定段基地址。用来描述段基地址的就被称为段描述符。段描述符可以被视为一个64位的数字,这里面定义了段的线性地址和段的属性,每个段描述符对应一个段(即段基地址),因为存在许多的段,会有许多段描述符。为了高效管理这些段描述符,在Linux内核中定义了一个段描述符数组结构,称为段描述符表。每个段描述符占用8个字节。同时内核定义了一个段选择符作为段描述符数组索引来选择需要的段基地址。段选择符是个16位数字,有了段选择符后,逻辑地址只需包括段选择符和偏移地址即可,不需同时包括段基地址和偏移地址。同时段选择符末三位被用来作为属性,因此段选择符最大只能索引2^13个段描述符。
  分段的实现流程大致如图所示,当一个48位逻辑地址产生后,会先根据段选择符找到对应的段描述符,从描述符中取得对应的段基地址,再将段基地址加上逻辑地址后32位的偏移地址组合成需要的线性地址,这之中会根据段描述符记录的段属性来检测是否能完成操作(如可写可读)。需要注意的是,在x86处理器中,分段是必须开启而分页是可选的。因此当分页机制未开启时通过分段得到的线性地址就是实际上的物理地址。

  分段的原理和实现已经介绍完毕,之后详细介绍段描述符、段描述符表和段选择符的内容。

2.1 段描述符

  段描述符详细结构如图,通常加载的段描述符需要两个寄存器存储,因而图中寄存器位数会有重复,用ab来分别表示。其中

- LIMIT:段限长(a:0-15、b:16-19,总20位),表明段的长度。基本单元根据G颗粒度标志1Bit或者是4K,即最大段长为1MB或4GB。
- BASE:基地址(a:16-31、b:0-7、24-31,总32位),即段的线性地址。
- TYPE:段属性(b:7-11),该字段制定了段的属性,不同类型的段有不同属性,下文会介绍。
- S :S字段(b:12)指明段描述符是系统段描述符(S=0)还是数据段描述符(S=1)
- DPL:特权级(b:8-11),特权级规定了CPU能执行的操作,0-3级。0级最高,3级最低
- P:P字段(b:15)表明了段是否存在内存中(P=1存在)。
- D/B:b:22
- G:颗粒度(b:23)表明了段限长的基本单位。G=0时基本单位为4KB,否则为1Bit。

  段描述符之所以设计的如此奇怪,很大程度上是为了兼容以前的程序。
  系统总共有数据段描述符、代码段描述符、系统段描述符,他们之间互相有所差别,如图所示

其中系统描述符还有以下种类,这里不一一细说了。
- 局部描述符表LDT的段描述符
- 任务状态段TSS描述符
- 调用门描述符
- 中断门描述符
- 陷阱门描述符
- 任务门描述符

2.2 段描述符表

  段描述符表是段描述符的数组。里面依次存放着不同的段描述符。有两种段描述符表,全局描述符表GDT(Global Descriptor Table),局部描述符表LDT(Local Descriptor Table)。
  x86使用GDTR寄存器保存全局描述符表的线性地址,用LDTR寄存器保存局部描述符表的线性地址。

2.3 段选择符

  段选择符详细结构如图,其中

- 0-1:请求特权级RPL(Requested Privilege Level)。
-  2 :描述符表指示标志TI(Table Index),TI=0时表示描述符在GDT中,TI=1时表示描述符在LDT中。
- 3-15:索引值

我们可以看到段选择符末三位被用来作为属性,因此段选择符最大只能索引2^13个段描述符。同时TI标志选择全局或局部描述符,因此可索引的最大段描述符数量GDT和LDT是相等的。

参考

  • 《Linux内核完全剖析》——赵炯
原创粉丝点击