从80286开始,intel处理以两种不同的方式执行地址转换,即实模式(real mode)和保护模式(protected mode)。
只所以从286开始有这两种方式,是因为286之前的cpu工作在实模式,这实际上一种不健壮的模式,不适用多进程,所以在80286开始启用保护模式,仍然存在实模式,一是因为向上兼容,二是在系统启动的时候(如在BIOS执行时)还是需要这种模式的,这个情况有点像动态链接器(其本身也是一个动态对象)被加载的时候,需要一种类似自举的方式。
大概类似于七夕和2.14吧,很久很久以前,我们是只过七夕的,后来的后来,2.14成了主题,7.7也仍然存在。我去,宅了一天就出去吃两顿饭的功夫,已经被各种高举的花刺瞎狗眼。呵呵,开个玩笑的,还是祝大家v节快乐,没有脱单的单身狗也早日脱单,
啰嗦了,言归正传。
逻辑地址、线性地址、物理地址
1,逻辑地址,汇编指令中用来指定一个操作数或者指令的地址,由一个基地址(段)和一个偏移量组成。
2,线性地址,也成虚拟地址,就是常说的0x00000000-0xffffffff(32bit)。
3,物理地址,就是内存芯片了。
MMU负责各个地址的转换,MMU的分段单元可以讲逻辑地址转化为线性地址, 其分页单元可以讲线性地址转化为物理地址。
段选择符和段寄存器
前面说了逻辑地址有一个基地址和一个偏移量组成,其中基地址是一被称为段选择符,在32位机器上是16位数,偏移量为32位。
其16位数分别为index字段13位,指定了放在GDT和LDT中的相应段描述符的入口。
TI(table indicator)字段1位,为0表示在GDT中,为1表示在LDT中
RPL,cpu特权级
为了快速找到段选择符,处理器提供段寄存器,其唯一目的就是存放段选择符,即我们在汇编中常见的
1,cs,专用寄存器之代码段寄存器,指向包含程序指令的段,其最后两位组成的数表示cpu当前特权级,0最高,3最低,linux只有0和3。
2,ss,专用寄存器之栈段寄存器,指向包含当前程序栈的段。
3,ds,专用寄存器之数据段寄存器,指向静态或者全局数据段。
4,es,通用段寄存器
5,fs,通用
6,gs,通用
段描述符
每个段都由一个8字节的段描述符表示,它描述了段的特征。
段描述符存放在全局描述符表(GDT)或局部描述符表(LDT)中,通常一个cpu有一个GDT,而每个进程除了存放在DGT中的段之外如果还需要创建附件的段,可以有自己的LDT,所以LDT是进程级的。GDT和当前正在的LDT,其大小和地址分别存放在gdtr和ldtr控制寄存器中。
下面为段描述符中的字段(共八个字节,64位)
S | 系统标志,如果被清零,表示一个系统段,存储如LDT这样的关键数据结构,否则是普通代码或者数据段 |
Base | 段的首字节的线性地址 |
G | 粒度标志,为0表示以字节为单位,否则以4096字节为单位 |
Limit | 段中最后一个内存单元的偏移量,从而决定段的长度 |
Type | 段的类型和它的存取权限 |
DPL | 描述符的特权级 |
P | 是否在主存中,linux总是设置为1,因为linux从来不把整个段交换到磁盘上去 |
D或B | 是代码段还是数据段 |
为了加速逻辑地址到线性地址的转化,x86提供一种附加的非编程的寄存器,供6个可编程的段寄存器使用。
每当一个段选择符被装入段寄存器时,相应的段描述符就由内存装入到对应的非编程寄存器中,自此其,针对那个段的逻辑地址就可以不用访问主存中的GDT和LDT,cpu直接引用存放段描述符的寄存器即可,只有当段寄存器内容发生改变时,才有必要访问GDT和LDT。
逻辑地址到线性地址的转换:
1,先检查TI,以决定是在那个描述符表中,可以从对应的ldtr/gdtr中获取其大小和地址。
2,index*8加上dgtr或者ldtr
3,将逻辑地址的偏移量加上段描述符的Base字段就得到线性地址。
分页,页(page)和页框(page frame)