超标量架构
早期的单发射架构微处理器的流水线设计目标是做到平均每个时钟周期能执行一条指令,但这一目标不能满足提高处理器性能的要求。为了提高处理器的性能,处理器要具有每个时钟周期发射执行多条指令的能力。超标量体系结构可描述一种微处理器设计理念,它能够让处理器在一个时钟周期执行多条指令。
乱序执行
CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元的技术,避免处理器在计算对象不可获取时等待,从而导致流水线停顿。
寄存器重命名
现代处理器中的一种技术,用于避免机器指令或者微操作不必要的顺序化执行,提高处理器的指令级并行能力。它在乱序执行的流水线中有两个作用——消除指令之间的寄存器读后写相关(Write-after-Read, WAR)和写后写相关(Write-after-Write, WAW),当指令执行发生例外或者转移指令猜测错误而取消后面的指令时,可用于保证现场的精确性。当一条指令要把内容写入一个结果寄存器时不直接写入这个结果寄存器,而是先写入一个中间寄存器进行过渡,当这条指令提交时再写入结果寄存器。
分支预测
当处理一条分支指令时,可能会产生跳转,从而打断流水线指令的处理,因为处理器无法确定该指令的下一条指令,直到分支指令执行完毕。流水线越长,处理器等待的时间便越长,分支预测技术就是为了解决这一问题而出现的。分支预测是处理器在程序分支指令执行前预测其结果的一种机制。在 ARM中,使用全局分支预测器进行分支预测,该预测器由分支目标缓冲器(Branch Target Buffer, BTB)、全局历史缓冲器(Global History Buffer, GHB)、MicroBTB,以及返回栈缓冲器(return stackbuffer)组成。
译码器
指令由操作码和地址码组成。操作码表示要执行的操作性质,即执行什么操作;地址码是操作码执行时的操作对象的地址。计算机执行一条指定的指令时,必须首先分析这条指令的操作码是什么,以决定操作的性质和方法,然后才能控制计算机其他各部件协同完成指令表达的功能,这个分析工作由译码器来完成。
组成计算机的五个经典部件包括输入设备、输出设备、存储器、数据通路和控制器,其中最后两个部件通常合称为处理器。
一个典型的应用程序,如字处理程序或大型数据库系统,可能包括数百万行代码,并依靠丰富的软件库来实现复杂的功能。然而,计算机中的硬件只能执行极为简单的低级指令。从复杂的应用程序到简单的指令,需要经过几个软件层次来将高层的操作(高级语言描述) 逐步解释或翻译成简单的计算机指令。这也是伟大思想抽象的一个典型例子。
下图给出了这些软件的层次结构,最外层是应用软件,中心是硬件,各种系统软件(systems software)位于两者之间。系统软件:提供常用服务的软件,包括操作系统、编译器、加载器和汇编器等。
在复杂的应用中,应用软件通常又有多个层次。例如,一个数据库系统运行于系统软件之上,而系统软件上运行的某个应用可能又反过来运行在该数据库之上。
系统软件有很多种,其中有两种对现代计算机系统来说是最重要的:操作系统和编译器。操作系统(operating system)是用户程序和硬件之间的接口,为用户提供各种服务和管理功能。操作系统最重要的作用包括:
当前主要使用的操作系统有 Linux、iOS 和 Windows。
编译器(compiler)完成另外一项重要功能: 将用高级语言(如C、C++、Java等)编写的程序翻译成硬件能执行的指令。现代编程语言功能强大,而硬件却只能执行简单的指令。因此,翻译过程相当复杂。
操作系统:为了使程序更好地在计算机上运行而管理计算机资源的监管程序。
编译器:将高级语言语句翻译为汇编语言语句的程序。
控制电子设备时,首先要向其发送电信号。对于计算机来说,最简单的信号是通(on)和电(off)。因此,计算机的字母表只需两个字母。正如英语的 26个字母可以随意组合、写多少不受限制一样,计算机的两个字母也可以随意组合。代表这两个字母的符号是0和1,我们通常认为计算机语言就是以2为基数的数或称为二进制数(binary number), 其中每个字母是一个二进制位(binary digit)或称为一个比特(bit)。计算机服从于我们的命令,即指令(instruction)。指令是能被计算机识别并执行的二进制位串。例如,位串
1000110010100000
的含义是告诉计算机将两个数相加。
使用数字既表述指令又表示数据是计算机的基础。
第一代程序员直接使用二进制数与计算机交互,工作起来非常枯燥,以至于人们很快发明了一种接近于人类思维方式的助记符表示。起初,助记符由手工翻译成二进制,过程仍然繁琐。随后人们又开发了专门的软件将助记符自动翻译成对应的二 进制, 利用计算机来帮助生成计算机程序, 这种特殊的软件被称为 汇编器(assembler)。汇编器将助忆符表示的指令翻译为对应的二进制串。这种符号语言的名称沿用至今, 即汇编语言(assembly language)。而机器可以理解的二进制语言被称为机器语言(machine language)。
汇编语言:机器指令的助记符表示。
机器语言: 机器指令的二进制表示。
通过编写一个程序来将更强大的高级语言翻译成计算机指令是计算机发展早期的重大突破。高级编程语言(high-level programminglanguage) 及其编译器极大地提高了软件的生产率和软件质量。下图展示了这些程序和语言之间的关系,这是抽象思想的典型应用。
高级编程语言有多个优点:
立即数寻址:
操作数是位于指令自身中的常数。
寄存器寻址:
操作数是寄存器。
基址寻址/偏移寻址:
操作数在内存中,其地址是一个寄存器和指令中常数的和。
PC相对寻址:
地址是PC与指令中常数的和。
若任务间相互独立,则任务的并行执行是比较容易的。但任务之间往往需要相互协作,这种协作通常意味着某些任务要写的值正是其他任务需要读取的。只有知道写操作何时完成,其他任务才能安全地读取数据。因此,任务之间需要同步。若不同步,就可能产生数据竞争(data race), 即程序运行结果根据事件发生的不同情况而变化。
数据竞争:若来自不同线程的两个访存操作访问同一个地址,它们连续出现,并且至少其中有一个是写操作,那么这两个访存操作就会形成数据竞争。
在计算中,同步机制通常和用户级的程序或例程一起建立,依赖于硬件提供的同步指令。本节我们重点关注加锁和解锁同步操作。用加锁和解锁可以直接建立区域,即互斥(mutual exclusion)区,仅允许单个处理器操作。更复杂的同步机制的实现也与此类似。
在多处理器中实现同步的关键是需要一组硬件原语,能够对一个存储地址进行原子读和原子写,读写之间其他任何操作都不得插入和干预。若没有这样的硬件原语,那么建立基本同步机制的代价将会变得很高,并且这种代价会随着处理器数量的增加而变得更为离谱。
建立基本硬件原语有若干可选的方案,这些方案都可以提供对一个存储地址进行原子和原子写的能力,并能用某种方法辨别一个读/写操作是否为原子操作。通常,体系结构构师并不希望用户直接使用这些基本的硬件原语,而是希望系统程序员用这些原语来建立同步库。
下面介绍一种硬件原语以及如何用这种原语建立基本的同步原语。原子交换(atomic exchange或atomic swap)是建立同步操作的一种典型操作,该原语将寄存器中的一个值和储器中的一个值进行互换。
为了展示如何通过该原语建立同步原语,我们假定需要建立一个简单的锁,其数值为0时表示解锁,为1时表示加锁。处理器尝试对锁单元加锁的方法是,用一个寄存器中的1该锁对应的存储地址的值进行交换。若交换指令返回的值为 1,则表明该锁已被其他处理占用。若返回值为0,则表示锁是自由的,可对其加锁。后一种情况下,加锁成功,锁值样要被修改为1,以防止其他同样获得返回值0的处理器竞争占用。
例如,假定有两个处理器试图同时进行交换操作,这种竞争可以被阻止。因为一个处器先进行交换,返回0;而第二个处理器进行交换时,返回值就变成了1。使用交换原语现同步的关键是操作的原子性:交换操作是不可切分的,同时发生两个交换操作会由硬件进行排序:因此,在这种模式下,对两个试图设置同步变量的处理器而言,让它们都认为同成功设置了同步变量是不可能的。
PS:以后将进一步深入介绍进程同步的相关内容,以及从软件到硬件实现的这一过程。
将一个存储在外存(磁盘或闪存)某文件中的C程序转换为计算机上可执行程序的四个步骤,下图展示了翻译中的各个层次。虽然有些系统可能将这些步骤合并以减少翻译的时间,但实际上程序都要经过这四个逻辑阶段才能被执行。
用高级语言编写的程序首先被编译为汇编语言程序,然后被汇编为机器语言组成的目标模块。链接器将多个模块和库例程组合在一起解析所有的引用。加载器将机器代码加载到内存的适当位置供处理器执行。为了加速翻译过程,某些步骤被跳过或和其他步骤组合在一起。一些编译器直接产生目标模块,一些系统使用带链接功能的加载器直接完成后两步。
这一块详细介绍可以看:C编译器、链接器、加载器详解_51CTO博客_链接器和加载器