转于 https://www.cnblogs.com/mdumpling/p/8494806.html

段选择器

Intel对分段内存管理逻辑地址的定义是【段号+段内地址】。在x86-16体系的分段管理中,CPU给出的内存地址是16位的段内偏移地址,段的基址从段寄存器中获得,最后计算出24位的物理地址。 而在IA-32体系中引入了保护模式,每个进程有4G的独立地址空间,CPU直接给出的是32位的段内偏移地址,段的基址从内存中获得,最后计算出32为的物理地址。他们最大的不同就在于获取基址的方式以及计算方法。

IA-32为了保持向前兼容,保留了CS/DS/ES/SS这4个寄存器,但因为不在从段寄存器中获得段价值,这4个段寄存器实际上已经失去了原本的作用(但不代表没有使用)。IA-32在内存中使用一张段表来记录各个段映射的物理内存地址。在译地的过程中,x86-16是通过16位的段基址和16位的段内偏移不是简单的相加,而是通过 段值*0x10 + 偏移地址 对基址重定向的方式计算得到物理地址,而IA-32中则相对简单,不需要对基址重定向,这一点和前面分页内存管理是相似的。而CPU只需要为这个段表提供一个记录其首地址的寄存器就可以了。 同样也可以使用TLB来加速。
在IA-32中保留的CS/DS/ES/SS这4个16位段寄存器不再被解释为段的基地址,Intel为了保持兼容性将这些寄存器的16个位分成3个用于不同功能的域,称为段选择器。

其中3-15是选择子,存放的是段描述符的索引(可以理解为段号),该描述符为64bit用于描述存储器段的位置、长度和访问权限。而段描述符可以分为两种,全局描述符(GDT)和局部描述符(LDT),对应着第2位,而0,1两位是表示CPU的权限级别(0-4级)。在IA-32中一共有6个段选择器

image.png

CS保存了代码段描述符的索引;
DS保存了数据段描述符的索引;
SS保存堆栈段描述符索引;

ES、FS、GS则作为一般用途,可以指向任意的数据段,实现自定义寻址。

段描述符

段描述符就是前面说到的段表中的每一个项目的,一个段描述符由8个字节组成。它描述了段的特征。前面提到段描述符可以分为GDT和LDT两类。通常来说系统只定义一个GDT,而每个进程如果需要放置一些自定义的段,就可以放在自己的LDT中。IA-32中引入了GDTR和LDTR两个寄存器,就是用来存放当前正在使用的GDT和LDT的首地址。

上面的图是Linux中不同段的描述符,结构基本是一致的,只有少数字段有差别。其中最重要的就是BASE字段,一共32位,保存的是当前段的首地址。 在Linux系统中,每个CPU对应一个GDT。一个GDT中有18个段描述符和14个未使用或保留项。其中用户和内核各有一个代码段和数据段,然后还包含一个TSS任务段来保存寄存器的状态。其他的段则包括局部线程存储,电源管理,即插即用等多个段。而Linux系统中,大多数用户态的程序都不使用LDT。

段地址计算

在IA-32中,逻辑地址是16位的段选择符+32位偏移地址,段寄存器不在保存段基址,而是保存段描述符的索引。

  • IA-32首选确定要访问的段(方式x86-16相同),然后决定使用的段寄存器。
  • 根据段选择符号的TI字段决定是访问GDT还是LDT,他们的首地址则通过GTDR和LDTR来获得。
  • 将段选择符的Index字段的值*8,然后加上GDT或LDT的首地址,就能得到当前段描述符的地址。(乘以8是因为段描述符为8字节)
  • 得到段描述符的地址后,可以通过段描述符中BASE获得段的首地址。

总地址计算

  • CPU给出要访问的逻辑地址;
  • 通过分段内存管理的地址转换机制,将逻辑地址转换为线性地址,也就是分页系统中的虚拟地址;
  • 通过分页内存管理的地址转换机制,将虚拟地址转换为物理地址;