X86汇编语言-从实模式到保护模式—笔记(16)-第10章 x86处理器编程架构(1)
本帖最后由 兰陵月 于 2017-12-10 20:33 编辑第10章32位x86处理器编程架构
所谓处理器架构,或者处理器编程架构,是指一整套的硬件结构,以及与之相适应的工作状态,这其中的灵魂部分就是一种设计理念,决定了处理器的应用环境和工作模式,也决定了软件开发人员如何在这种模式下解决实际问题。结构内的资源对程序员来说是可见的、可访问的,受程序的控制以改变处理器的运行状态;非架构的资源取决于具体的硬件实现。
处理器架构实际上是不断扩展的,新处理器必须延续旧的设计思路,并保持兼容性和一致性;同时还会有所扩充和增强。
Intel32位处理器架构简称IA-32(Intel Architecture,32-bit),是以1978年的8086处理器为基础发展起来的。32位处理器有32根地址线,数据线的数量是32根或者64根。它可以访问232,即4GB的内存,而且每次可以读写连续的4字节或者8字节,这称为双字(Double Word)或者4字(Quad word)访问。当然也可以按字节或者字来访问内存。
10.1IA-32架构的基本执行环境
【10.1.1寄存器的扩展】
在16位处理器内,有8个通用寄存器AX、BX、CX、DX、SI、DI、SP和BP,其中前4个还可以拆分成两个独立的8位寄存器来用,即AH、AL、BH、BL、CH、CL、DH和DL。32位处理器在16位处理器的基础上,扩展了这8个通用寄存器的长度,使之达到32位。它们的名字分别是EAX、EBX、ECX、EDX、ESI、EDI、ESP和EBP。可以在程序中使用这些寄存器,即使是在实模式下。
32位通用寄存器的高16位是不可独立使用的,但低16位保持同16位处理器的兼容性。
32位处理器可以运行16位处理器上的软件。但它并不是16位处理器的简单增强。事实上,32位处理器有自己的32位工作模式,即32位保护模式。在这种模式下,可以完全、充分地发挥处理器的性能。同时,在这种模式下面,处理器可以使用它全部的32根地址线,能够访问4GB内存。
在32位模式下,为了生成32位物理地址,处理器需要使用32位的指令指针寄存器。为此,32位处理器扩展了IP,使之达到32位,即EIP。当它工作在16位模式下时,依然使用16位的IP;工作在32位模式下时,使用的是全部的32位EIP。EIP和IP寄存器也只由处理器内部使用,程序中是无法直接访问的。对IP和EIP的修改通常是用某些指令隐式进行的,这些指令包括JMP、CALL、RET和IRET等。同样32位模式下,16位的标志寄存器FLAGS同样扩展为32位的EFLAGS。
在32位模式下,对内存的访问从理论上来说不再需要分段,因为它有32根地址线,可以自由访问任何一个内存位置。但是,IA-32架构的处理器是基于分段模型的,因此,32位处理器依然需要以段为单位访问内存,即使它工作在32位模式下。不过,它也提供了一种变通方案,即只分一个段,段的基址是0x00000000,段的长度是4GB。在这种情况下,可以视为不分段,即平坦模型(Flat Mode)。
每个程序都有属于自己的内存空间。在16位模式下,一个程序可以自由地访问不属于它的内部位置,甚至可以对那些地方的内容进行修改。这当然是不安全的,也不合法,但却没有任何机制来限制这种行为。在32位模式下,处理器要求在加载程序时,先定义该程序所拥有的段,然后允许使用这些段。定义段时,除了基地址(起始地址)外,还附加了段界限、特权级别、类型等属性。当程序访问一个段时,护理期将用固件实施各种检查工作,以防止对内存的违规访问。
在32位模式下,传统的段寄存器,如CS、SS、DS、ES,保存的不再是16位段基址,而是段的选择子,即用于选择所要访问的段,因此它的新名字叫段选择器。除了段选择器之外,每个段寄存器还包括一个不可见部分,称为描述符高速缓存器,里面有段的基地址和各种访问属性。这部分内容程序不可访问,由处理器自动使用。
32位处理器增加了两个额外的段寄存器FS和GS。对于某些复杂的程序来说,多出两个段寄存器可能会令它们感到高兴。
【10.1.2基本的工作模式】
8086具有16位的段寄存器、指令指针寄存器和通用寄存器,因此,我们称它为16位的处理器。尽管它可以访问1MB的内存,但是只能分段进行,而且由于只能使用16位的段内偏移量,故段的最大长度只能是64KB。8086只有一种工作模式,叫实模式。
80286是一款16位处理器,它有24根地址总线,可以访问16M内存,但依然只能分成多个段进行。80286首次提出了保护模式的概念。在保护模式下,段寄存器中保存的不再是段地址,而是段选择子,真正的段地址位于段寄存器的描述符高速缓存中,是24位的。因此,运行在保护模式下的80286处理器可以访问全部16MB内存。但由于80286的通用寄存器是16位的,因此段的长度依然不能超过64KB。实模式等同于8086模式,在本书中,实模式和16位保护模式统称为16位模式。在16位模式细啊,数据的大小是8位或者16位的;控制转移和内存访问时,偏移量也是16位的。
80386以及后续的32位处理器,都兼容实模式,可以运行实模式下的8086程序。而且,在刚加电时,这些处理器都自动处于实模式下,此时,它相当于一个非常快速的8086处理器。只有在进行一番设置之后,才能运行在保护模式下。
在保护模式下,所有的32位处理器都可以访问多大4GB内存,它们可以工作在分段模型下,每个段的基地址是32位的,段内偏移量也是32位的,因此,段的长度不受限制。在最典型的情况下,可以将整个4GB内存定义成一个段来处理,这就是所谓的平坦模式。在平坦模式下,可以执行4GB范围内的控制转移,也可以使用32位的偏移量访问任何4GB范围内的任何位置。32位保护模式兼容80286的16位保护模式。
除了保护模式,32位处理器还提供虚拟8086模式(V86模式),在这种模式下,IA-32处理器被模拟成多个8086处理器并行工作。V86模式是保护模式的一种,可以在保护模式下执行多个8086程序。
【10.1.3线性地址】
为IA-32处理器编程,访问内存时,需要在程序中给出段地址和偏移量,因为分段是IA-32架构的基本特征之一。
段的管理是由处理器的段部件负责进行的,段部件将段地址和偏移地址相加,得到访问内存的地址。一般来说,段部件产生的地址就是物理地址。
IA-32处理器支持多任务。在多任务环境下,任务的创建需要分配内存空间;当任务终止后,还要回收它所占用的内存空间。在分段模型下,内存的分配是不定长的,程序大时,就分配一大块内存;程序小时,就分配一小块。时间长了,内存空间就会碎片化,就有可能出现一种情况:内存空间是有的,但都是小块,无法分配给某个人物。为了解决这个问题,IA-32处理器支持分页功能,分页功能将物理内存空间划分成逻辑上的页。页的大小是固定的,一般为4KB,通过使用页,可以简化内存管理。
当页功能开启时,段部件产生的地址就不再是物理地址了,而是线性地址(Linear Address),线性地址还要经页部件转换后,才是物理地址。
线性地址的概念用来描述任务的地址空间。IA-32处理器上的每个任务都拥有4GB的虚拟内存空间,这是一段长4GB的平坦空间,就像一段平直的线段,因此叫线性地址空间。相应地,有段部件产生的地址,就对应着线性地址空间上的每一个点,这就是线性地址。
10.2现代处理器的结构和特点
【10.2.1流水线】
8086时代,处理器就已经有了指令预取队列。当指令执行时,如果总线是空闲的(没有访问内存的操作),就可以在指令执行的同时预取指令并提前译码,这种做法是有效的,能大大加快程序的执行速度。
为了提高处理器的执行效率和速度,可以把一条指令的执行过程分解成若干个细小的步骤,并分配给相应的单元来完成。各个单元的执行时独立的、并行的。如果一来,各个步骤的执行在时间上就会重叠起来,这种执行指令的方法就是流水线(Piple-Line)技术。
一般来说,流水线的效率受执行时间最长的那一级的限制,要缩短各级的执行时间,就必须让每一级的任务减少,与此同时,就需要把一些复杂的任务再进行分解。比如,2000年之后推出的Pentium 4处理器采用了NetBurst微结构,它进一步分解指令的执行过程,采用了31级超深流水线。
【10.2.2高速缓存】
影响处理器速度的另一个因素是存储器。从处理器内部向外看,它们分别是寄存器、内存和硬盘。
寄存器的速度是最快的,原因在于它使用了触发器,这是一种利用反馈原理制作的存储电路。触发器的工作速度是纳秒(ns)级别的,但是价格不菲。动态存储器(DRAM)访问速度通常是几十个纳秒。硬盘等机械和电子混合体,速度最慢,通常在毫秒级(ms)。
在这种情况下,因为需要等待内存和硬盘这样的慢速设备,处理器便无法全速运行。为了缓解这一矛盾,高速缓存(Cache)技术应运而生。高速缓存是处理器与内存(DRAM)之间的一个静态存储器,容量较小,但速度可以与处理器匹配。
高速缓存的用处源于程序在运行时所具有的局部性规律。首先,程序常常访问最近刚刚方位过的指令和数据,或者与它们相邻的指令和数据。比如,程序往往是序列化地从内存中取指令执行的,循环操作往往是执行一段固定的指令。当访问数据时,要访问的数据通常都被安排在一起;其次,一旦访问了某个数据,那么,不久之后,它可能会被再次访问。
利用程序运行时的局部性原理,可以把处理器正在访问和即将访问的指令和数据块从内存调入高速缓存中。于是,每当处理器要访问内存时,首先检索高速缓存。如果要访问的内容已经在高速缓存中,那么,很好,可以用极快的速度直接从高速缓存中取得,这称为命中(Hit);否则,称为不中(miss)。在不中的情况下,处理器在取得需要的内容之前必须重新装载高速缓存,而不只是直接到内存中去取那个内容。高速缓存的装载是以块为单位的,包括那个所需数据的邻近内容。为此,需要额外的时间来等待块从内存载入高速缓存,在该过程中所损失的时间称为不中惩罚(miss penalty)。
【10.2.3乱序执行】
为了实现流水线技术,需要将指令拆分成更小的可独立执行部分,即拆分成微操作(micro-operations),简写为μops。
一旦将指令拆分成微操作,处理器就可以在必要的时候乱序执行程序。也可以使得栈的操作更有效率。
【10.2.4寄存器重命名】
IA-32架构的处理器只有8个32位通用寄存器,但通常都会被我们全部派上用场(甚至还觉得不够)。因此,我们不能奢望在每个计算当中都使用新的寄存器。不过,在处理器内部,却有大量的临时寄存器可用,处理器可以重命名这些寄存器以代表一个逻辑寄存器,比如EAX。
寄存器重命名以一种完全自动和非常简单的方式工作。每当指令写逻辑寄存器时,处理器就为那个逻辑寄存器分配一个新的临时寄存器。
在所有的操作完成之后,临时寄存器的内容写入真实的寄存器中,该处理过程称为引退(Retirement)。
所有通用寄存器,栈指针、标志、浮点寄存器,甚至段寄存器都有可能被重命名。
【10.2.5分支目标预测】
流水线并不是百分之百完美的解决方案。实际上,有很多潜在的因素会使得流水线不能达到最佳的效率。比如说遇到转移指令,我们就必须清空(Flush)流水线,从要转移到的目标位置处重新取指令放入流水线。
随着复杂架构下的流水线变得越来越长,程序分支带来的问题开始变得很大。让处理器的设计者不能接受,毕竟不中处罚的代价越来越高。
为了解决这个问题,在1996年的Pentium Pro处理器上,引入分支预测技术(Branch Prediction)。分支预测的核心问题是,转移是发生还是不会发生。当然这种提前预测是很困难的,几乎不可能。想想看,如果能够提前知道结果,还执行这些指令干嘛。
但是从统计学的角度来看,有些事情一旦出现,下一次还会出现的概率较大。一个典型的例子就是循环。事实上,很多预测通常是很准的。
在处理器内部,有一个小容量的高速缓存器,叫分支目标缓存器(Branch Target Buffer,BTB),当处理器执行了一条分支语句后,它会在BTB中记录当前指令的地址、分支目标的地址,以及本次分支预测的结果。下一次,在那条转移指令实际执行前,处理器会查找BTB,看有没有最近的转移记录。如果能找到对应的条目,则推测执行和上一次相同的分支,把该分支的指令送入流水线。
当然,当该指令实际执行时,如果预测是失败的,那么,清空流水线,同时刷新BTB中的记录。这个代价较大。
页:
[1]