CSAPP第四章处理器体系结构

指令被编码为一个或多个字节序列组成的二进制格式,一个处理器支持的指令和指令的字节级编码称他为他的指令集体系结构(ISA)。

Y86-64指令体系结构

Y86-64指令集是本书作者自己定义的一个简单指令集,与X86-64 相比,Y86-64 指令集的数据类型、指令和寻址方式都要少一些。定义一个指令集体系结构包括定义各种状态单元、指令集和它们的编码、一组编程规范和异常事件处理。

程序员可见状态

包括如下的状态单元:

  • 15个程序寄存器,每个程序寄存器存储一个64位的字,寄存器%rsp被入栈、出栈、调用和返回指令作为栈指针。
  • 3个1位条件码,保存着最近的算数或逻辑指令所造成的影响的有关信息
  • 程序计数器PC,存放当前正在执行指令的地址
  • 内存从概念上来说就是一个很大的字节数组,保存着程序和数据
  • 状态码stat,表明程序执行的总体状态,会指示是正常运行还是出现了某种异常

df8d031ef8414464b7d07a60c9511753

Y86-64指令

以下是作者对Y86-64指令的定义,只包括8字节整数操作,寻址方式比较少,操作也少。图中左边是指令的汇编码表示,右边是字节编码:

  • movq指令分成了4个不同的指令:irmovqrrmovqmrmovqrmmovq,分别显式的指明源和目的的格式。源可以是立即数(i)、寄存器(r)或内存(m),指令名字的第一个字母就表明了源的类型。目的可以是寄存器(r)或内存(m),指令名字的第二个字母指明了目的的类型。
  • 4个整数操作指令,见下图OPq指令,分别是addqsubqandqxorq,只对寄存器数据进行操作,这些指令会设置3个条件码ZFSFOF(零、符号和溢出)。
  • 7个跳转指令是jmpjlejljejnejgejg
  • 6个条件传送指令:cmovlecmovlcmovecmovnecmovgecmovg
  • call将call的下条指令地址值压到栈顶,然后将PC值设置为call后面跟的目的地址;ret将PC设置为当前栈顶存放的值。
  • pushqpopq指令实现了入栈和出栈。
  • halt指令停止指令的执行,执行halt指令会导致处理器停止,并将状态码设置为HLT

2111c985e864413188d354d12ca1f7bc

指令编码

如1.2中的图4.2所给指令字节级编码,每条指令需要 1 ~ 10 个字节不等,且第一个字节表明指令的类型,这个字节分为两个部分,每部分 4 位:高 4 位是code部分(值为 0 ~ 0xB),低 4 位是function部分(function值只有在一组相关指令共用一个code时才有用),如下图所示:

3e23e574e05d4cd29ee4e13615af8c07

15个程序寄存器中每个都有一个范围在0~0xE之间的寄存器标识符(registerID),程序寄存器保存在CPU的寄存器文件中,这个寄存器文件就是一个小的、以寄存器ID作为地址的随机访问存储器,在编码过程中,当需要指明不应访问任何寄存器时,就用ID0xF来表示。

f880889a10e8402c8f474003a16198b4

指令集的一个重要性质就是字节编码必须有唯一的解释,任意一个字节序列要么是一个确定且唯一的指令序列的编码,要么就不是一个合法的字节序列。Y86-64就具有这个性质,因为每条指令的第一个字节有唯一的codefunction组合,给定这个字节,就可以决定所有其他附加字节的长度和含义。这个性质保证了处理器可以无二义性地执行目标代码程序,即使代码嵌入在程序的其他字节中,只要从序列的第一个字节开始处理,我们仍然可以很容易确定指令序列。反过来说,如果不知道一段代码序列的起始位置,我们就不能确定如何将字节序列划分成单独的指令,所以确定的字节序列 => 起始位置 + 指令的字节编码

Y86-64异常

Y86-64状态码可以取以下值,1表示执行正常,2表示执行一条halt指令,3遇到非法读写,4表示遇到非法指令代码,其中2、3、4则为异常状态码。Y86-64的状态码为异常时,程序会停止(没有异常处理),一般完整的指令集定义,都会有异常处理程序

dc3bece75ac9492c9e0dab03e9553b50

逻辑设计和硬件控制语言HCL

逻辑门

逻辑门类比于C语言的逻辑运算,而不是按位与、或、非。逻辑门总是活动的(active),一旦一个门的输入发生变化,在很短的时间内,输出也会相应地变化。

e9f399cab4c84d929dedcbdade72cfbd

组合电路和HCL布尔表达式

将很多的逻辑门组合成一个网,就能构建计算块(computational block),称为组合电路(combinational circuits)。如何构建这些网有几个限制:

  • 每个逻辑门的输入必须连接到下述选项之一:一个系统输入,某个存储器单元的输出,某个逻辑门的输出。
  • 两个或多个逻辑门的输出不能连接在一起。否则可能会使线上的信号矛盾,可能会导致一个不合法的电压或电路故障。
  • 这个网必须是无环的。也就是在网中不能有路径经过一系列的门而形成一个回路,这样的回路会导致该网络计算的函数有歧义。
  • 下图中的两个组合电路,第一个是位相等,第二个是多路复用器(通常称为MUX)。

bb1e7ca337684ae789accd0c4e243f69

位相等和多路复用器对应的HCL表达式分别如下:

1
2
bool eq = (a && b) ||(!a && !b);
bool out = (s && a) ||(!s && b);

集合关系

在处理器设计中,很多时候都需要将一个信号与许多可能匹配的信号做比较,以此来检测正在处理的某个指令代码是否属于某一类指令代码。举个例子,假设想从一个2位信号 code 中选择高位和低位来产生四路复用器的控制信号 s1 和 s0,对应的HCL表达式:

1
2
3
bool s1 = code == 2 || code == 3;
bool s0 = code == 1 || code == 3;

将其写成集合关系表述就是:

1
2
bool s1 = code in {2, 3};
bool s0 = code in {1, 3};

存储器和时钟

组合电路从本质上讲,不存储任何信息,相反,它们只是简单地响应输入信号,产生等于输入的某个函数的输出。为了产生时序电路(能够存储各种操作之间的信息),也就是有状态并且在这个状态上进行计算的系统,我们必须引入按位存储信息的设备。存储设备都是由同一个时钟控制的,时钟是一个周期性信号,决定什么时候要把新值加载到设备中。考虑两类存储器设备:

  • 时钟寄存器(简称寄存器),存储单个位或字,时钟信号控制寄存器加载输入值。
  • 随机访问存储器(简称内存),存储多个字,用地址来选择应该读或写哪个字,随机访问存储器包括:
    • 处理器的虚拟内存系统,硬件和操作系统软件结合起来使处理器可以在一个很大的地址空间内访问任意的字;
    • 寄存器文件,在其里面,寄存器标识符(r0、r1…)作为地址,CPU根据地址获取寄存器中的值。在 IA32 或 y86-64 处理器中,寄存器文件有15 个程序寄存器(%rax ~ %r14)。在MIPS中,寄存器文件有32个通用寄存器。

这里需要区分一下针对于组合电路所说的硬件寄存器和针对于机器级编程的程序寄存器:

  • 在硬件中,寄存器直接将它的输入和输出线连接到电路的其他部分,用来存储计算的状态。
  • 在机器级编程中,寄存器代表的是 CPU 中为数不多的可寻址的字,这里的地址是寄存器 ID。这些字通常都保存在CPU的寄存器文件中,寄存器文件就是一个小的、以寄存器ID作为地址的随机访问存储器。

下图是硬件寄存器的工作方式,大多数时候,寄存器都保持在稳定状态(用 x 表示),产生的输出等于它的当前状态。当新的信号沿着寄存器前面的组合逻辑电路传播,这时,产生了一个新的寄存器输入(用 y 表示),但是只要时钟是低电位的,寄存器的输出就仍然保持不变。当时钟变成高电位时候,输入信号就加载到寄存器中,成为下一个状态 y,直到下一个时钟上升沿,这个状态就一直是寄存器的新输出。每当每个时钟到达上升沿时,值才会从寄存器的输入传送到输出。

8508090571354a139f77573746b73529

下图是寄存器文件的工作方式,寄存器文件不是组合电路,因为它有内部存储。寄存器文件有两个读端口(A 和 B),还有一个写端口(W),这样一个多端口随机访问存储器允许同时进行多个读和写操作。两个读端口有地址输入 srcA 和 srcB和对应的数据输出 valA 和 valB,写端口有地址输入 dstW,以及数据输入 valW。例如,读取$r3中的值时,将 src A 设为 3,在一段延迟之后,程序寄存器 %rbx 中存放的值就会出现在输出 valA 上。向寄存器文件写入值是由时钟信号控制的,控制方式类似于将值加载到时钟寄存器,每次时钟上升时,输入 valW 上的值会被写入输入 dstW 上的寄存器 ID 指示的程序寄存器,当 dstW 设为特殊的 ID 值 0xF 时,不会写任何程序寄存器。

Y86-64的顺序实现

注:本章节太偏硬件了与本人的方向相异太多,暂时tj此章节。