那么在保护模式下,我们是如何通过segment:offset最终得到物理地址的呢?
首先,在计算机中存在两个表,GDT,LDT。它们两个其实是同类型的表,前者叫做全局段描述符表,后者叫做本地段描述符表。他们都是用来存放关于某个运行在内存中的程序的分段信息的。比如某个程序的代码段是从哪里开始,有多大;数据段又是从哪里开始,有多大。GDT表是全局可见的,也就是说每一个运行在内存中的程序都能看到这个表。所以操作系统内核程序的段信息就存在这里面。还有一个LDT表,这个表是每一个在内存中的程序都包含的,里面指明了每一个程序的段信息。我们可以看一下这两个表的结构,如下图所示:
我们从图中可以看到,无论是GDT,还是LDT。每一个表项都包括三个字段:
Base : 32位,代表这个程序的这个段的基地址。
Limit : 20位,代表这个程序的这个段的大小。
Flags :12位,代表这个程序的这个段的访问权限。
当程序中给出逻辑地址 segment:offset时,他并不是像实模式那样,用segment的值作为段基址。而是把这个segment的值作为一个selector,代表这个段的段表项在GDT/LDT表的索引。比如你当前要访问的地址是segment:offset = 0x01:0x0000ffff,此时由于每个段表项的长度为8,所以此时应该取出地址8处的段表项。然后首先根据Flags字段来判断是否可以访问这个段的内容,这样做是为了能够实现进程间地址的保护。如果能访问,则把Base字段的内容取出,直接与offset相加,就得到线性地址(linear address)了。之后就是要根据是否有分页机构来进行地址转换了。
比如当前Base字段的值是0x00f0000,则最后线性地址的值为0x00f0ffff。
如上所述就是保护模式下,内存地址的计算方法。
综述,通过上面的叙述可见,保护模式还是要比实模式的工作方式灵活许多,可以在以下几个方面看出来:
1. 实模式下段基地址必须是16的整数倍,保护模式下段基地址可以是4GB空间内的任意一个地址。
2. 实模式下段的长度是65536B,但是保护模式下段的长度也是可以达到4GB的。
3. 保护模式下可以对内存的访问多加一层保护,但是实模式没有。