计算机体系结构

当前流行的计算机基本采用的是冯·诺伊曼计算机体系结构,采用的是一种将程序指令和数据存储在一起的存储结构。冯·诺伊曼计算机中的指令和数据存储器其实指的是计算机中的内存,然后在配合CPU处理器就组成了一个最简单的计算机了。

下图是 Intel 公司实现的 X86/AMD64 体系结构

AMD64架构

左边是内存部分是常见的内存布局。其中text一般对应代码段,用于存储要执行指令数据,代码段一般是只读的。然后是rodata和data数据段,数据段一般用于存放全局的数据,其中rodata是只读的数据段。而heap段则用于管理动态的数据,stack段用于管理每个函数调用时相关的数据。在汇编语言中一般重点关注text代码段和data数据段,因此Go汇编语言中专门提供了对应TEXT和DATA命令用于定义代码和数据。

中间是X86提供的寄存器。寄存器是CPU中最重要的资源,每个要处理的内存数据原则上需要先放到寄存器中才能由CPU处理,同时寄存器中处理完的结果需要再存入内存。X86中除了状态寄存器FLAGS和指令寄存器IP两个特殊的寄存器外,还有AX、BX、CX、DX、SI、DI、BP、SP几个通用寄存器。在X86-64中又增加了八个以R8-R15方式命名的通用寄存器。因为历史的原因R0-R7并不是通用寄存器,它们只是X87开始引入的MMX指令专有的寄存器。在通用寄存器中BP和SP是两个比较特殊的寄存器:其中BP用于记录当前函数帧的开始位置,和函数调用相关的指令会隐式地影响BP的值;SP则对应当前栈指针的位置,和栈相关的指令会隐式地影响SP的值;而某些调试工具需要BP寄存器才能正常工作。

右边是X86的指令集。CPU是由指令和寄存器组成,指令是每个CPU内置的算法,指令处理的对象就是全部的寄存器和内存。我们可以将每个指令看作是CPU内置标准库中提供的一个个函数,然后基于这些函数构造更复杂的程序的过程就是用汇编语言编程的过程。

Go汇编中的伪寄存器

Go汇编为了简化汇编代码的编写,引入了PC、FP、SP、SB四个伪寄存器。四个伪寄存器加其它的通用寄存器就是Go汇编语言对CPU的重新抽象,该抽象的结构也适用于其它非X86类型的体系结构。

四个伪寄存器和X86/AMD64的内存和寄存器的相互关系如下图:

Go汇编的伪寄存器

在AMD64环境,伪PC寄存器其实是IP指令计数器寄存器的别名。伪FP寄存器对应的是函数的帧指针,一般用来访问函数的参数和返回值。伪SP栈指针对应的是当前函数栈帧的底部(不包括参数和返回值部分),一般用于定位局部变量。伪SP是一个比较特殊的寄存器,因为还存在一个同名的SP真寄存器。真SP寄存器对应的是栈的顶部,一般用于定位调用其它函数的参数和返回值。

当需要区分伪寄存器和真寄存器的时候只需要记住一点:伪寄存器一般需要一个标识符和偏移量为前缀,如果没有标识符前缀则是真寄存器。比如(SP)+8(SP)没有标识符前缀为真SP寄存器,而a(SP)b+8(SP)有标识符为前缀表示伪寄存器。

全局变量的内存分布

在Go汇编语言中变量是没有类型的,因此在Go语言中有着不同类型的变量,底层可能对应的是相同的内存结构。深刻理解每个变量的内存布局是汇编编程时的必备条件。

[2]int类型数组的内存布局:

数组全局变量定义

变量在data段分配空间,数组的元素地址依次从低向高排列。

然后再查看下标准库图像包中image.Point结构体类型变量的内存布局:

结构体全局变量定义

变量也是在data段分配空间,变量结构体成员的地址也是依次从低向高排列。

因此[2]intimage.Point类型底层有着近似相同的内存布局。

本文节选自