指令集、体系架构、微架构 [转]
转载自《操作系统真相还原》
指令集是什么?表面上看它是一套指令的集合。集合的意思显而易见,那咱们说说什么是指令。
在计算机中,CPU只能识别0、1这两个数,甚至它都不知道数是什么,它只知道要么“是”,要么“不是”,恰好用0、1来表示这两种状态而已。
人发明的东西逃不出人的思维,所以,先看看我们人类的语言是怎么回事。
不同的语言对同一种事物有不同的名字,这个名字其实就是代码。比如说人类的好朋友:狗,咱们在中文里称之为狗,但在英文中它被称为dog,虽然用了两种语言,但其描述的都是这种会汪汪叫、对人类无比忠诚的动物。人是怎样识别小狗的呢?识别信息来自听觉、视觉等,这是因为人天生具备处理声音和图像的能力,能够识别出各种不同的声音和颜色不同的图像。可是计算机只能处理0、1这两个数,所以让计算机识别某个事物,只有用01这两个数来定义。也就是说,要用0、1来为各种事物编码。
为了更好地说明指令集,咱们这里不再用现有的语言举例子,当然也不是要自创指令集。下面举个简单的例子来演示指令集的模型。
咱们拿表达式A=B+C 为例。假设A 、B 、C 都是内存变量的值,它们的地址分别是Ox3000 、Ox3004 、Ox3008 。在此用Ra 表示寄存器A, Rb 表示寄存器B, Re 表示寄存器c.
完成这个加法的步骤是先将B 和C 载入到Ra 和Rb 寄存器中,再将两个寄存器的值相加后送入寄存器Ra,之后再将寄存器Ra 的值写入到地址为Ox3000 的内存中。
步骤有了,咱们再设计完成这些步骤的指令。
步骤1 :将内存中的数据载入到寄存器,咱们假设它的指令名为load 。
步骤2:两个寄存器的加法指令,假设指令名为add。
步骤3 :将寄存器中的内容存储到内存,假设指令名为store。
以上指令名都是假设的,名字可以任意取,因为CPU 不识别指令名。指令名是编译器用来给人看的,为的是方便人来编程, CPU 它只认编码。目前CPU 中的指令,无论是哪种指令集,都由操作码和操作数两部分组成(有些指令即使指令格式中没有列出操作数,也会有隐含的操作数)。咱们也采用这种操作码+操作数的思路,分别为这两部分编码。
咱们先为操作码设计编码。
接下来为操作数编码,操作数一般是立即数、寄存器、内存等,咱们这里主要是为寄存器编码。
好啦,操作码和操作数都有了,其实指令集己经完成了。不过在一长串的二进制01 中,哪些是操作码,哪些是操作数呢?这就是指令格式的由来啦。我们人为规定个格式,规定操作码和操作数的大小及位置,然后在CPU 硬件电路中写死这些规则,让CPU 在硬件一级上识别这些格式,从而能识别出操作码和操作数。
假设我们的指令格式最大支持三个寄存器参数和一个立即数参数。其中操作码和各寄存器操作数各占1字节,立即数部分占4 字节。各条指令并不是完全按照此格式填充,不同的指令有不同的参数,只有操作码部分是固定的,其他操作数部分是可选的。当CPU 在译码阶段识别出操作码后,CPU自然知道该指令需要什么样的操作数,这是写死在硬件电路中的,所以不同的指令其机器码长度很可能不一致。
为了演示指令集模型,我们在上面假设了寄存器名、指令名、格式。按理说这对于指令集来说已经全了,不过,为方便咱们了解编译器,不如咱们再假设个指令的语法吧,咱们这里学习Intel 的语法格式:“指令目的操作数,源操作数飞目的操作数在左,源操作数在右,此赋值顺序比较直观。Intel 想表达的是a=b这种语序,如a=b ,便是mov a, b。
以上三个步骤的机器码按照十六进制表示为:
以上自定义的指令便是按照咱们假设的语法来生成的。对于机器码的大小,由于指令不同,需要的操作数也不同,所以机器码大小也不同。另外,机器码中的立即数是按照x86 架构的小端字节序写的,这一点大家要注意。小端字节序是数值中的低位在低地址,高位在高地址,数位以字节为单位。
步骤2 的机器码为01000110。操作码占1 字节, CPU 识别出第1 字节的二进制01 是add 指令,知道此指令的操作数是3 个寄存器,并且第1 个寄存器操作数是目的寄存器,另外两个寄存器是源操作数(这都是我们假定的,并且是写死在硬件中的规则,不同的指令有不同的规则,您也可以创造出内存和寄存器混合作为操作数的加法指令)。于是到第2 宇节去读取寄存器编码,发现其值为二进制00 ,就是寄存器Ra 对应的编码。接着到下一个字节处继续读出寄存器编码,发现是二进制01 ,也就是寄存器Rb, Re 同理。于是将寄存器Rb 和Re 的值相加后存入到寄存器b。
步骤3 中,机器码为10 00 0c300000, CPU 读取机器码的第1 字节发现其为二进制10 ,知道其为指令store,于是便确定了,目的操作数是个立即数形式的内存地址,源操作数是个寄存器。接着到指令格式中的寄存器操作数1 的位置去读取寄存器编码,发现其值为00 ,这就是寄存器Ra 的编码。机器码中剩下的部分便作为立即数,这样便将寄存器Ra 的值写入到内存0x0000300c 中了。
以上指令集的模型,确实太过于简单了,也许称之为模型都非常勉强。现实中的指令格式要远远复杂得多。下面我们看看目前世面上的指令集有哪些。
最早的指令集是CISC (Complex Instruction Set Computer ),意为复杂指令集计算机。从名字上看,这套指令集相当复杂,当初这套指令集问世的时候,它的研发者们都没想过要给它起名,只是因为后来出现了相对精简高效的指令集,所以人们为了加以区分,才将最初的这套相对复杂的指令集命名为CISC ,而后来精简高效的指令集称为RISC (Reduced Instruction Set Computer )。
CISC 和RISC 并不是具体的指令集,而是两种不同的指令体系,相当于指令集中的门派,是指令的设计思想。举个例子,就像中医与西医,中医讲究从整体上调理身体,西医则更多的是偏向局部。这就是两种不同的医疗思路,类似于CISC 和阳SC 这两种指令体系。那什么是指令集呢?拿中医举例,像华伦、张仲景这两位医圣,他们虽然都是基于中医的思想治病,但医术各有特色,水平也不尽相同,这就相当于不同的指令集。一会儿咱们会介绍具体的指令集。
为什么说CISC 复杂呢?
首先,因为它是最早的指令集,当初都是摸着石头过河,肯定有一些瑕疵在里面。其次,当初的程序员都是用汇编语言开发程序,他们当然希望汇编语言强大啦,尽量多一些指令,尽量一个指令能多干几件事,所以指令集中的指令越来越多,越来越复杂。不过这样的好处是程序员同学很爽。最后,CISC是Intel使用的指令集,Intel公司在兼容性方面做得最好,指令集在发展的过程中,还要兼容过去有瑕疵的古董,以至于最后的指令集变得有点“奇形怪状”了。
作为后起之秀的RISC ,借鉴了前辈CISC 的经验,取其精华,弃其糟柏,当然要更好更轻量啦。它是怎么来的呢?
CISC 不是做得很全很强吗,可是很多时候,程序员并不会用到那些复杂的指令和寻址方式,即使用到了,编译器有时候为了优化,未必“全”将其编译为复杂的形式。这就导致了CPU 中的复杂的指令和寻址方式无用武之地。根据二八定律,指令集中20%的简单指令占了程序的80% ,而指令集中80% 的复杂指令占了程序的20% 。根据这个特性,处理器及指令集被重新设计,保留了那些基本常用的指令,减少了硬件电路的复杂性。这样,大部分指令部能在一个时钟周期内完成,更有利于提升流水线的效率。而且,指令采用了定长编码,这样译码工作更容易了。由于其太优秀了,后来的处理器,如MIPS, ARM, Power都采用RISC 指令体系,做得最好的就是MIPS 处理器,它严格遵守RISC 思想,业界公认其优雅。
我们常用的CPU 是Intel 和AMD 公司的产品,它们用的指令集便是基于CISC 思想的x86.。 AMD 的x86指令架构是Intel 授权给他们的,为区别于此, Intel 在官方手册上称自己的指令集为IA32。
虽然AMD 采用的也是x86 指令集,但Intel 可没把硬件实现方法也告诉AMD ,否则AMD 的CPU 和Intel 的CPU 不就完全一样了吗,人家Intel 也不肯呢。指令集是一套约定,里面规定的是有哪些指令、指令的二进制编码、指令格式等,如何实现这套约定,这是硬件自己的事。打个比方,这就像和朋友约好了在某餐厅吃饭,咱是坐车去,还是走着去,这是咱们的事,与吃饭是无关的。说白了,在Intel 的CPU 上运行的软件也能够在AMD 的CPU 上运行,原因就是它们共用了同用一套指令集,也就是对二进制编码达成了共识。它们面对相同的需求,可能采取了不同的行动,但都完成了任务。比如机器码是b80000, Intel的CPU 经过译码,知道这是将0 赋值给寄存器ax,相当于汇编语言mov ax, 0 。AMD 的CPU 在译码时,也得将此机器码认为是将0 赋值给寄存器ax。至于它们在物理上是怎么将0 传入寄存器ax 中的,这是它们各自实现的方式,与指令集无关。它们各自实现的方式,就叫微架构。
总结一下,指令集是具体的一套指令编码,微架构是指令集的物理实现方式。
发展到后来, x86 指令集越来越复杂。它本属于CISC 体系,但由于效率低下,最终在其内部实现上采取了RISC 内核,即一条CISC 指令在译码时,分解成多条RISC 指令,这样其执行效率便可与RISC 媲美啦。
目前市面上常见的指令集有五种,除x86 是CISC 指令体系外, ARM、MIPS 、Power、C6000 都是RISC 指令体系的指令集。
CPU 与指令集是对应的,一种CPU 只能识别一种指令集,所以很多CPU 都以其支持的指令集来称呼。比如ARM 、MIPS,它们本身是CPU名称,又是指令集名称。
ARM 主要用在手机中,作为手机的处理器。Power 是IBM 用于服务器上的处理器。C6000 是数字信号处理器,广泛用于视频处理。而MIPS 虽然本身很优秀,但其在各领域起步都较晚,并没有广泛应用的领域。
由于MIPS 本身的优越性,龙芯用的就是mips 指令集,有没有人问,为什么咱们自主研发的CPU 还要用人家国外的指令集?就不能也研发出一套指令集吗?能倒是能,不过语言不通用。就像我自己可以发明一门语言,语言本身没什么问题,问题是我用自己发明的语言和别人交流,谁昕得懂呢,谁又愿意去学这门语言呢?大家都很忙,不通用的东西没人愿意花精力去学。如果龙芯也自立门户创造新的指令集,那有谁愿意给它写编译器呢?即使有了编译器,操作系统也要重新编译发布,应用程序也要重新编译发布,指令集背后不仅是个计算机生态链,更重要的是全球经济链。
平时所说的编程语言,虽然其上层表现各异,归根结底是要在具体的CPU 上运行的,所以必须由编译器按照该CPU 的指夺集,翻译成符合该CPU 的指令。说到这,不得不说一下交叉编译,本质上交叉编译就是用在A平台上运行的编译器,编译出符合B 平台CPU 指令集的程序,编译出的程序直接能在B平台上运行啦。这里的平台指的就是CPU 指令体系结构。