《操作系统真象还原》——0.25 指令集、体系结构、微架构、编程语言

简介: 假设我们的指令格式最大支持三个寄存器参数和一个立即数参数。其中操作码和各寄存器操作数各占1字节,立即数部分占4字节。各条指令并不是完全按照此格式填充,不同的指令有不同的参数,只有操作码部分是固定的,其他操作数部分是可选的。

本节书摘来自异步社区《操作系统真象还原》一书中的第0章,第0.25节,作者:郑钢著,更多章节内容可以访问云栖社区“异步社区”公众号查看

0.25 指令集、体系结构、微架构、编程语言

指令集是什么?表面上看它是一套指令的集合。集合的意思显而易见,那咱们说说什么是指令。

在计算机中,CPU只能识别0、1这两个数,甚至它都不知道数是什么,它只知道要么“是”,要么“不是”,恰好用0、1来表示这两种状态而已。

人发明的东西逃不出人的思维,所以,先看看我们人类的语言是怎么回事。

不同的语言对同一种事物有不同的名字,这个名字其实就是代码。比如说人类的好朋友:狗,咱们在中文里称之为狗,但在英文中它被称为dog,虽然用了两种语言,但其描述的都是这种会汪汪叫、对人类无比忠诚的动物。人是怎样识别小狗的呢?识别信息来自听觉、视觉等,这是因为人天生具备处理声音和图像的能力,能够识别出各种不同的声音和颜色不同的图像。可是计算机只能处理0、1这两个数,所以让计算机识别某个事物,只有用01这两个数来定义。也就是说,要用0、1来为各种事物编码。

为了更好地说明指令集,咱们这里不再用现有的语言举例子,当然也不是要自创指令集。下面举个简单的例子来演示指令集的模型。

咱们拿表达式A=B+C为例。假设A、B、C都是内存变量的值,它们的地址分别是0x3000、0x3004、0x3008。在此用Ra表示寄存器A,Rb表示寄存器B,Rc表示寄存器C。

完成这个加法的步骤是先将B和C载入到Ra和Rb寄存器中,再将两个寄存器的值相加后送入寄存器Ra,之后再将寄存器Ra的值写入到地址为0x3000的内存中。

步骤有了,咱们再设计完成这些步骤的指令。

步骤1:将内存中的数据载入到寄存器,咱们假设它的指令名为load。

screenshot

步骤2:两个寄存器的加法指令,假设指令名为add。

步骤3:将寄存器中的内容存储到内存,假设指令名为store。

以上指令名都是假设的,名字可以任意取,因为CPU不识别指令名。指令名是编译器用来给人看的,为的是方便人来编程,CPU它只认编码。目前CPU中的指令,无论是哪种指令集,都由操作码和操作数两部分组成(有些指令即使指令格式中没有列出操作数,也会有隐含的操作数)。咱们也采用这种操作码+操作数的思路,分别为这两部分编码。

咱们先为操作码设计编码。

screenshot

接下来为操作数编码,操作数一般是立即数、寄存器、内存等,咱们这里主要是为寄存器编码。
screenshot

好啦,操作码和操作数都有了,其实指令集已经完成了。不过在一长串的二进制01中,哪些是操作码,哪些是操作数呢?这就是指令格式的由来啦。我们人为规定个格式,规定操作码和操作数的大小及位置,然后在CPU硬件电路中写死这些规则,让CPU在硬件一级上识别这些格式,从而能识别出操作码和操作数。

假设我们的指令格式最大支持三个寄存器参数和一个立即数参数。其中操作码和各寄存器操作数各占1字节,立即数部分占4字节。各条指令并不是完全按照此格式填充,不同的指令有不同的参数,只有操作码部分是固定的,其他操作数部分是可选的。当CPU在译码阶段识别出操作码后,CPU自然知道该指令需要什么样的操作数,这是写死在硬件电路中的,所以不同的指令其机器码长度很可能不一致。

为了演示指令集模型,我们在上面假设了寄存器名、指令名、格式。按理说这对于指令集来说已经全了,不过,为方便咱们了解编译器,不如咱们再假设个指令的语法吧,咱们这里学习Intel的语法格式:“指令目的操作数,源操作数”。目的操作数在左,源操作数在右,此赋值顺序比较直观。Intel想表达的是 a=b这种语序,如a=b,便是mov a,b。

以上三个步骤的机器码按照十六进制表示为:
screenshot

以上自定义的指令便是按照咱们假设的语法来生成的。对于机器码的大小,由于指令不同,需要的操作数也不同,所以机器码大小也不同。另外,机器码中的立即数是按照x86架构的小端字节序写的,这一点大家要注意。小端字节序是数值中的低位在低地址,高位在高地址,数位以字节为单位。前面有一小节说明大小端字节序问题。

步骤2的机器码为01 00 01 10。操作码占1字节,CPU识别出第1字节的二进制01是add指令,知道此指令的操作数是3个寄存器,并且第1个寄存器操作数是目的寄存器,另外两个寄存器是源操作数(这都是我们假定的,并且是写死在硬件中的规则,不同的指令有不同的规则,您也可以创造出内存和寄存器混合作为操作数的加法指令)。于是到第2字节去读取寄存器编码,发现其值为二进制00,就是寄存器Ra对应的编码。接着到下一个字节处继续读出寄存器编码,发现是二进制01,也就是寄存器Rb,Rc同理。于是将寄存器Rb和Rc的值相加后存入到寄存器Ra。

步骤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和RISC这两种指令体系。那什么是指令集呢?拿中医举例,像华佗、张仲景这两位医圣,他们虽然都是基于中医的思想治病,但医术各有特色,水平也不尽相同,这就相当于不同的指令集。一会儿咱们会介绍具体的指令集。

为什么说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指令体系结构。

相关文章
|
12天前
|
Java 调度 开发者
构建高效微服务架构:后端开发的新趋势深入理解操作系统之进程调度策略
【4月更文挑战第30天】 随着企业数字化转型的不断深入,传统的单体应用逐渐不能满足快速迭代和灵活部署的需求。微服务架构以其高度模块化、独立部署和易于扩展的特性,成为现代后端开发的重要趋势。本文将探讨如何构建一个高效的微服务架构,包括关键的设计原则、技术选型以及可能面临的挑战。
|
12天前
|
运维 Cloud Native 持续交付
构建未来:以云原生为基石的分布式系统架构深入理解操作系统的内存管理机制
【4月更文挑战第30天】 随着企业数字化转型的不断深入,传统的IT架构已难以满足市场对于敏捷性、可扩展性和成本效益的需求。云原生技术作为推动这一变革的关键因素,其设计理念和实现方式正在重塑软件开发和运维模式。本文将探讨云原生架构的核心组件,包括容器化、微服务、持续集成/持续部署(CI/CD)、以及无服务器计算等,并分析其在构建分布式系统中的作用与挑战。通过实际案例,我们将展示如何利用云原生技术构建高效、弹性和可维护的分布式系统。
|
14天前
|
运维 负载均衡 监控
软件体系结构 - 关系数据库(3)主从架构
【4月更文挑战第26天】软件体系结构 - 关系数据库(3)主从架构
24 0
|
19天前
|
消息中间件 Kubernetes 供应链
软件体系结构 - 架构风格(14)SOA架构风格
【4月更文挑战第21天】软件体系结构 - 架构风格(14)SOA架构风格
24 0
|
19天前
|
存储 前端开发 Java
软件体系结构 - 架构风格(13)MVC架构风格
【4月更文挑战第21天】软件体系结构 - 架构风格(13)MVC架构风格
27 0
|
19天前
|
存储 XML vr&ar
软件体系结构 - 架构风格(12)超文本系统架构风格
【4月更文挑战第21天】软件体系结构 - 架构风格(12)超文本系统架构风格
23 0
|
19天前
|
存储 算法 数据挖掘
软件体系结构 - 架构风格(11)黑板架构架构风格
【4月更文挑战第21天】软件体系结构 - 架构风格(11)黑板架构架构风格
22 0
|
19天前
|
存储 SQL 数据库
软件体系结构 - 架构风格(10)数据库系统架构风格
【4月更文挑战第21天】软件体系结构 - 架构风格(10)数据库系统架构风格
31 0
|
19天前
|
XML 存储 JSON
软件体系结构 - 架构风格(9)基于规则的系统架构风格
【4月更文挑战第21天】软件体系结构 - 架构风格(9)基于规则的系统架构风格
34 0
|
19天前
|
SQL 设计模式 算法
软件体系结构 - 架构风格(8)解释器架构风格
【4月更文挑战第21天】软件体系结构 - 架构风格(8)解释器架构风格
39 0