补充:关于GDT/LDT、段选择子和段描述符的解释
GDT/LDT:GDT/LDT是段描述符表,里面定义了每个段的段描述符的界限和属性,而段描述符的基址是在代码段中初始化的。
其中,LDT是局部描述符表,LDT在GDT中也有段描述符,LDT还有其对应的选择子,除此以外,LDT的功能和GDT一样。
段选择子:段选择子是用来寻址段描述符的,即段描述符定义在GDT中,由段选择子将其赋值给段寄存器。如果TI被置位为1,那么系统将从当前LDT中寻找相应描述符,TI为0就从GDT中寻找相应描述符。
这是段选择子的结构:
段描述符:定义了段的基址,界限和属性,定义在GDT中,由段选择子寻址。段描述符先在实模式中初始化完基址,再在保护模式中由段选择子寻址将其赋值给段寄存器。
最近在学于渊的《Orange’s:一个操作系统的实现》,学到GDT(Global Descriptor Table),即段描述符表。段描述符表中存储的是段描述符的索引。网上也有很多关于段描述符的详解,但是我看着很费劲,所以想自己写下自认为容易理解的对段描述符的描述。
段描述符记录的是地址空间上的一些属性,如一个地址空间是否可以被写入,可以被多少优先级的代码写入,是不是允许执行等的一些属性。段描述符是64位的所以需要8个字节的空间来存储,下面是用NASM语法写的描述符的数据结构:
; 描述符
; usage: Descriptor Base, Limit, Attr
; Base: dd
; Limit: dd (low 20 bits available)
; Attr: dw (lower 4 bits of higher byte are always 0)
%macro Descriptor 3
dw %2 & 0FFFFh ; 段界限1
dw %1 & 0FFFFh ; 段基址1
db (%1 >> 16) & 0FFh ; 段基址2
dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性1 + 段界限2 + 属性2
db (%1 >> 24) & 0FFh ; 段基址3
%endmacro ; 共 8 字节
这里注意这两句话:
; Limit: dd (low 20 bits available)
; Attr: dw (lower 4 bits of higher byte are always 0)
段界限定义了32位,但是只有低20位有效,属性定义了16位,但是高8位的低4位总是0。
我对NASM语法也不是很熟悉,所以要在这里多说明一些。
在nasm中:%macro是定义多行宏,%macro Descriptor 3 是定义名为Descriptor的宏,接受3个参数,三个参数依次为基址,界限和属性。%1表示的是参数1(段基址),%2表示参数2(段界限),%3表示参数3(段属性)。将来传进来的参数就替换了%1,%2,%3。
代码还是需要结合段描述符的具体结构来看:
这里用的是代码段和数据段描述符的结构,此外,描述符的种类还有系统段描述符和门描述符,这里先不多余介绍。从上图来看,段基址是32
现在来看代码吧!
第一行: dw %2 & 0FFFFh ; 段界限1
这行很好理解,段界限1双字型参数2的低16位。
第二行: dw %1 & 0FFFFh ; 段基址1
段基址1取双字型参数1的低16位。
第三行: db (%1 >> 16) & 0FFh ; 段基址2
段基址2取双字型参数1的高16位的低8位。
第四行: dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性1 + 段界限2 + 属性2
就是这第四行把我搞得晕头转向。。。
((%2 >> 8) & 0F00h)这一部分是段界限2,也就是参数2的高16位的高8位的低4位。
(%3 & 0F0FFh)这一部分是属性1和属性2,属性1就是字型参数3的高8位的高四位,属性2就是参数3的低8位。
第五行: db (%1 >> 24) & 0FFh ; 段基址3
段基址3取双字型参数1的高16位的高8位。
总的来说,Descriptor 3这个宏就是将描述符的64位用nasm语法分块描述出来。
如果想了解段描述符的各个位的属性,可以参照:段描述符属性参照表