谈谈分段机制
在Lab2的笔记中,比较详细地描述了内存管理中的分页机制,但是并没有讲分段机制。这篇文章试着讲讲。
1 |
|
这是整个地址的转换过程。
从实模式讲起
在实模式中,需要寻址20位的空间,2^20即1MB。但4个相关的寄存器CS(code segment), DS(data segment), SS(stack segment), ES(extra segment)都是16位的。寻址时用到16位的CS和一个16位的IP(Instruction Pointer):把16位的CS左移4位,低4位就全为0,再加上16位的IP,得到20位的地址,即为物理地址。这样也被称为CS:IP对。
注意:每个segment都是16位的,2^16(64KB)。但每段并不一定用完这64KB。而且不同的CS:IP对也可能计算得出相同的物理地址,所以在内存空间中,不同的segment可能会有重叠。
保护模式
80286中的保护模式
从80286中,有了保护模式,现在可寻址24位的空间,2^24(16MB)。但并不是通过继续左移CS来完成的。现在的CS里有一个index,指向一个segment descriptor,在这个descriptor中有一个24位的基地址,用16位的IP加上这个基地址就得到了物理地址。
80386中的保护模式
80386在分段后多了一个分页机制,还多了两个段寄存器FS和GS,CPU没有指定这两个寄存器的用途。下面是80386的一些寄存器。
主要看segment register,每个寄存器都是16位,但是还有不可见部分。
在可见部分的16位,结构如下:
每个16位中都有13位的index,最多可指向2^13(8192)个descriptor。Table Indicator指示是GDT还是LDT。RPL是保护模式中的等级。
GDT(global descriptor table)就像一个数组,每项为64位(8byte),最多可以有8192项,每项的结构为:
有32位的基地址,segment limit的长度为20位。其他的是一些权限、控制位。
保护模式的流程
在80386中,有GDT和LDT。通过SGDT(store)得到GDTR(GDT Register),GDTR里存储了GDT的基地址和GDT的大小。LGDT(load)存入GDTR。LDT同理。寻址时,通过DS/CS等segment register中的index找到对应的index,乘以8后加上GDTR中的基地址,即可找到对应的项(index*8+基地址,*8是因为segment descriptor的大小为8byte)。在segment descriptor中得到对应的segment的基地址后,加上32位的EIP,形成线性地址,下一步就是分页。
通过这种方式,使得GDT和LDT存储了一些全局的和局部的descriptor,程序运行时,通过对应的segment register找到这些descriptor,这样就形成了分段。通过不同的段进行第一层的内存管理。
扁平模式
然而,后来的操作系统并不想使用这个从16位系统遗留下来的分段机制。但是CPU为了兼容性还保留了分段机制,而且不能关闭,所以许多操作系统模拟扁平模式。具体做法是让kernel CS/kernel DS/user CS/user DS对应的segment descriptor基地址都为0,limit都为4GB,这样就使得通过分段后,得到的线性地址为0+EIP还是等于EIP,分段对EIP无影响。而EIP是32位的,已经能独立寻址4GB的空间,也是在limit的范围之内的。通过这种方式,在不关闭分段机制的前提下使得地址转换不受分段机制的影响。
有一个问题是limit位只有20位,如何能表示32位(4GB)的空间?这是因为在segment descriptor中有一个Granularity位,为0时表示limit的单位为byte,此时最多包括2^20(1MB)的空间;为1时表示limit的单位为4096byte(一页),此时最多包括2^20(1M)*4KB=4GB的空间,和之前的是相对应的。