一、通用概念
下面的内容将从程序员的角度介绍x86处理器系列及其主机系统的体系结构。其中包括所有Intel IA-32和Intel 64处理器,如Intel Pentium和Core Duo,以及Advanced Micro Devices(AMD)处理器,如Athlon、Phenom、Opteron和AMD64。
1.1 基本的微机设计
下图展示了一个假想的计算机的设计结构:
其中central processor unit(CPU)负责进行计算和逻辑操作,其中包含一组存储信息的寄存器,一个高频率时钟(clock),一个控制单元(control unit)以及一个算术逻辑单元:
- 时钟将CPU内部的运算和其他系统部件进行同步
- 控制单元(control unit,CU)协调执行一串机器指令的顺序
- 算术逻辑单元(arithmetic logic unit,ALU)执行算术运算和逻辑运算,例如加减法,与或非等。
CPU通过连接到计算机主板CPU插槽的插脚连接到计算机的其余部分。大多数引脚连接到数据总线、控制总线和地址总线。内存存储单元是在计算机程序运行时保存指令和数据的地方。存储单元接收来自CPU的数据请求,将数据从随机存取存储器(RAM)传输到CPU,并将数据从CPU传输到存储器。所有的数据处理都在CPU中进行,因此驻留在内存中的程序必须先复制到CPU中,然后才能执行。程序指令可以被一次一条复制到CPU中,也可以分组一起进行复制。
总线是一组将数据从计算机的一部分传输到另一部分的平行导线。计算机系统通常包含四种总线类型:数据、I/O、控制和地址。数据总线在CPU和内存之间传输指令和数据。I/O总线在CPU和系统输入/输出设备之间传输数据。控制总线使用二进制信号来同步连接到系统总线的所有设备的动作。当当前执行的指令在CPU和内存之间传输数据时,地址总线保存指令和数据的地址。
时钟:涉及CPU和系统总线的每个操作都由一个以恒定速率脉冲的内部时钟同步。机器指令的基本时间单位是机器周期(或时钟周期)。时钟周期的长度是一个完整的时钟脉冲所需的时间。在下图中,时钟周期被描绘为一个下降沿和下一个下降沿之间的时间:
时钟周期的持续时间是以时钟速度的倒数来计算的,而时钟速度又是以每秒的振荡来衡量的。例如,每秒振荡10亿次(1千兆赫)的时钟产生持续时间为十亿分之一秒(1纳秒)的时钟周期。
一条机器指令至少需要一个时钟周期才能执行,少数指令需要超过50个时钟周期(例如8088处理器上的乘法指令)。由于CPU、系统总线和内存电路的速度不同,需要内存访问的指令通常有称为等待状态的空时钟周期。
1.2 指令执行周期(instruction execution cycle)
我们所定义的一条机器指令并不会被立即执行。CPU必须经过一系列预定义的步骤才能执行机器指令,称为指令执行周期。假设指令指针寄存器保存了我们要执行的指令的地址。执行步骤如下:
- 首先,CPU必须从一个称为指令队列(instruction queue)的内存区域获取指令。在执行此操作之后,它立即递增指令指针
- 接下来计算机通过读取到的二进制数据解码指令。解码后的指令可能包含操作数。
- 如果包含了操作数,那么CPU会从寄存器或内存中取得操作数。有时这个过程包含了地址的计算
- 接下来CPU会执行指令(此时我们已经有我们需要的操作数了)。指令执行时会更新几个标志位的值。
- 最终,如果指令需要输出操作数,CPU会将存储结果存储在输出的操作数中。
我们可以将上述步骤简化为三步:获取数据、解码和执行。
图2-2显示了典型CPU内的数据流框图。该图有助于显示在指令执行周期中交互的组件之间的关系。为了从存储器中读取程序指令,我们需要传递给地址总线一个地址。接下来,内存控制器将我们所请求的代码放在数据总线上,使代码在代码缓存中可用。指令指针的值决定下一步执行哪条指令。指令解码器对指令进行分析,将适当的数字信号发送到控制单元,控制单元协调ALU和浮点单元。虽然控制总线没有显示在这个图中,但它携带的信号使用系统时钟来协调不同CPU组件之间的数据传输。
1.3 从内存中读取数据
计算机从内存中读取数据比从寄存器内部读取数据要慢很多。这时因为从内存中读取数据需要经过以下四个步骤:
- 将我们要从内存中读取的数据的地址放置在地址总线上
- . Assert (change the value of) the processor’s RD (read) pin.
- 等待一个时钟周期,等待内存芯片响应。
- 将数据从数据总线复制到目标操作数。
这些步骤中的每一步通常都需要一个时钟周期。计算机CPU通常用它们的时钟速度来描述。例如,1.2GHz的速度意味着时钟每秒振荡12亿次。因此,考虑到每一个时钟周期只持续1/1200000000秒,4个时钟周期的速度相当快。不过,这比CPU寄存器慢得多,CPU寄存器通常可在在一个时钟周期内进行访问。
幸运的是,CPU设计者很久以前就发现,因为大多数程序必须访问变量,所以计算机内存会造成速度瓶颈。他们想出了一个聪明的方法来减少读写内存的时间,他们将最近使用的指令和数据存储在高速内存中,称为缓存。其思想是,程序更可能希望重复访问相同的内存和指令,因此cache将这些值保存在可以快速访问的位置。另外,当CPU开始执行一个程序时,它可以向前看,并将下千条指令(例如)加载到缓存中,前提是这些指令很快就会被需要。如果该代码块中碰巧有一个循环,那么缓存中也会有相同的指令。当处理器能够在高速缓存中找到数据时,我们称之为高速缓存命中(cache hit)。另一方面,如果CPU试图在缓存中找到某个东西,但它不在那里,我们称之为缓存未命中(cache miss)。
x86系列的高速缓存有两种类型。一级缓存(或主缓存)存储在CPU上。二级缓存(或二级缓存)稍微慢一点,通过高速数据总线连接到CPU。这两种缓存以最佳方式协同工作。
高速缓存比传统的RAM快的原因之一是因为高速缓存是由一种叫做静态RAM的特殊内存芯片构成的。它是昂贵的,但它不必不断刷新,以保持其内容。另一方面,称为动态RAM的传统内存必须不断刷新。速度慢得多,但更便宜。
1.4 加载并执行程序
在程序可以运行之前,它必须由一个称为程序加载器(program loader)的应用程序加载到内存中。加载后,操作系统必须将CPU指向程序的入口点,即程序开始执行的地址。以下步骤更详细地分解了此过程:
- 操作系统(OS)在当前磁盘目录中搜索程序的文件名。如果在那里找不到名称,它将在预定的目录列表(称为路径)中搜索文件名。如果操作系统找不到程序文件名,就会发出错误消息。
- 如果找到了程序文件,操作系统将从磁盘目录中检索有关程序文件的基本信息,包括文件大小及其在磁盘驱动器上的物理位置。
- 操作系统确定内存中的下一个可用位置,并将程序文件加载到内存中。它为程序分配一块内存,并将有关程序大小和位置的信息输入一个表(有时称为描述符表)。此外,操作系统可以调整程序中指针的值,使其包含程序数据的地址。
- 操作系统开始执行程序的第一条机器指令(其入口点)。程序一开始运行,就称为进程。操作系统为进程分配一个标识号(进程ID),用于在运行时跟踪进程。
- 这个过程是自己运行的。操作系统的任务是跟踪进程的执行并响应对系统资源的请求。例如内存、磁盘文件和输入输出设备。
- 当进程结束后,它将从内存中被移除
二、32位x86处理器
这篇文章着重介绍32位x86处理器的各种基本特征,32位x86处理器包括了Intel IA-32处理器以及AMD 32位处理器。
1.1 运行模式
x86处理器有三种基本的运行模式:保护模式(protected mode)、实地址模式(real-address mode)和系统管理模式(system management mode)。此外,还有一种称为virtual-8086的子模式,这个子模式是保护模式的一个特殊变种。接下来简要介绍下每种模式:
保护模式
保护模式是处理器最初始的运行模式。在保护模式下, all instructions and features are available。每个程序都给定了一块独立的内存空间,称为段,且处理器会阻止程序引用不在自己段中的内存。
Virtual-8086 模式
当处于保护模式时,处理器可以在安全的环境下直接执行实地址模式的程序(例如MS-DOS程序)。换句话说,如果一个程序崩溃或者尝试在系统内存地址写入信息,它将不会影响到正在执行的其他程序。现在的操作系统可以同时执行多个独立的virtual-8086会话(session)。
实地址模式
实地址模式采用和8086相同的16位段和偏移量,最大寻址空间1MB。它是CPU启动时的模式,实地址模式和早期8086CPU存在着一个重要的区别,这就是实地址模式拥有转换成其他模式的能力。
系统管理模式
系统管理模式为操作系统提供了实现一些特殊功能的能力,这些功能可以是电源管理及系统安全管理等。注意这些功能通常由电脑生产商实现,他们会制定有关系统设置的部分功能。
2.2 程序执行环境
2.1 地址空间
在32位保护模式下,一个程序可以线性寻址到最大4GB的地址空间。但从P6处理器开始,一种称为extended physical addressing的技术允许寻址到最大64G的物理空间。实地址模式的程序只能寻址1MB范围的地址,如果处理器正在保护模式下运行并且在虚拟8086模式下执行了多个程序,那么每个程序都有1MB的内存空间。
在实地址模式下,程序只能寻址1MB大小的空间。
2.2 寄存器
寄存器介绍
32位CPU所含有的寄存器有:
- 8个32位通用寄存器,其中包含4个数据寄存器(EAX、EBX、ECX、EDX)、2个变址寄存器(ESI和EDI)和2个指针寄存器(ESP和EBP)
- 6个段寄存器(ES、CS、SS、DS、FS、GS)
- 1个指令指针寄存器(EIP)
- 1个标志寄存器(EFLAGS)
通用寄存器
通用寄存器最常被用来进行算术运算和数据寻址,如下图所示(以eax为例),通用寄存器的低16位都可以被单独使用:
上图还展示了ax寄存器可以被划分为AL和AH使用,这两个都是八位寄存器。支持最低划分为8位的寄存器有EAX、EBX、ECX、EDX:
32位 | 16位 | 高8位 | 低8位 |
EAX | AX | AH | AL |
EBX | BX | BH | BL |
ECX | CX | CH | CL |
EDX | DX | DH | DL |
剩下的通用寄存器没有八位模式:
32位 | 16位 |
ESI | SI |
EDI | DI |
EBP | BP |
ESP | SP |
特殊用法
一些通用寄存器有如下的特殊用法:
- EAX被自动用于乘法和除法指令,它通常被称为extended accumulator register
- ECX被CPU自动用作循环计数器
- ESP被用来在栈上寻址数据,它很少被用于其他的用途(例如算术运算等)。它通常被称为extended stack pointer register
- ESI和EDI通常被用作数据传输,它们有时被称为extended source index和extended destination index register
- EBP通常被高级程序语言用来引用栈上的函数参数以及局部变量。它通常被称为extended frame pointer register
段寄存器
在实地址模式下,十六位段寄存器指示了为程序预先分配的段地址。在保护模式下,段寄存器hold pointers to segment descriptor tables。
指令指针
指令指针(instruction pointer)简称为EIP,包含着处理器要执行的下一条指令的地址。有一些特定的机器指令控制EIP的值,使得程序进行特定的跳转。
标志寄存器
标志寄存器(简称为EFLAGS)由一些独立的二进制位组成,这些二进制位控制着CPU的运行并反应了CPU的运行结果
控制标志(control flags)控制着CPU的运行。例如,它们可以控制CPU在每个指令执行完后都暂停,当CPU发生算术溢出时进行干预,进入保护模式等。
状态标志(status flags)反映了CPU算术和逻辑运算的结果,存在以下几个标志状态位:
- 进位标志(CF,Carry Flag)主要用来反映运算是否产生进位或错位。如果运算结果的最高位产生了一个进位或错位,那么其值为1,否则其值为0(使用该标志位的情况有:多字(字节)数的加减运算,无符号数的大小比较运算,移位操作,字(字节)之间移位,专门改变CF值的指令等。)
- 奇偶标志(PF,Parity Flag)用于反映运算结果中“1”的个数的奇偶性。如果“1”的个数为偶数,则PF的值为1,否则其值为0
- 辅助进位标志(AF,Auxiliary Carry Flag)用来指示在字操作时,是否发生低字节向高字节进位或错位;在字节操作时,是否发生低4位向高4位进位或错位。如上两种情况发生都会将这个标志位置1
- 溢出标志(OF,Overflow Flag)用于反映有符号数加减运算所得结果是否溢出。如果运算结果超过当前运算数所能表示的范围,则称为溢出,OF的值被置为1,否则,OF的值被清为0
- 符号标志(SF,Sign Flag)用来反映运算结果的符号位,它与运算结果的最高位相同。在微机系统中,有符号数采用补码表示法,所以,SF也就反映运算结果的正负号。运算结果为正数时,SF的值为0,否则其值为1。
- 零标志(ZF,Zero Flag)用来反映运算结果是否为0。如果运算结果为0,则其值为1,否则其值为0。在判断运算结果是否为0时,可使用此标志位。
MMX寄存器
MMX技术在实现高级多媒体和通信应用程序时可提高英特尔处理器的性能。八个64位MMX寄存器支持称为SIMD(单指令多数据)的特殊指令。顾名思义,MMX指令对MMX寄存器中包含的数据值进行并行操作。虽然它们看起来是独立的寄存器,但MMX寄存器名实际上是浮点单元使用的相同寄存器的别名。
XMM寄存器
x86体系结构还包含8个128位寄存器,称为XMM寄存器。它们用于将单指令多数据扩展指令集数据流传输到指令集。
浮点单元(Floating-Point Unit,FPU)
浮点单元(FPU)执行高速浮点运算。有一段时间,这需要一个单独的协处理器芯片。从Intel486开始,FPU已集成到主处理器芯片中。FPU中有八个浮点数据寄存器,分别命名为ST(0)、ST(1)、ST(2)、ST(3)、ST(4)、ST(5)、ST(6)和ST(7)。其余的控制寄存器和指针寄存器如图2-5所示。
2.3 x86内存管理
x86处理器根据第上面描述的几种基本操作模式管理内存。保护模式是最健壮和强大的,但它限制了应用程序直接访问系统硬件。
在实地址模式下,我们只能寻址到1MB大小的内存(从00000到FFFFF)。处理器只能运行一个程序,但它可以随时干预这个正在运行的程序来处理来自外部的请求(称为interrupts)。应用程序可以访问任何内存地址,甚至包括直接和系统硬件有关的地址。MS-DOS系统就运行在实地址模式下,Windows95和98也可以进入实地址模式进行工作。
在保护模式下,处理器可以同时运行多个程序。系统为每个程序分配了共4GB大小的内存空间。且每个程序都会被分配到自己使用的内存空间,每个程序都不能访问其他程序的代码和数据。MS-Windows和Linux在保护模式下运行。
在virtual-8086模式下,计算机在保护模式下运行,并创建一个具有自己的1M字节地址空间的virtual-8086计算机,该地址空间模拟在实地址模式下运行的80x86计算机。例如,windows NT和2000在打开命令窗口时会创建一个virtual-8086机器。我们可以同时运行许多这样的窗口,并且每个窗口都受到保护,不受其他窗口的操作影响。在Windows NT、2000和XP下,某些直接引用计算机硬件的MS-DOS程序将不会在此模式下运行。
三、64位x86处理器
在本节中,我们将重点介绍所有使用x86-64指令集的64位处理器的基本体系结构细节。此组包含Intel 64和AMD64处理器系列。指令集是我们已经研究过的x86指令集的64位扩展。以下是一些基本功能:
- 它是向后兼容x86指令集的
- 地址的长度为64位,允许使用大小为字节的虚拟地址空间。在当前的芯片实现中,只使用最低的48位。
- 它可以使用64位的通用寄存器,允许指令使用64位长度的整数操作数
- 它使用比x86更多8个的通用寄存器
- 它使用48位的物理地址空间,允许支持寻址到最高256G的内存空间
但需要注意,在本机64位模式下运行时,这些处理器不支持16位实模式或虚拟8086模式。(有一种传统模式仍然支持16位编程,但在64位版本的Microsoft Windows中不可用。)
尽管x86-64实际上指代指令集,我们此时将它看作一种处理器。为了学习汇编语言,我们不必考虑支持x86-64的处理器之间的硬件实现差异。
第一个使用x86-64的英特尔处理器是Xeon,其次是许多其他处理器,包括Core i5和Core i7处理器。AMD使用x86-64的处理器有Opteron和Athlon 64。
另一种64位体系结构被称为IA-64,后来更名为Itanium。IA-64指令集与x86和x86-64完全不同。Itanium处理器通常用于高性能数据库和网络服务器。
3.1 64位操作模式
英特尔64位体系结构引入了一种新的模式IA-32e,技术上讲它包含两个子模式:兼容模式和64位模式。
兼容模式
在兼容模式下运行时,现有的16位和32位应用程序通常可以在不重新编译的情况下运行。但是,16位Windows(Win16)和DOS应用程序不会在64位Microsoft Windows中运行。与早期版本的Windows不同,64位Windows没有虚拟DOS机子系统来利用处理器切换到virtual-8086模式的能力。
64位模式
在64位模式下,处理器运行使用64位线性地址空间的应用程序。这是64位Microsoft Windows的本机模式。此模式启用64位指令操作数。
3.2 64位模式下的基本执行环境
在64位模式下,地址理论上可以达到64位,尽管处理器目前只支持48位地址。在寄存器方面,以下是与32位处理器最重要的区别:
- 十六个通用寄存器(在32位模式下,我们只有八个通用寄存器)
- 八个八十位浮点寄存器
- 一个64位状态寄存器,称为RFLAGS(只有低32位被使用)
- 一个64位指令寄存器,称为RIP
在32位模式下,状态寄存器称为EFLAGS,指令寄存器称为RIP。此外,在谈到x86处理器时,我们提到了一些用于多媒体处理的专用寄存器:
- 八个64位MMX寄存器
- 十六个128位XMM寄存器(在32位模式下,我们只有八个)
通用寄存器
在32位处理器中通用寄存器是执行算术、移动数据和循环数据的指令的基本操作数。通用寄存器可以访问8位、16位、32位或64位操作数(带有特殊前缀)。
在64位模式下,默认操作数大小为32位,有8个通用寄存器。但是,通过向每条指令添加REX(寄存器扩展)前缀,操作数可以是64位长,并且总共有16个通用寄存器可用。此时我们拥有与32位模式相同的寄存器,加上8个编号寄存器,从R8到R15。表2-1显示了启用REX前缀时哪些寄存器可用:
下面是几点需要注意的细节:
- 在64位模式下,单个指令不能同时访问高字节寄存器(如AH、BH、CH和DH), 和任何一个新字节寄存器(如DIL)的低字节。
- 在64位模式下,32位EFLAGS寄存器被64位RFLAGS寄存器替换。两个寄存器共享相同的低32位,rflag的高32位不使用。
- 32位和64位模式下的状态标志相同(status flag)