对linux内核中GDT和LDT的理解

GDT(Global Descriptor Table)

  在实模式下当我们需要对一个内存地址进行访问的时候,使用的是 【段基地址:偏移地址】,这样计算出来的地址就是内存的实际地址。但是到了保护模式,内存管理分为段式,和段页式,也就是段模式必不可少。我们先不考虑页模式。
  对段模式来说,访问一个内存仍然用的【段基地址:偏移地址】。IA32允许将一个段的基地址设置为32bit所能表示的任何值,limit(段大小)则可以设置成以2^12为倍数的任何值。(实模式下段基地址只能是16的倍数,因为A20地址线的问题,其低四位是通过左移运算得到的,只能是0,从而达到16bit的段寄存器能表示20bit基地址的目的,limit只能是固定的64kb)。所以,在保护模式下,对一个段的描述包括以下三个方面:【Base Address,Limit,Access】,他们加在一起被放在一个64bit长的数据结构中,被成为段描述符。
  在这种情况下,如果我们直接通过一个64bit段描述符来引用一个段的时候,就必须使用一个64bit长的段寄存器装入这个段描述符。但是intel为了向后兼容,将段基址寄存器仍然规定为16bit(尽管每个段寄存器事实上都有一个64bit长的不可见的部分,但是对于程序员来说,段寄存器就是16bit),那么。很明显,我们无法使用16bit长度的段寄存器来引用64bit的段描述符。
  怎么办呢?解决的办法是把这些长度为64bit的段描述符放入一个数组中,而将段寄存器中的值作为下标索引来间接引用(事实上就是将段寄存器中的高13bit的内容作为索引)。这个全局数据就是GDT。事实上GDT中存放的不仅仅是段描述符,还有其他描述符,他们都是64bit长。随后讨论。
对linux内核中GDT和LDT的理解
GDT可以放在内存的任何位置,当程序员使用一个段寄存器来引用一个段描述符时,CPU必须知道GDT的入口,也就是基地址放在哪里,Intel的设计者提供了一个寄存器GDTR用来存放GDT的入口地址,程序员将GDT设定在内存的某个位置之后,可以通过LGDT指令将GDT的入口地址装入此寄存器,此后,CPU就能根据此寄存器中的内容作为GDT的入口来访问GDT了。

当我们要访问某个段中的一个地址时候:
1.从GDTR中拿到GDT在内存中的基地址,得到段描述符表
2.从段选择子中的前13位得到我们要访问的段的描述符在段描述符表中的索引(需要考虑TI和RPL)
3.从段描述符表中得到要访问的段的描述符,得到其基地址
4.基地址加上偏移地址就是我们要访问的内存地址(当然这里是虚拟地址,接下来是分页机制的功能将虚地址转换为物理地址,不做讨论。)

我们来看一个例子吧:
给出逻辑地址:33h:12345678h转换为线性地址
1.段选择子SEL=33h=0000000000100 0 01b
前13bit是4,则在GDT中选择第四个描述符;TI=0代表是在GDT中选择,
后边的特权级RPL=1,假设第四个段描述符的基地址为11111111h
2.OFFSET=12345678h,则线性地址为:base+offset=11111111h+12345678h=123456789h

对linux内核中GDT和LDT的理解

LDT(Local Descriptor Table)

局部描述符表可以后若干张,每个任务可以有一张,我们可以这样理解GDT和LDT:GDT为一级描述符表,LDT为二级描述符表。
直接看图吧:
对linux内核中GDT和LDT的理解
我们还是来看一个例子吧:
给出逻辑地址:37h:12345678h转换为线性地址
1.段选择子:37h=0000000000100 1 01b,最低第三bit是1,在LDT中选择
2.首先,从GDTR中得到GDT表
3.从以LDTR寄存器为GDT的段选择子,用LDTR的前13bit得到LDT在GDT中的索引,得到LDT的基址(假设为4)
4.使用段选择子37h的前13bit在LDT表中得到LDT描述符(假设是3),从而得到LDT段描述符的基址(假设为11111111h)
5:基地址加上偏移地址12345678得到线性地址:23456789h