首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

第一个 POWER5 程序

第一个 POWER5 程序

您的第一个 POWER5 程序 现在我们来看实际代码。我们编写的第一个程序仅仅载入两个值、将其相加并退出,将结果作为状态代码,除此之外没有其他功能。将一个文件命名为 sum.s,在其中输入如下代码:
清单 1. 您的第一个 POWER5                程序
1
#Data sections holds writable memory declarations .data .align 3 #align to 8-byte boundary #This is where we will load our first value from first_value: #"quad" actually emits 8-byte entities .quad 1 second_value: .quad 2 #Write the "official procedure descriptor" in its own section .section ".opd","aw" .align 3 #align to 8-byte boundary #procedure description for ._start .global _start #Note that the description is named _start, # and the beginning of the code is labeled ._start _start: .quad ._start, .TOC.@tocbase, 0 #Switch to ".text" section for program code .text ._start: #Use register 7 to load in an address #64-bit addresses must be loaded in 16-bit pieces #Load in the high-order pieces of the address lis 7, first_value@highest ori 7, 7, first_value@higher #Shift these up to the high-order bits rldicr 7, 7, 32, 31 #Load in the low-order pieces of the address oris 7, 7, first_value@h ori 7, 7, first_value@l #Load in first value to register 4, from the address we just loaded ld 4, 0(7) #Load in the address of the second value lis 7, second_value@highest ori 7, 7, second_value@higher rldicr 7, 7, 32, 31 oris 7, 7, second_value@h ori 7, 7, second_value@l #Load in the second value to register 5, from the address we just loaded ld 5, 0(7) #Calculate the value and store into register 6 add 6, 4, 5 #Exit with the status li 0, 1 #system call is in register 0 mr 3, 6 #Move result into register 3 for the system call sc




讨论程序本身之前,先构建并运行它。构建此程序的第一步是汇编 它:
as -m64 sum.s -o sum.o
这会生成一个名为 sum.o 的文件,其中包含对象代码,这是汇编代码的机器语言版,还为连接器增加了一些附加信息。“-m64” 开关告诉汇编程序您正在使用 64 位 ABI 和 64                    位指令。所生成的对象代码是此代码的机器语言形式,但无法直接按原样运行,还需要进行连接,之后操作系统才能加载并运行它。连接的方法如下:
ld -melf64ppc sum.o -o sum
这将生成可执行的 sum。要运行此程序,按如下方法操作:
./sum
echo $?
这将输入 “3”,也就是最终结果。现在我们来看看这段代码的实际工作方式。
由于汇编语言代码的工作方式非常接近操作系统的级别,因此组织方式与它将生成的对象和可执行文件也很相近。那么,为了理解代码,我们首先需要理解对象文件。
对象和可执行文件划分为 “节”。程序执行时,每一节都会载入地址空间内的不同位置。它们都具有不同的保护和目的。我们需要关注的主要几节包括:
.data 包含用于该程序的预初始化数据
.text 包含实际代码(过去称为程序文本)
.opd 包含 “正式过程声明”,它用于辅助连接函数和指定程序的入口点(入口点就是要执行的代码中的第一条指令)
我们的程序做的第一件事就是切换到 .data 节,并将对齐量设置为 8 字节的边界(.align 3 会将汇编程序的内部地址计数器对齐为 2^3 的倍数)。
first_value: 这一行是一个符号声明。它将创建一个称为 first_value                的符号,与汇编程序中列出的下一条声明或指令的地址同义。请注意,first_value 本身是一个常量                而不是变量,尽管它所引用的存储地址可能是可更新的。first_value 只是引用内存中特定地址的一种简化方法。
下一条伪指令 .quad 1 创建一个 8 字节的数据值,容纳值 1。
之后,我们使用类似的一组伪指令定义地址 second_value,容纳 8 字节数据项,值为 2。
.section ".opd", "aw" 为我们的过程描述符创建一个 “.opd” 节。强制这一节对齐到 8 字节边界。然后将符号 _start                声明为全局符号,也就是说它在连接后不会被丢弃。然后声明 _start 腹稿本身( .globl 汇编程序未定义                _start,它只是使其在定义后成为全局符号)。接下来生成的三个数据项是过程描述符,本系列后续文章中将讨论相关内容。
现在转到实际程序代码。.text 伪指令告诉汇编程序我们将切换到 “text” 一节。之后就是 ._start 的定义。
第一组指令载入第一个值的地址,而非值本身。由于 PowerPC 指令仅有 32 位长,指令内仅有 16 位可用于加载常量值(切记,address of                first_value 是常量)。由于地址最多可达到 64 位,因此我们必须采用每次一段的方式载入地址(本系列的第 2 部分将介绍如何避免这样做)。汇编程序中的                @ 符号指示汇编程序给出一个符号值的特殊处理形式。这里使用了以下几项:
@highest 表示一个常量的第 48-63 位
@higher 表示一个常量的第 32-47 位
@h 表示一个常量的第 16-31 位
@l 表示一个常量的第 0-15 位
所用的第一条指令表示 “载入即时移位(load immediate shifted)”。这会在最右端(first_value 的第 48-63 位)载入值,将数字移位到左边的 16                位,然后将结果存储到寄存器 7 中。寄存器 7 的第 16-31 位现包含地址的第 48-63 位。接下来我们使用 “or immediate” 指令对寄存器 7                和右端的值(first_value 的第 32-47 位)执行逻辑或运算,将结果存储到寄存器 7 中。现在地址的第 32-47 位存储到了寄存器的第 0-15 位中。寄存器 7                现左移 32 位,0-31 位将清空,结果存储在寄存器 7 中。现在寄存器 7 的第 32-63 位包含我们所载入的地址的第 32-63 位。下两条指令使用了 “or immediate” 和 “or                immediate shifted” 指令,以类似的方式载入第 0-31 位。
仅仅是要载入一个 64 位值就要做许多工作。这也就是为什么 PowerPC 芯片上的大多数操作都通过寄存器完成,而不通过立即值 —— 寄存器操作可一次使用全部 64                位,而不仅限于指令的长度。下一期文章将介绍简化这一任务的寻址模式。
现在只要记住,这只会载入我们想载入的值的地址。现在我们希望将值本身载入寄存器。为此,将使用寄存器 7 去告诉处理器希望从哪个地址处载入值。在圆括号中填入 “7” 即可指出这一点。指令                ld 4, 0(7) 将寄存器 7 中地址处的值载入寄存器 4(0 表示向该地址加零)。现在寄存器 4 是第一个值。
使用类似的过程将第二个值载入寄存器 5。
加载寄存器之后,即可将数字相加了。指令 add 6, 4, 5 将寄存器 4 的内容与寄存器 5 的内容相加,并将结果存储在寄存器 6(寄存器 4 和寄存器 5 不受影响)。
既然已经计算出了所需值,接下来就要将这个值作为程序的返回/退出值了。在汇编语言中退出一个程序的方法就是发起一次系统调用(使用 exit                系统调用退出)。每个系统调用都有一个相关联的数字。这个数字会在实现调用前存储在寄存器 0 中。从寄存器 3 开始存储其余参数,系统调用需要多少参数就使用多少寄存器。然后 sc                指令使内核接收并响应请求。exit 的系统调用号是 1。因此,我们需要首先将数字 1 移动到寄存器 0 中。
在 PowerPC 机器上,这是通过加法完成的。addi 指令将一个寄存器与一个数字相加,并将结果存储在一个寄存器中。在某些指令中(包括 addi),如果指定的寄存器是寄存器                0,则根本不会加上寄存器,而是使用数字 0。这看上去有些令人糊涂,但这样做的原因在于使 PowerPC 能够为相加和加载使用相同的指令。
退出系统调用接收一个参数 —— 退出值。它存储在寄存器 3 中。因此,我们需要将我们的应答从寄存器 6 移动到寄存器 3 中。“register move” 指令 rm 3, 6                执行所需的移动操作。现在我们就可以告诉操作系统已经准备好接受它的处理了。
调用操作系统的指令就是 sc,表示 “system call”。这将调用操作系统,操作系统将读取我们置于寄存器 0 和寄存器 3 中的内容,然后退出,以寄存器 3                的内容作为返回值。在命令行中可使用命令 echo $? 检索该值。
需要指出,这些指令中许多都是多余的,目的仅在于教学。例如,first_value 和 second_value                实际上是常量,因此我们完全可以直接载入它们,跳过数据节。同样,我们也能一开始就将结果存储在寄存器 3 中(而不是寄存器 6),这样就可以免除一次寄存器移动操作。实际上,可以将寄存器同时                作为源寄存器和目标寄存器。所以,如果想使其尽可能地简洁,可将其写为如下形式:
清单 2.                第一个程序的简化版本
1
.section ".opd", "aw" .align 3 .global _start _start: .quad ._start, .TOC.@tocbase, 0 .text li 3, 1 #load "1" into register 3 li 4, 2 #load "2" into register 4 add 3, 3, 4 #add register 3 to register 4 and store the result in register 3 li 0, 1 #load "1" into register 0 for the system call sc




查找最大值 我们的下一个程序将提供更多一点的功能 —— 查找一组值中的最大值,退出并返回结果。
在名为 max.s 的文件中键入如下代码:
清单 3.                查找最大值
1







为汇编、连接和运行程序,执行:
as -a64 max.s -o max.o
ld -melf64ppc max.o -o max
./max
echo $?
您之前已体验了一个 PowerPC 程序,也了解了一些指令,那么应该可以看懂部分代码。数据节与上一个程序基本相同,差别只是在 value_list 声明后有几个值。注意,这不会改变                value_list —— 它依然是指向紧接其后的第一个数据项地址的常量。对于之后的数据,每个值使用 64 位(通过 .quad                表示)。入口点声明与前一程序相同。
对于程序本身,需要注意的一点就是我们记录了各寄存器的用途。这一实践将很好地帮助您跟踪代码。寄存器 3 存储当前最大值,初始设置为 0。寄存器 4 包含要载入的下个值的地址。最初是                value_list,每次遍历前进 8 位。寄存器 5 包含紧接 value_list 中数据之后的地址。这使您可以轻松比较寄存器 4 和寄存器 5,以便了解是否到达了列表末端,并了解何时需要跳转到                end。寄存器 6 包含从寄存器 4 指向的位置处载入的当前值。每次遍历时,它都会与寄存器 3(当前最大值)比较,如果寄存器 6 较大,则用它取代寄存器 3。
注意,我们为每个跳转点标记了其自己的符号化标签,这使我们能够将这些标签作为跳转指令的目标。例如,beq end 跳转到这段代码中紧接 end                符号定义之后的代码处。
要注意的另外一条指令是 ld 6, 0(4)。它使用寄存器 4 中的内容作为存储地址来检索一个值,此值随后存储到寄存器 6 中。
结束语 如果一切顺利,您现在对 PowerPC 的汇编语言编程应有了基本的了解。指令最初看上去可能有点麻烦,但习惯总会成自然。在下一期文章中,我们将介绍 PowerPC                处理器的各种寻址模式,说明如何更有效地将它们用于 64 位编程。第 3 篇文章将更全面地介绍 ABI,讨论有哪些寄存器、分别有哪些用途、如何调用函数并从函数返回,以及关于 ABI 的其他有趣内容。
返回列表