转载

深入iOS系统底层之指令集介绍

说到指令集以及CPU架构体系,大家就会想到计算机专业课程里面的计算机体系结构的方面的内容。既然课程中已经有了的内容我就不想那么枯燥的去复述一遍,而是先看一个类的定义:

//定义寄存器编号
typedef enum : int {
    Reg0,
    Reg1,
    Reg2,
    Reg3
} RegNum;
//定义系统调用编号
typedef enum : int {
    Int3         //设备输出,将寄存器Reg0中的内容输出到屏幕
} Interrupt;
//定义指令索引
typedef int Instruct;
/**
 虚拟CPU类,模拟CPU所提供的指令。
 虚拟CPU由4个寄存器和运算部件组成。四个寄存器的编号分别定义在RegNum中;运算部件提供了赋值、加减、比较、跳转9个指令。
 */
@interface VCPU : NSObject
//将一个常量值赋值给编号为reg的寄存器中。
-(void)moveFromConst:(int)val toReg:(RegNum)reg;
//将编号为reg1的寄存器中的值赋值给编号为reg2的寄存器中。
-(void)moveFromReg:(RegNum)reg1 toReg:(RegNum)reg2;
//将编号为reg的寄存器中的值赋值给地址为addr的内存中。
-(void)moveFromReg:(RegNum)reg toAddr:(Addr)addr;
//将地址为addr的内存中的值赋值给编号为reg的寄存器中。
-(void)moveFromAddr:(Addr)addr toReg:(RegNum)reg;
//将编号为reg1的寄存器中的值加上编号为reg2的寄存器中的值并将结果保存到编号为reg2的寄存器中。
-(void)addFromReg:(RegNum)reg1 toReg:(RegNum)reg2;
//将编号为reg1的寄存器中的值减去编号为reg2的寄存器中的值并将结果保存到编号为reg2的寄存器中。
-(void)subFromReg:(RegNum)reg1 toReg:(RegNum)reg2;
//如果两个寄存器内容相等则执行instruct所指定的指令,否则什么也不做。
-(void)isEqualReg:(RegNum)reg1 withReg:(RegNum)reg2 thenGoto:(Instruct)instruct;
//跳转到instruct所指定的指令中去。
-(void)jumpTo:(Instruct)instruct;
//系统返回
-(void)ret;
//系统调用,目前只支持屏幕输出调用Int3,表示将寄存器编号为0中的值输出到屏幕。
-(void)sys:(Interrupt)interrupt;
@end

上面是一个叫VCPU的类的定义部分,它是一个用OC语言实现的用来模拟CPU功能的类。我们再来看一个使用这个类的代码片段:

-(void)main:(VCPU*)cpu memory:(VMemory*)memory
{
    VINSTRUCT_BEGIN
    
    VINSTRUCT(0, [cpu moveFromConst:10 toReg:Reg0])             //将常数10保存到CPU的寄存器Reg0中
    VINSTRUCT(1, [cpu moveFromConst:15 toReg:Reg1])             //将常数15保存到CPU的寄存器Reg1中
    VINSTRUCT(2, [cpu addFromReg:Reg0 toReg:Reg1])              //将寄存器Reg0中的值于寄存器Reg1中的值相加并保存到Reg1中
    VINSTRUCT(3, [cpu moveFromReg:Reg1 toAddr:0x1000])           //将保存在Reg1中的相加结果保存到内存地址为0x1000处的内存中
    VINSTRUCT(4, [cpu moveFromAddr:0x1000 toReg:Reg0])           //将内存地址0x1000处的内存值保存到寄存器Reg0中
    VINSTRUCT(5, [cpu moveFromConst:25 toReg:Reg1])             //将常数25保存到CPU的寄存器Reg1中
    VINSTRUCT(6, [cpu isEqualReg:Reg0 withReg:Reg1 thenGoto:9]) //如果Reg0中的值和Reg1中的值相等则执行第9条指令:进行打印输出
    VINSTRUCT(7, [cpu moveFromReg:Reg1 toAddr:0x1000])           //将寄存器Reg1中的值保存到内存地址为0x1000中。
    VINSTRUCT(8, [cpu jumpTo:10])                               //跳转去执行第10条指令
    VINSTRUCT(9, [cpu sys:Int3])                                //系统调用,输出保存在Reg0中的值。
    VINSTRUCT(10, [cpu ret])                                    //程序结束。
    
    VINSTRUCT_END
}

您能看懂上面代码所实现的功能吗(要想查看并运行完整的代码请到我的 github站点中的VirtualSystem处下载)?您是否在例子里面隐约的感受到了上面代码里面涉及到的一些关于CPU、内存、进程等方面的概念和知识了?它其实就是实现了如下的简单功能:

    -(void)main
    {
         int  a = 10;
         int  b = 15;
         a = a + b;
         if ( a == 25){
               NSLog(@"output:%d",a);
           }
     }

考察一下VCPU类,你会发现这个类提供了一些非常基础的操作方法:加减处理、数据移动、比较、地址跳转、系统调用等功能。调用者可以利用这个类提供的操作方法来编写并完成某个特定的功能。这个类的内部实现还提供了几个临时存储空间,可以通过RegNum编号来读写里面的数值。 VCPU实际上是一个对真实CPU所具有的能力的一个简单的模拟类。我们来看一下CPU的组成:

深入iOS系统底层之指令集介绍

CPU组成

从上面的CPU结构图片中可以看出CPU主要分为存储单元(SU)运算单元(ALU)以及控制单元(CU)。如果将这些部件和结构映射到VCPU这个类时你会发现:存储单元所对应的就是里面的数据成员;而运算单元和控制单元则对应里面的所有实例方法,运算单元提供了CPU指令的实现(VCPU类提供了众多的方法实现)。

我们称一个CPU里面所提供的所有的指令的集合称之为指令集。

我们可以用OC语言来实现一个VCPU类,也可以用Swift语言来实现一个Swift版本的VCPU类,也可以用Java语言来实现一个Java版本的VCPU类。 这其中不同的语言所提供的方法的定义形式是完全不同的: 就比如说OC里面可以提供直接操作内存地址的方法,但是Java里面则无法提供直接操作内存地址的方法;即使是OC语言中我们要实现VCPU类中的方法也可以有很多种不同的方式。

不同的厂家以及不同的技术工艺和技术水平以及具体的设备上所实现的CPU的体系架构以及提供的功能也是有差异的。比如ARM指令架构体系的CPU、x86指令体系架构的CPU、POWER-PC指令架构体系的CPU。这些不同体系的CPU因为架构完全不同导致所提供的指令和存储单元也完全不同。我们不可能让ARM指令直接在X86的CPU上执行(就如OC的提供方法无法在Java中执行是一个道理)。相同体系架构下的CPU指令则在一定程度上是可以相互兼容的,因为相同架构体系下的CPU的指令集是一致的(类比为接口一致,但是内部实现则不相同),比如说Intel公司所生产的x86系列的CPU和AMD公司所生产的x86系列CPU所提供的指令集是相似和兼容的,他们之间的差别只是内部的实现不同而已。

CPU指令集定义的是一个中央处理器所应该提供的基础功能的集合,它是一个标准是一个接口也是一个协议。在软件开发中具有协议和接口定义的概念,无论是消费者还是提供者都需要遵循这个标准来进行编程和交互:提供者要实现接口所具有的功能,至于如何实现则是内部的事情,不对外暴露,消费者也不需要知道具体的实现细节;消费者则总是要按接口提供的功能方法并组合使用来完成某种功能。这种设计的思维对于硬件系统也是一样适用的。一般情况下某种CPU指令集通常都是由某些设计或者生产CPU的公司或者某标准组织共同定义而形成。那么目前市面上有哪些主流的CPU指令集或CPU架构体系呢?

此处参考自:https://baike.baidu.com/item/Intel%20x86/1012845?fromtitle=x86&fromid=6150538

x86架构是Intel公司在1978年推出的Intel 8086中央处理器中首度出现,从Intel80386开始支持32位的系统。x86现在几乎是个人计算机的标准平台,成为了历来最成功的CPU架构。其他公司也有制造x86架构的处理器:有AMD、Cyrix、NEC、IBM、IDT以及Transmeta等。

64位架构

到2002年,由于32位特性的长度,x86的架构开始到达某些设计的极限。这个导致要处理大量的信息储存大于4GB会有困难。Intel原本已经决定在64位的时代完全地舍弃x86兼容性,推出新的架构称为IA-64技术作为他的Itanium处理器产品线的基础。IA-64与x86的软件天生不兼容;它使用各种模拟形式来运行x86的软件,不过,以模拟方式来运行的效率十分低下,并且会影响其他程序的运行。AMD公司则主动把32位x86(或称为IA-32)扩充为64位。它以一个称为AMD64的架构出现(在重命名前也称为x86-64),且以这个技术为基础的第一个产品是单内核的Opteron和Athlon 64处理器家族。由于AMD的64位处理器产品线首先进入市场,且微软也不愿意为Intel和AMD开发两套不同的64位操作系统,Intel也被迫采纳AMD64指令集且增加某些新的扩充到他们自己的产品,命名为EM64T架构(显然他们不想承认这些指令集是来自它的主要对手),EM64T后来被Intel正式更名为Intel 64(也就是x64指令集)。

在iOS编程时如果要运行在模拟器上,代码生成的机器指令时就需要指定使用i386还是x64指令集,因为目前的mac电脑上基本采用了x86或者x64架构的CPU。

  • ARM指令集

此处参考自:https://baike.baidu.com/item/ARM/7518299

ARM处理器是英国Acorn有限公司设计的低功耗成本的第一款RISC微处理器。全称为Advanced RISC Machine。ARM处理器本身是32位设计,但也配备16位指令集。1978年12月5日,物理学家赫尔曼·豪泽(Hermann Hauser)和工程师Chris Curry,在英国剑桥创办了CPU公司(Cambridge Processing Unit),主要业务是为当地市场供应电子设备。1979年,CPU公司改名为Acorn公司。

起初,Acorn公司打算使用摩托罗拉公司的16位芯片,但是发现这种芯片太慢也太贵。"一台售价500英镑的机器,不可能使用价格100英镑的CPU!"他们转而向Intel公司索要80286芯片的设计资料,但是遭到拒绝,于是被迫自行研发。

1985年,Roger Wilson和Steve Furber设计了他们自己的第一代32位、6M Hz的处理器,

深入iOS系统底层之指令集介绍

Roger Wilson和Steve Furber

并用它做出了一台RISC指令集的计算机,简称ARM(Acorn RISC Machine)。这就是ARM这个名字的由来。

目前市面上的主流智能手机等移动设备配备的CPU都采用ARM架构。iOS应用真机编译出来的机器指令都是ARM指令,因此需要在编译时指定armv7或者arm64指令集。

  • MIPS架构

此处参考自: https://baike.baidu.com/item/MIPS架构/1539401?fr=aladdin

MIPS架构(英语:MIPS architecture,为Microprocessor without interlocked piped stages architecture的缩写),是一种采取精简指令集(RISC)的处理器架构,1981年出现,由MIPS科技公司开发并授权,广泛被使用在许多电子产品、网络设备、个人娱乐装置与商业装置上。最早的MIPS架构是32位,最新的版本已经变成64位。

目前国内的龙芯CPU,采用的就是MIPS指令集。

  • POWER -PC

此处参考自:https://baike.baidu.com/item/POWER%20PC/5963071?fr=aladdin

POWER-PC由摩托罗拉公司和苹果公司联合开发的高性能32位和64位RISC微处理器系列,以与垄断PC机市场的Intel微处理器和微软公司的软件相竞争。PowerPC微处理器1994年推出。

IBM以前跟Intel竞争过桌面处理器市场,但由于市场策略不当等原因,IBM没赚到什么钱,于是决定退出桌面市场。POWER系列处理器是它退出桌面市场后才开发出来的服务器用处理器,苹果电脑用的处理器只是Power系列里的一种,据说是IBM为苹果特制的简化版本,而苹果独一无二的经营理念使苹果电脑与其它PC都不兼容,所以目前的Power系列处理器不能用于桌面PC。目前苹果电脑因PowerPC处理器不适合苹果发展而转而使用Intel处理器。

您是否在很多iOS库的头文件里面看到过POWER-PC的宏定义,早期的苹果电脑都用POWER-PC的CPU,现在苹果电脑基本都改为x64架构的CPU了。

CPU体系的分类

上面列出了一些关于CPU架构和指令集的介绍,不同的体系结构具有各自的优缺点,我们可以从不同的角度对CPU进行分类:

按字长

所谓字长就是指CPU的指令在一个周期内能够处理的最大的数字或者理解为对内存地址的最大的寻址能力。因此按这个长度可以做如下分类:

  • 8位(比如:Intel8086/以及一些小型家电的芯片)

  • 16位(比如:Intel80286)

  • 32位(比如:Intel的x86系列, ARM的armv7,armv7s系列)

  • 64位(比如:Intel的x64, ARM的arm64)

一般情况下大字长的CPU指令集都会兼容小字长的CPU指令集。比如32位的应用程序能够在64位的CPU上执行,而小字长的CPU指令集则无法直接提供大字长指令集的能力,如需要支撑则通常都是通过模拟来完成的,比如说一个64位字长CPU的读取数据指令在32位字长CPU上就可以通过模拟两次读取来完成,现在有的CPU提供了指令模拟的功能,因此某些64位的应用程序还是可以运行在32位的CPU上的,只不过性能和速度会存在很大的损耗。

按指令复杂度

此处参考自:https://wenku.baidu.com/view/b5a138d43186bceb19e8bb62.htmlhttps://zhidao.baidu.com/question/200786121943026445.html

所谓指令的复杂度就是指CPU指令集中所提供的指令的数量、指令寻址模式、指令参数、以及CPU内部的架构设计的复杂度、以及指令本身所占据的字节数等来进行划分的一种方式,一般有两种类型的分类:

  • CISC指令集。CISC的英文全称为“Complex Instruction Set Computer”,即“复杂指令系统计算机”,从计算机诞生以来,人们一直沿用CISC指令集方式。早期的桌面软件是按CISC设计的,并一直沿续到现在。目前,桌面计算机流行的x86体系结构即使用CISC。在CISC微处理器中,程序的各条指令是按顺序串行执行的,每条指令中的各个操作也是按顺序串行执行的。顺序执行的优点是控制简单,但计算机各部分的利用率不高,执行速度慢。CISC架构的服务器主要以x86/x64架构(Intel Architecture)为主,而且多数为中低档服务器所采用。

  • RISC指令集。RISC的英文全称为“Reduced Instruction Set Computer”,即“精简指令集计算机”,是一种执行较少类型计算机指令的微处理器,起源于80年代的MIPS主机(即RISC机),RISC机中采用的微处理器统称RISC处理器。这样一来,它能够以更快的速度执行操作(每秒执行更多百万条指令,即MIPS)。目前的智能移动设备中的CPU几乎都采用RISC指令集,比较有代表的就是ARM指令集和POWER-PC指令集。

下面的表格举出了CISC和RISC两种体系结构的差别:

深入iOS系统底层之指令集介绍

RISC和CISC的差异比较

按指令流和数据流来

此处参考自:http://blog.csdn.net/conowen/article/details/7256260

按指令流和数据流来进行分类的依据是CPU的一条指令可以同时处理多少条数据,或者一条数据同时被多少条指令处理,以及在一个CPU时间周期内可以同时执行多少条指令等规则来划分的。因此可以划分为如下四种:

  • 单指令流单数据流机器(SISD)

SISD机器是一种传统的串行计算机,它的硬件不支持任何形式的并行计算,所有的指令都是串行执行。并且在某个时钟周期内,CPU只能处理一个数据流。因此这种机器被称作单指令流单数据流机器。早期的计算机都是SISD机器,如冯诺.依曼架构,如IBM PC机,早期的巨型机和许多8位的家用机等。

  • 单指令流多数据流机器(SIMD)

SIMD是采用一个指令流处理多个数据流。这类机器在数字信号处理、图像处理、以及多媒体信息处理等领域非常有效。Intel处理器实现的MMXTM、SSE(Streaming SIMD Extensions)、SSE2及SSE3扩展指令集,都能在单个时钟周期内处理多个数据单元。也就是说我们现在用的单核计算机基本上都属于SIMD机器。(个人觉得GPU也属于这个范畴)

  • 多指令流单数据流机器(MISD)

MISD是采用多个指令流来处理单个数据流。由于实际情况中,采用多指令流处理多数据流才是更有效的方法,因此MISD只是作为理论模型出现,没有投入到实际应用之中。

  • 多指令流多数据流机器(MIMD)

MIMD机器可以同时执行多个指令流,这些指令流分别对不同数据流进行操作。最新的多核计算平台就属于MIMD的范畴,例如Intel和AMD的双核处理器等都属于MIMD。

虚拟环境

最后我们还是回到VCPU类来,VCPU是一个对CPU的简单的模拟实现。我们知道用vmware软件可以用来模拟出一个操作系统运行的硬件环境,而实现了虚拟设备的功能;微软公司在2017年宣布他的Visual studio 2017上能够开发并运行iOS应用,并且可以无缝的将代码拷贝到XCODE上编译并运行。其实现的原理是Visual studio2017本身提供了一个OC语言编译器,同时他内部也提供了一个Cocoa UI框架的模拟实现版本,所以能在上面运行iOS应用。

从上面的几个例子中我们可以发现一个特点就是:一个系统各个层次之间的调用总是通过某些约定的规则或者定义的接口来进行的,并且调用者是不知道也不需要知道提供者是如何实现这些能力的,总是一切皆是接口:

深入iOS系统底层之指令集介绍

接口的形式提供层次之间的调用

正是因为有这些接口的定义以及标准的形成,我们才可以将原本真实的实现模拟出另外一个虚拟的实现出来。这也就是所谓的虚拟化的本质。虚拟化可以发生在任何一个层面,也可以进行全局虚拟或者是部分虚拟。我们可以对CPU的指令以及硬件接口进行模拟从而构建出一套类似vmware一样的虚拟机软件来运行任何操作系统;我们也可以对操作系统提供的接口API进行模拟从而构建出一套类似Wine一样的虚拟Windows运行环境出来;我们还可以对操作系统所提供的文件系统或者存储系统来进行模拟从而提供出一套类似Docker之类的应用容器出来;我们也可以对Cocoa Framework进行模拟从而提供出一个套类似Vistual studio2017上能运行和编写OC应用的编译环境来。

深入iOS系统底层之指令集介绍

虚拟的实现原理

虚拟化首先要先接口标准定义,然后再在别人接口之上完成了一套自己的实现。现在的系统从上层的软件到下层的硬件之间都是通过接口协议进行调用的,因此我们可以在各个层次上都实现虚拟的能力。

正文到此结束
Loading...