关于GDT的理解
在网上看了挺多关于GDT的描述,大都花了大量的篇幅讲述其具体的结构,这里只是单纯从为什么需要GDT来分析GDT的出现。
什么是GDT?
GDT的全称叫做Global Descriptor Table,中文名叫全局描述符。其实在早期x86系统中是没有GDT的,GDT的引入是为了向下兼容和引入保护模式才出现的产物。我们知道当计算机加点时,CPU最开始是运行在实模式上的。要想从实模式运行到保护模式则需要引入GDT。
声明:以下关于GDT的理解不涉及到其具体的内部数据结构,如GDT的内部数据存储格式等等。
为什么会出现GDT?
对于x86操作系统,由于电脑开机时是处于实模式状态下,在实模式状态下所有的内存都是允许被访问操作的,并且在实模式中只能操作20位的地址总线,也就意味着只能访问1MB大小的内存,而实模式下,使用的是16位的寄存器,所以在实模式下访问内存都需要一个16位段寄存器+16位偏移地址进行访问20位的地址空间。而到了32位系统时,则由于CPU无法访问高于1MB的内存就需要进行升级。此时就产生出了GDT。
不考虑兼容性的情况下
假设下,如果不考虑向下兼容,则首先需要32位的地址总线,并且需要一个可以进行寻址的32位段寄存,并且由于寄存器和地址总线的位数相同,完全可以直接用寄存器的值进行寻址,而不需要使用段模式寻址。但是,这样会导致之前所有的程序都无法使用,因为过去原有的程序的寻址方式都发生了改变。并且由于32位寄存器可以访问32位的所有地址(也就意味着进程可以访问任意的物理地址),所以只要能控制该寄存器就可以访问任意的地址,也就对内存没有保护的作用。
GDT的产生
为了能够保证向下兼容并且能够对内存进行保护和划分,全局描述符表就产生了。
我们知道在实模式下只能访问1MB的内存,并且使用的段寄存器+偏移地址的方式进行寻址,并且这种访存方式是不存在限制的。为了能够对内存的操作加以限制(也就对内存进行保护),首先想到的方式就是通过查询一个中间表G1,表G1存储了每个地址段的访问权限,例如程序A想要访问0x0F000的内存,则可以先查询表G1,查看该内存地址是否可以让程序A进行访问。通过这种方式就可以对内存进行保护,这样就解决了内存保护的问题。
现在则需要在向下兼容的基础上访问更大的地址空间,由于向下兼容的缘故,所以段寄存器使用的是16位的,那么如何使用16位的段寄存器去寻址32位的内存空间呢?还是同样的方法,就是让段地址+偏移地址不在访问真实地址(这里指程序想要访问的物理地址),而是访问一个中间表G2,而G2则在内存中保存了想要访问的真实地址段,获取到真正的物理地址后由后续的32位寄存器进行访存操作就可以通过16位段寄存器完成4GB的内存访问。而将中间表G1和中间表G2合并就是全局描述符表(GDT)。而此时的段寄存器就不再是用于存储段地址了(此时段寄存器变为段选择子),而是用于存储访问GDT的下标,所以GDT的最大长度取决于段寄存器使用多少位来表示下标。
总结
GDT保存了内存的访问权限、拓展高地址段、段地址大小等数据。在x86中,每个GDT项大小为64位,而段选择子使用13位用于偏移查询,所以GDT有8192(2的13次方)个GDT项。当采用段式内存管理时,则此时GDT项中的地址存储的就是真正的物理地址。而在页式管理中,GDT中存储的数据就不再是物理地址了,而是页表的地址和偏移(所以也把这种地址叫做线性地址);之后再从页表获取到的地址则是真正的物理地址。