寻址模式以及寻址模式之所以重要的原因在开始讨论寻址模式之前,让我们首先来回顾一下计算机内存的概念。您可能已经了解了关于内存和编程的一些事实,但是由于现代编程语言正试图淡化计算机中的一些物理概念,因此复习一下相关内容是很有用的:
- 主存中的每个位置都使用连续的数字地址 编号,内存位置就使用这个地址来引用。
- 每个主存位置的长度都是一个字节。
- 较大的数据类型可以通过简单地将多个字节当作一个单位实现(例如,将两个内存位置放到一起作为一个 16 位的数字)。
- 寄存器的长度在 32 位平台上是 4 个字节,在 64 位平台上是 8 个字节。
- 每次可以将 1、2、4 或 8 个字节的内存加载到寄存器中。
- 非数字数据可以作为数字数据进行存储 —— 惟一的区别在于可以对这些数据执行哪些操作,以及如何使用这些数据。
新接触汇编语言的程序员有时可能会对我们有多少访问内存的方法感到惊奇。这些不同的方法就称为寻址模式。 有些模式逻辑上是等价的,但是用途却不同。它们之所以被视为不同的寻址模式,原因在于它们可能根据处理器采用了不同的实现。
有两种寻址模式实际上根本就不会访问内存。在立即寻址模式 中,要使用的数据是指令的一部分(例如 li 指令就表示 “立即加载”,这是因为要加载的数字就是这条指令本身 的一部分)。在寄存器寻址模式 中,我们也不会访问主存的内容,而是访问寄存器。
访问主存最显而易见的寻址模式称为直接寻址模式。在这种模式中,指令本身就包含了数据加载的源地址。这种模式通常用于全局变量访问、分支以及子程序调用。稍微简单的一种模式是相对寻址模式,它会根据当前程序计数器来计算地址。这通常用于短程分支,其中目标地址距当前位置很近,因此指定一个偏移量(而不是绝对地址)会更有意义。这就像是直接寻址模式的最终地址在汇编或链接时就知道了一样。
索引寻址模式 对于全局变量访问数组元素来说是最为有效的一种方式。它包括两个部分:一个内存地址以及一个索引寄存器。索引寄存器会与某个指定的地址相加,结果用作访问内存时使用的地址。有些平台(非 PowerPC)允许程序员为索引寄存器指定一个倍数。因此,如果每个数组元素的长度都是 8 个字节,那么我们就可以使用 8 作为倍数。这样就可以将索引寄存器当作数组索引来使用。否则,就必须按照数据大小来增加或减少索引寄存器了。
寄存器间接寻址模式 使用一个寄存器来指定内存访问的整个地址。这种模式在很多情况中都会使用,包括(但不限于):
- 解除指针变量的引用
- 使用其他模式无法进行的内存访问(地址可以通过其他方式进行计算,并存储到寄存器中,然后就使用这个值来访问内存)
基指针寻址模式 的工作方式与索引寻址模式非常类似(指定的数字和寄存器被加在一起得出最终地址),不过两个元素的作用交换了。在基指针寻址模式中,寄存器中保存的是基址,数字是偏移量。这对于访问结构中的成员是非常有用的。寄存器可以存放整个结构的地址,数字部分可以根据所访问的结构成员进行修改。
最后,假设我们有一个包括 3 个域的结构体:第一个域是 8 个字节,第二个域是 4 个字节,最后一个域是 8 个字节。然后,假设这个结构体本身的地址在一个名为 X 的寄存器中。如果我们希望访问这个结构体的第二个元素,就需要在寄存器中的值上加上 8。因此,使用基指针寻址模式,我们可以指定寄存器 X 作为基指针,8 作为偏移量。要访问第三个域,我们需要指定寄存器 X 作为指针,12 作为偏移量。要访问第一个域,我们实际上可以使用间接寻址模式,而不用使用基指针寻址模式,因为这里没有偏移量(这就是为什么在很多平台上第一个结构体成员都是访问最快的一个成员;我们可以使用更加简单的寻址模式 —— 在 PowerPC 上这并不重要)。
最后,在索引寄存器间接寻址模式 中,基址和索引都保存在寄存器中。所使用的内存地址是通过将这两个寄存器加在一起来确定的。
指令格式的重要性为了解寻址模式对于 PowerPC 处理器上的加载和存储指令是如何工作的,我们必须先要对 PowerPC 指令格式有点了解。PowerPC 使用了加载/存储(也成为 RISC)指令集,这意味着访问主存的惟一 时机就是将内存加载到寄存器或将寄存器中的内容复制到内存中时。所有实际的处理都发生在寄存器之间(或寄存器和立即寻址模式操作数之间)。另外一种主要的处理器体系结构 CISC(x86 处理器就是一种流行的 CISC 指令集)几乎允许在每条指令中进行内存访问。采用加载/存储体系架构的原因是这样可以使处理器的其他操作更加有效。实际上,现代 CISC 处理器将自己的指令转换成了内部使用的 RISC 格式,以实现更高的效率。
PowerPC 上的每条指令都正好是 32 位长,指令的 opcode(操作符,告诉处理器这条指令是什么的代码)占据了前 6 位。这个 32 位的长度包含了所有的立即寻址模式的值、寄存器引用、显式地址以及指令选项。这实现了非常好的压缩。实际上,内存地址对于任何指令格式可以使用的最大长度只有 24 位!最多只能给我们提供 16MB 的可寻址空间。不要担心 —— 有很多方法都可以解决这个问题。这只是为了说明为什么指令格式在 PowerPC 处理器上是如此重要 —— 您需要知道自己到底需要使用多少空间!
您不必记住所有的指令格式就能使用它们。然而,了解一些指令的基本知识可以帮助您读懂 PowerPC 文档,并理解 PowerPC 指令集中的通用策略和一些细微区别。PowerPC 具有 15 种不同的指令格式,很多指令格式都有几种子格式。但只需要关心其中的几种即可。
使用 D-Form 和 DS-Form 指令格式对内存进行寻址D-Form 指令是主要的内存访问指令格式之一。它看起来像下面这样:
D-Form 指令格式0 到 5 位操作码
6 到 10 位源/目标寄存器
11 到 15 位地址/索引寄存器/操作数
16 到 31 位数字地址、偏移量或立即寻址模式值
这种格式用来进行加载、存储和立即寻址模式的计算。它可以用于以下寻址模式:
- 立即寻址模式
- 直接寻址模式(通过指定地址/索引寄存器为 0)
- 索引寻址模式
- 间接寻址模式(通过指定地址为 0 )
- 基指针寻址模式
如您所见,D-Form 指令非常灵活,可以用于任何寄存器加地址的内存访问模式。然而,对于直接寻址和索引寻址来说,它的用处就非常有限了;这是因为它只能使用一个 16 位的地址域。它所提供的最大寻址范围是 64K。因此,直接和索引寻址模式都很少用来获取或存储内存。相反,这种格式更多用于立即寻址模式、间接寻址模式和基指针寻址模式,因为在这些寻址模式中,64K 限制几乎都不是什么问题,因为基寄存器中就可以保存完整的 64 位的范围。
DS-Form 只在 64 位指令中使用。它与 D-Form 非常类似,不同之处在于它使用地址的最后两位作为扩展操作符。然而,它会在地址中 Value 部分最右边加上两个 0 。其范围与 D-Form 指令相同(64K),但是却将其限定为 32 位对齐的内存。对于汇编程序来说,这个值是通常是指定的 —— 它会通过汇编进行浓缩。例如,如果我们希望偏移量为 8,就仍然可以输入 8;汇编程序会将这个值转换成位表示 0b000000000010,而不是 0b00000000001000。如果我们输入一个不是 4 的部署的数字,那么汇编程序就会出错。
注意在 D-Form 和 DS-Form 指令中,如果源寄存器被设置为 0,而不是使用寄存器 0,那么它就不会使用寄存器参数。
下面让我们来看一个使用 D-Forms 和 DS-Forms 构成的指令。
立即寻址模式指定在汇编程序中是这样指定的:
此处 dst 是目标寄存器,src 是源寄存器(在计算中使用),value 是所使用的立即寻址模式的值。立即寻址模式指令永远都不会使用 DS-Form。下面是几个立即寻址模式的指令:
清单 1. 立即寻址模式的指令1
2
3
4
5
6
7
8
9
10
11
12
| #Add the contents of register 3 to the number 25 and store in register 2
addi 2, 3, 25
#OR the contents of register 6 to the number 0b0000000000000001 and store in register 3
ori 3, 6, 0b00000000000001
#Move the number 55 into register 7
#(remember, when 0 is the second register in D-Form instructions
#it means ignore the register)
addi 7, 0, 55
#Here is the extended mnemonics for the same instruction
li 7, 55
|
在使用 D-Form 的非立即寻址模式中,第二个寄存器被加到这个值上来计算加载或存储数据的内存的最终地址。这些指令的通用格式如下:
在这种格式中,加载/存储数据的地址是作为 d(a) 指定的,其中 d 是数字地址/偏移量,而 a 是地址/偏移量所使用的寄存器的编号。它们被加在一起计算加载/存储数据的最终有效地址。下面是几个 D-Form/DS-Form 加载/存储指令的例子:
清单 2. 使用 D-Form 和 DS-Form 加载/存储指令的例子1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| #load a byte from the address in register 2, store it in register 3,
#and zero out the remaining bits
lbz 3, 0(2)
#store the 64-bit contents (double-word) of register 5 into the
#address 32 bits past the address specified by register 23
std 5, 32(23)
#store the low-order 32 bits (word) of register 5 into the address
#32 bits past the address specified by register 23
stw 5, 32(23)
#store the byte in the low-order 8 bits of register 30 into the
#address specified by register 4
stb 30, 0(4)
#load the 16 bits (half-word) at address 300 into register 4, and
#zero-out the remaining bits
lhz 4, 300(0)
#load the half-word (16 bits) that is 1 byte offset from the address
#in register 31 and store the result sign-extended into register 18
lha 18, 1(31)
|
仔细观察,您就可以看出在有一种在指令开头指定的 “基址操作码”,随后是几个修饰符。l 和 s 用于 “load(加载)” 和 “store(存储)” 指令。b 表示一个字节,h 表示一个双字节(16 位)。w 表示一个字(32 位), d 表示一个双字节(64 位)。然后对于加载指令来说,a 和 z 修饰符说明在将数据加载到寄存器中时,该值是符号扩展的,还是简单进行零填充的。最后,还可以附加上一个 u 来告诉处理器使用这条指令的最终计算地址来更新地址计算过程中所使用的寄存器。
使用 X-Form 指令格式对内存进行寻址X-Form 用来进行索引寄存器间接寻址模式,其中两个寄存器中的值会被加在一起来确定加载/存储的地址。X-Form 的格式如下:
X-Form 指令格式0 到 5 位操作码
6 到 10 位源/目标寄存器
11 到 15 位地址计算寄存器 A
16 到 20 位地址计算寄存器 B
21 到 30 位扩展操作符
31 位保留未用
操作符的格式如下:
此处 opcode 是指令的操作符,dst 是数据传输的目标(或源)寄存器,rega 和 regb 是用来计算地址所使用的两个寄存器。
下面给出几个使用 X-Form 的指令的例子: |