3.2 x86体系结构

计算机组成

3 指令系统体系结构

3.2 x86体系结构

X86是商业上最为成功,影响力最大的一种体系结构。但从技术的角度看,它又存在着很多的问题,那我们就来一起分析X86这种体系结构的特点。

要探讨x86体系结构,我们就得从8086开始说起。8086是英特尔在1978年推出的一款16位的微处理器。

它内部的通用寄存器为16位,对外有16根数据线和20根地址线,因此可以访问的存储单元数量为2的20次方,也就是1MByte。而cpu发到存储器的地址,我们称为物理地址,8086的物理地址采用了段加偏移的方式,关于这一点我们在后面进行解释。8086作为微处理器,在一个芯片上集成了原先由多个部件组成的CPU的功能,因此可以以低廉的价格,很小的体积和功耗,提供相对不错的性能,这也为个人计算机的发展奠定了基础。

1981年IBM就推出了自己的个人计算机pc5150。它采用了8086的简化版本8088,这是一款八位宽的微处理器,此后个人计算机的市场迅速增长,反过来也推动了x86微处理器的发展。

1982年英特尔推出了80286,由于应用程序对内存空间的需求越来越大,286将地址总线扩展到了24位,可以寻址16兆的内存空间,而且更多的任务也要求更高效的管理模式,286引入了保护模式,虽然这个机制还不完善,但是一个非常重要的进步。为了兼容8086,286仍然保留了8086的工作模式,这就被称实模式,而兼容为之前处理器开发的软件成为x86能够成功的关键因素之一。

实模式又称为实地址模式。运行在实模式下的x86处理器就像是一个更快的8086,而且现在所有x86处理器在加电或复位后首先进入实模式。在实模式中会运行系统初始化程序,为后面进入保护模式做好准备。现在的个人计算机在启动之后,cpu首先会通过主板上的芯片组,从BIOS芯片中取出指令进行执行,这段程序就是运行在实模式下的。

那即使286将地址总线扩大到了24位,很快也无法满足应用程序的需求,所以英特尔在1985年将x86体系结构升级到32位,并将之命名为IA32,IA是Intel Architecture的缩写。

386是x86系列中的第一款32位微处理器,它的内部真正支持了32位的算术和逻辑运算,并提供了32位的通用寄存器,地址总线也扩展到了32位,这样就可以寻址到\(2^{32}\),也就是 4GByte 的内存空间。386还在286的基础上改进了保护模式,此外还增加了虚拟8086模式,以运行兼容8086的程序。

在386之后,x86的微处理器的主要工作模式就是保护模式。保护模式为多任务的执行提供了很好的支持和保护,同时可以访问4G字节的物理存储空间,这相比之前是一个很大的进步,可以在相当长的时间内满足软件的需求。而且从386开始还引入了虚存的概念,可以对存储空间进行更好的管理。简单说来保护模式,可以让操作系统加强对应用软件的控制,使得系统运行更加安全高效。

现在在系统启动后,会首先进入实模式,在初始化完成后,通过设置cpu中的控制寄存器,进入保护模式,操作系统和应用程序都运行在保护模式下。如果需要运行兼容8086的程序,可以从保护模式下切换到虚拟8086模式。在这种情况下,如果发生中断或异常,是会回到保护模式处理,处理完成后,再返回虚拟8086模式。不管在哪种模式下,经过复位以后都会重新从实模式开始启动。

32位的x86,也就是IA32体系结构,维持了很长时间,并逐渐占据了市场的主导地位,但是随着应用对内存空间的需求越来越大,32位的体系结构已经难以满足要求。在x86向64位升级的过程中,x86-64的体系结构是最先由AMD提出来的。而英特尔当时正专注于另一种64位的体系结构,称为IA64,但它与IA32是不兼容的。

这就是第一款64位的x86微处理器,AMD的皓龙。它可以访问高于4G字节的存储器,从而解决内存空间受限的问题,其实x86扩展到64位实在说不上是一个亮点。因为在当时已经有很多体系结构都已经升级到了64位,但是由于x86一直坚持着向前兼容的设计原则,聚集了大量的应用程序,形成了很好的生态环境,因此AMD的x86-64的更重要的一点是,它能够兼容32位的x86程序,而且运行这些程序不会降低性能。与此对应的是英特尔提出的IA64体系结构,这是基于这个结构的安腾处理器,它与IA32体系结构并不兼容,只是提供了一种模式,可以运行32位的x86程序,但是会大大降低性能,这就成为了安腾不受欢迎的重要原因之一。中间曲线图是英特尔对安腾销售的预期,这条蓝色的线是97年的预测,第二条线是98年的预测,第三条线是99年的预测,实际的销售情况是这条橙色的线所表示的,所以最后英特尔也回过头来才用AMD的扩展方案。

这就是x86-64,x86-64支持原有的32位的x86的运行模式,并将之命名为传统模式。新增的模式称为长模式。长模式又分为两个子模式,64位模式和兼容模式。在兼容模式下,原有的x86程序不需要重新编译,就可以高效的运行,这也是x86-64得以成功的关键。

x86体系结构从16位演变到64位,其中有很多的变化,我们来看两个重要的方面。第一是寄存器模型的变化。

这是8086所使用的寄存器。我们分别来看它各自的功能。

在通用计算机中,前四个又称为数据寄存器,它们都是16位寄存器,但也可以当做两个八位寄存器使用。大多数算术运算和逻辑运算都会使用到这些数据寄存器,用于临时保存数据,而且它们除了作为通用的用途之外,还有一些专门的用途。

接下来的四个寄存器,又被称为指针寄存器和变址寄存器。SP和BP这两个寄存器用于堆栈操作,SI和DI这两个寄存器用于串操作。这是它们名称的含义,之后在介绍相关的指令时,会讲解它们的具体用途。当然这些寄存器也可以当做普通的数据寄存器使用。

我们再来看标志寄存器,标志寄存器当中包含的若干标志位,标志位分为两大类。

状态标志用于反映cpu的工作状态。比如说执行完加法运算后,如果产生进位,就会将标志寄存器当中体现进位的状态位置为有效。

而控制标志则是对cpu的运行起特定的控制作用。例如,如果让cpu采用单步的方式,而不是连续执行指令的方式运行,则需将标志寄存器当中的某个控制标志置为有效。

这是8086的标志位,实际只使用了其中九个比特。红色这些是状态标志,例如比特0就是进位标志。另外三个紫色是控制标志。这些标志位我们也会结合着指令进行讲解。

从8086到IA32,寄存器模型也由16位扩展到了32位。通用寄存器都在原有的基础上增加了高16位,扩展成了32位。例如,原来的AX扩展成32位以后,新的名称为EAX。程序中使用EAX寄存器就是一个32位寄存器,但仍然可以使用AX访问16位,也可以使用AH或者AL访问其中的特定的八位。再来看指令指针寄存器也从16位扩展到32位,标志寄存器也是同样。但是段寄存器的长度没有扩展,而是增加了两个16位的段寄存器。

我们再来看x86-64对寄存器模型的扩展。x86-64将大多数寄存器扩展成了64位,例如EAX扩展了高32位以后成为了RAX,和之前一样,不但可以使用64位的RAX寄存器,同时也可以使用EAX、AX等等。需要注意的是,段寄存器仍然没有扩展。此外,x86-64还增加了八个64位的通用寄存器,命名为R8到R15。

然后我们来看x86体系结构演变过程中的另一个要点,就是存储器的寻址。

8086依靠指令指针寄存器,也就是IP寄存器进行寻址。IP寄存器中保存了一个内存地址,指向当前需要取出的指令。当cpu从内存中取出一个指令后,IP寄存器会自动增加指向下一个指令的地址。程序员不能直接操作IP寄存器,但是可以通过编写转移等指令来改变IP寄存器的内容。我们知道8086是16位的,我们使用IP寄存器可以访问到的内存空间就是2的16次方,也就是64k个字节单元。但其实8086对外有20位的地址线,寻址范围是1兆的字节单元。那如何解决这个矛盾呢?

8086中提供了段寄存器来解决这个问题。8086一共提供了四个段寄存器:CS是代码段寄存器,DS是数据段寄存器,ES是附加段,SS是堆栈段。

现在我们就来看8086的物理地址是如何生成的?我们在编写程序时直接给出的地址称为偏移地址,又叫偏移量,这是16位的。而段寄存器中存放了段基址,这一对地址就被称为逻辑地址。其中段基址在计算时首先被左移四位,然后和偏移量相加,这样就得到了一个20位的物理地址,这也就是从逻辑地址生成物理地址的过程。二进制的左移四位,实际相当于十进制的乘以16,所以物理地址相当于段基址乘以16,再加上偏移量。这就是8086段加偏移的物理地址生成方式。

用这样的方式,8086对存储器进行分段式的管理,每个段最大为2的16次方,也就是64k个字节,他们在编程时使用逻辑地址,这样的好处在于,如果我们将程序在存储器当中变换位置,那程序中原来编写的偏移地址都不需要改动,只需要修改段寄存器的内容就可以了。而且还要说明,不同的逻辑段实际上是可以有重叠的。这也为高效的利用存储器的空间提供了便利。

我们来看一个实例。如果我们写一条指令 MOV AX, [3000H],但实际上在段加偏移的模式下,操作数是默认存放在DS也就是数据段寄存器所指向的段中,让我们假设事先已经在DS段寄存器当中存入了2000H。那么实际的物理地址应该采用段寄存器乘以16,加上偏移量的方式得到最终我们所用的物理地址是23000H。

我们来看左边这个存储器的片段,假设代码段在这里,其中有一条指令就是我们现在所用的MOV指令。第一个字节是操作码,后两个字节就是指令中指定的偏移地址。那cpu在取回这条指令后,发现要从存储器中取得一个数据,因此cpu会取出DS寄存器的内容,并将其左移四位后,与指令编码中的偏移量相加,从而得到了23000这个地址,然后用这个地址去访问存储器。因为我们的目标寄存器AX是16位的,所以从存储器中取出两个字节,并依照着高地址放在高字节,低地址放在低字节的原则,存放到AX当中。这就完成了这条指令的执行。这样段加偏移的方式实际上是非常复杂的,这是在内部的运算器和通用寄存器只有16位的情况下,又想访问超过16位的地址空间所想出的一个折中的办法,算是在简陋的条件下用了一个巧妙的方法,从而提供了较好的性能。但是如果不仅限于微处理器,计算机中是早就采用了32位的地址空间的。

比如60年代的大型计算机 IBM S/360,在其比较早的型号中就使用了32位的通用寄存器,cpu对外发出32位的地址。

我们再来看IA32体系结构的存储器寻址。我们以指令的寻址为例,原先16位的IP寄存器已经扩展为了32位的EIP寄存器。当然在实模式下面还是使用 CS:IP 这样的逻辑地址。而在保护模式下,因为ERP寄存器的寻址能力已经达到了 \(2^{32}\),也就是4G个字节单元,这和CPU对外的地址总线的宽度是一致的,也就是说EIP寄存器完全有能力访问到外部存储器的每一个字节单元,但在保护模式下,IA32仍然采用了与之前形式上一致的寻址模式 CS:EIP 这样的逻辑地址的组合,但是在这种模式下,段寄存器 CS 的含义已经发生了变化。

实际在保护模式下,段基值是放在内存中的。这是一个存储器的片段,这些数据以八个字节为一个单位,我们称为一个描述符。我们来看其中一个描述符,字节2、3、4、7,一共四个字节,保存了一个基地址。而cpu中,CS段寄存器不再保存段基址,而是指向这个描述符的地址,但是我刚才说过CS只有16位,它无法在4G空间中寻址,所以CPU当中还增加了一个寄存器,叫 GDTR。而 GDTR 中则保存了这个全局描述符表的起始地址,CS 段寄存器中则保存了相对于这个起始地址的偏移量,所以我们可以这么理解,cpu在取指令时,先将 GDTR 中保存的地址取出,并于CS寄存器当中的内容相加,得到一个内存地址,用这个地址去访问存储器,获得了这个描述符对应的八个字节,再将其中的基地址部分提取出来,与EIP寄存器中保存的偏移地址相加,就得到了真正要访问的地址。当然在这个描述中还描述了这个段有多长,也就是段界限;以及对这个段的内容是否能读写,也就是权限;还有一些其它的信息。CPU通过这些信息可以由硬件来判断,当前要执行的这条指令是否符合这个段的要求,从而起到了保护的作用。我们可以看到分段的方式变得越来越复杂,虽然它提供了很好的保护机制,但是代价也是不小的。而且在分段的基础上,还可以采用分页等机制,进行更细粒度的管理,所以在有些系统上就对分段进行了简化的处理,让所有的程序都运行在一个段上,也就是不改变段寄存器的内容,而在x86-64上得到了进一步的简化。

图中一个x86-64当中对应的描述符,我们可以看到,很多的字节都被固定设置为零。也就是说,这时候已经没有了段基址和段界限,所有的代码段都是从地址0开始的。在描述符中只设置了这个段对应的权限和若干的控制位。

这些就是x86体系结构演变过程中的一些要点。现在我们已经了解了x86体系结构的基本特点,之后,我们将通过分析x86的具体指令,来进一步学习这种体系结构。

原文地址:https://www.cnblogs.com/narisu/p/9097982.html

时间: 2024-10-06 00:22:30

3.2 x86体系结构的相关文章

基于x86体系结构openwrt上的libmysqlclient交叉编译

1.建立交叉编译环境 首先,在自己的机子上建立交叉编译环境. 我使用的是Ubuntu14.04 x86_64,gcc-4.8.3.如果要建立一套十分完整的交叉编译环境,需要安装openwrt buildroot环境,操作步骤: http://wiki.openwrt.org/doc/howto/buildroot.exigence, 注意在其中make menuconfig之后出现配置界面,主要选定目标系统的体系架构,比如我的是x86,其他如ar71xx等,另外选中编译出toolchain,还有

X86/X64处理器体系结构及寻址模式

由8086/8088.x86.Pentium发展到core系列短短40多年间,处理器的时钟频率几乎已接近极限,尽管如此,自从86年Intel推出386至今除了增加一些有关流媒体的指令如mmx/sse之外,其他新增的大多数指令都可以从最初的指令集中组合实现同样的功能,整个编程模型维持了约有20多年. 1. 处理器体系结构 1.1. 处理器简要结构 我们都知道CPU的根本任务就是执行指令,对计算机来说最终都是一串由"0"和"1"组成的序列.CPU从逻辑上可以划分成3个模

x86函数调用约定

以下摘自<IDA Pro>,貌似有一些细节之处没有交代清楚呢,需要进一步思考.实践. 了解栈帧的基本概念后,接下来详细介绍它们的结构.下面的例子涉及x86体系结构和与常见的x86编译器(如Microsoft Visual C/C++或GNU的gcc/g++)有关的行为.创建栈帧的最重要的步骤是,通过调用函数将函数存入栈中.调用函数必须存储被调用函数所需要的参数,否则可能导致严重的问题.各个函数会选择并遵照某一特定的调用约定,以表明他们希望以何种方式接收参数. 调用约定指定调用方放置函数所需参数

284.软件体系结构集成开发环境的作用

软件体系结构集成开发环境基于体系结构形式化描述从系统框架的角度关注软件开发.体系结构开发工具是体系结构研究和分析的工具,给软件系统提供了形式化和可视化的描述.它不但提供了图形用户界面.文本编辑器.图形编辑器等可视化工具,还集成了编译器.解析器.校验器.仿真器等工具:不但可以针对每个系统元素,还支持从较高的构件层次分析和设计系统,这样可以有效地支持构件重用.具体来说,软件体系结构集成开发环境的功能可以分为以下5类. 1.辅助体系结构建模 建立体系结构模型是体系结构集成开发环境最重要的功能之一.集成

常见C++面试题(三)

strcpy和memcpy有什么区别?strcpy是如何设计的,memcpy呢? strcpy提供了字符串的复制.即strcpy只用于字符串复制,并且它不仅复制字符串内容之外,还会复制字符串的结束符.(保证dest可以容纳src.) memcpy提供了一般内存的复制.即memcpy对于需要复制的内容没有限制,因此用途更广. strcpy的原型是:char* strcpy(char* dest, const char* src); char * strcpy(char * dest, const

深入解析Linux内核及其相关架构的依赖关系

Linux kernel 成功的两个原因: 灵活的架构设计使得大量的志愿开发者能够很容易加入到开发过程中:每个子系统(尤其是那些需要改进的)都具备良好的可扩展性.正是这两个原因使得Linux kernel可以不断进化和改进. 一.Linux内核在整个计算机系统中的位置 分层结构的原则: the dependencies between subsystems are from the top down: layers pictured near the top depend on lower la

(转)Linux概念架构的理解

英文原文:Conceptual Architecture of the Linux Kernel 摘要 Linux kernel成功的两个原因:(1)架构设计支持大量的志愿开发者加入到开发过程中:(2)每个子系统,尤其是那些需要改进的,都支持很好的扩展性.正是这两个原因使得Linux kernel可以不断进化. 一.Linux内核在整个计算机系统中的位置 Fig 1 - 计算机系统分层结构 分层结构的原则:the dependencies between subsystems are from

Linux 内核中断内幕【转】

转自:http://www.ibm.com/developerworks/cn/linux/l-cn-linuxkernelint/ 本文对中断系统进行了全面的分析与探讨,主要包括中断控制器.中断分类.中断亲和力.中断线程化与 SMP 中的中断迁徙等.首先对中断工作原理进行了简要分析,接着详细探讨了中断亲和力的实现原理,最后对中断线程化与非线程化中断之间的实现机理进行了对比分析. 3 评论: 苏 春艳, 在读研究生 杨 小华 ([email protected]), 在读研究生 2007 年 5

SDN与NFV---简单的理解

SDN从2012年开始,在学术界受到了广泛的关注.在阅读了部分国外大牛写的相关综述性文章若干之后,发现其中似乎并没有看到NFV的影子. 提到SDN,能想到的基本上绕不过“控制转发分离.可编程接口.集中控制”,这三个特点.固然这三个特定很重要,也是SDN存在的价值.但除此之外,伴随着SDN一起成长的还有NFV,即网络功能虚拟化. 1. SDN出身于斯坦福实验室,算是学术界吧.而NFV出身于工业界. 2. SDN和NFV是可以相互独立存在的,据相关研究表明,二者结合起来的效果更优,但是需要处理的问题