深度探索Linux系统虚拟化:原理与实现
上QQ阅读APP看书,第一时间看更新

2.1.1 段式寻址

1978年,Intel发布了第一款16位微处理器8086。这款处理器有20根地址线,可以支持的地址空间达到1MB(220字节)。但是,这款处理器的数据总线的宽度是16位,也就是说,指令指针寄存器IP以及其他通用寄存器都是16位的,那么指令最大只能寻址64KB(216字节)的地址空间。为了解决这个问题,Intel的工程师们引入了段的概念,把1MB地址空间划分为多个不超过64KB大小的段,这样程序就可以访问全部地址空间了。8086微处理器有4个段寄存器cs、ds、es和ss,每个段寄存器也是16位的,用于存储段的起始地址,其他寄存器存储的则是段内偏移。因此在这种模式下,程序发起一次内存访问,使用的是如下地址格式,人们将这个地址称为逻辑地址:


Segment Base:Segment Offset

处理器的段单元(Segment Unit),也叫地址加法器,通过如下公式将这个逻辑地址转换为线性地址:


Segment Base << 4 + Segment Offset

这个公式将段基址左移4位,加上段内偏移,生成了20位的物理地址,此时指令可以寻址1MB地址空间的任何地址了。Intel将这种模式称为实模式。

在实模式下,一个主要的问题是没有内存保护。一个程序可以访问整个1MB的地址空间,因此,它也可以访问另一个程序的数据或代码,这是不安全的。除了内存外,实模式对程序访问的资源、执行的指令也没有保护,任何程序都可以执行任何可能导致意外后果的CPU指令。

从80286开始,Intel逐渐开始引入保护模式,到80386时,保护模式得到全面应用。保护模式引入了段描述符表,段描述符表中的每个描述符对应一个段,包括段的基址、段的长度限制,以及段的各种属性(比如段的访问权限等)。段描述符表包括GDT和LDT两种,GDT存储全局的段,每个任务可以有自己的LDT,存储任务自己的段。每个段描述符长度为64位,相比于之前段寄存器只能存储段基址,段描述符可以记录更多的段信息,如增加段访问的权限、读写或是执行的属性等,实现了对内存访问的保护。在有了段描述符表后,段寄存器中存储的不再是段基址,而是一个索引,用于从段描述表中索引具体的段。