1
0
mirror of https://github.com/chai2010/advanced-go-programming-book.git synced 2025-05-24 04:22:22 +00:00

ch3-02: done

This commit is contained in:
chai2010 2018-06-02 18:04:10 +08:00
parent 72aa19f8a8
commit 81b9e232cd
2 changed files with 9 additions and 41 deletions

View File

@ -24,7 +24,7 @@
* [2.12. 补充说明](ch2-cgo/ch2-12-faq.md)
* [第三章 汇编语言](ch3-asm/readme.md)
* [3.1. 快速入门](ch3-asm/ch3-01-basic.md)
* [3.2. 计算机结构(Doing)](ch3-asm/ch3-02-arch.md)
* [3.2. 计算机结构](ch3-asm/ch3-02-arch.md)
* [3.3. 常量和变量(TODO)](ch3-asm/ch3-03-const-and-var.md)
* [3.4. 函数(TODO)](ch3-asm/ch3-04-func.md)
* [3.5. 控制流(TODO)](ch3-asm/ch3-05-control-flow.md)

View File

@ -1,4 +1,4 @@
# 3.2. 计算机结构(Doing)
# 3.2. 计算机结构
汇编语言是直面计算机的编程语言因此理解计算机结构是掌握汇编语言的前提。当前流行的计算机基本采用的是冯·诺伊曼计算机体系结构在某些特殊领域还有哈佛体系架构。冯·诺依曼结构也称为普林斯顿结构采用的是一种将程序指令和数据存储在一起的存储结构。冯·诺伊曼计算机中的指令和数据存储器其实指的是计算机中的内存然后在配合CPU处理器就组成了一个最简单的计算机了。
@ -86,49 +86,17 @@ X86其实是是80X86的简称后面三个字母包括Intel 8086、80286
很多汇编语言的教程都会强调汇编语言是不可移植的。严格来说很多汇编语言在不同的CPU类型、或不同的操作系统环境、或不同的汇编工具链下是不可移植的。而这种不可移植性正是汇编语言普及的一个极大的障碍。虽然CPU指令集的差异是导致不好移植的较大因素但是汇编语言的相关工具链对此也有不可推卸的责任。而源自Plan9的Go汇编语言对此做了一定的改进首先Go汇编语言在相同CPU架构上是完全一致的也就是屏蔽了操作系统的差异同时Go汇编语言将一些基础并且类似的指令抽象为相同名字的伪指令从而减少不同CPU架构下汇编代码的差异当然寄存器名字和数量的差异是一直存在的。本节的目的也是找出一个较小的精简指令集以简化Go汇编语言学习的目的。
X86是一个极其复杂的系统有人统计x86-64中指令有将近一千个之多。不仅仅如此X86中的很多单个指令的功能也非常强大比如有论文证明了仅仅一个MOV指令就可以构成一个图灵完备的系统。以上这是两种极端情况太多的指令和太少的指令都不利于汇编程序的编写。因此我们将尝试精简出一个X86-64指令集以便于Go汇编语言的学习
寄存器是CPU中最重要的资源每个要处理的内存数据原则上需要先放到寄存器中才能由CPU处理同时寄存器中处理完的结果需要再存入内存。X86中除了状态寄存器和指令指令两个特殊的寄存器外还有AX、BX、CX、DX、SI、DI、BP、SP几个通用寄存器。在X86-64中又增加了八个以R8-R15方式命名的通用寄存器。因为历史的原因R0-R7并不是通用寄存器它们只是X87开始引入的MMX指令专有的寄存器。在通用寄存器中BP和SP是两个比较特殊的寄存器其中BP用于记录当前函数帧的开始位置和函数调用相关的指令会隐式地影响SP的值SP则对应当前栈指针的位置和栈相关的指令会隐式地影响SP的值
再次之前我们件简单看下X86和X86-64的结构图
X86是一个极其复杂的系统有人统计x86-64中指令有将近一千个之多。不仅仅如此X86中的很多单个指令的功能也非常强大比如有论文证明了仅仅一个MOV指令就可以构成一个图灵完备的系统。以上这是两种极端情况太多的指令和太少的指令都不利于汇编程序的编写。通用的基础机器指令大概可以分为数据传输指令、算术运算和逻辑运算指令、控制流指令等几类。因此我们将尝试精简出一个X86-64指令集以便于Go汇编语言的学习。
![](../images/ch3-02-x86.jpg)
基础的数据传输指令有MOV、LEA、PUSH、POP等几个。其中MOV指令可以用于将字面值移动到寄存器、字面值移到内存、寄存器之间的数据传输、寄存器和内存之间的数据传输。需要注意的是MOV传输指令的内存操作数只能有一个可以通过某个临时寄存器要达到类似目的。LEA指令将标参数准格式中的内存地址加载到寄存器而不是加载内存位置的内容。PUSH和POP分别是压栈和出栈指令通用寄存器中的SP为栈指针栈是向低地址方向增长的。
![](../images/ch3-02-x86-64.jpg)
基础算术指令有ADD、SUB、MUL、DIV等指令。其中ADD、SUB、MUL、DIV用于加、减、乘、除运算最终结果存入目标寄存器。基础的逻辑运算指令有AND、OR和NOT等几个指令对应逻辑与、或和取反等几个指令。
<!-- TODO: 合并两个图 -->
控制流指令有CMP、JMP-if-x、JMP、CALL、RET等指令。CMP指令用于两个操作数做减法根据比较结果设置状态寄存器的符号位和零位可以用于有条件跳转的跳转条件。JMP-if-x是一组有条件跳转指令常用的有JL、JLZ、JE、JNE、JG、JGE等指令对应小于、小于等于、等于、不等于、大于和大于等于等条件时跳转。JMP指令则对应无条件跳转将要跳转的地址设置到IP指令寄存器就实现了跳转。而CALL和RET指令分别为调用函数和函数返回指令。
其中最重要的部分是指令指针、标题标志和通用寄存器部分。对比可以发现X86和X86-64结构还是比较相似的只是64位CPU的通用寄存器更多一些而已。因为历史原因X86中的前8个通用寄存器并不是以数字顺序命名它们是以AX、BX、CX、DX、SI、DI、BP、SP方式命名。而X86-64新增加的8个通用寄存器则是以R8-R15方式命名。其中R0-R7并不是通用寄存器它们之上X87开始引入的MMX指令专有的寄存器。在通用寄存器中BP和SP是两个比较特殊的寄存器其中BP用于记录当前函数帧的开始位置和函数调用相关的指令会隐式地影响SP的值SP则对应当前栈指针的位置和栈相关的指令会隐式地影响SP的值。
<!-- TODO: 指令表格 -->
我们可以尝试用一个结构体来类比简化的X86结构
为了简单我们省略了位运算指令很多高级指令。完整的X86指令在 https://github.com/golang/arch/blob/master/x86/x86.csv 文件定义。同时Go汇编还正对一些指令定义了别名具体可以参考这里 https://golang.org/src/cmd/internal/obj/x86/anames.go 。
```go
type X86CPU struct {
Flag uint
AX, BX, CX, DX int
SP uintptr
IP uintptr
Memery [...]int
}
```
其中Flag对应计算机的内部状态标志我们一般不会直接读取Flag只有在一些特殊的场景才会需要直接操作比如操作系统中进程切换时保存和恢复状态
TODO
<!--
指令类型:
1. 数据传输: LEA/MOVQ
2. 算术运算: +-*/%
4. 逻辑运算 and or
5. 跳转: JMP, 有条件跳转
6. 函数调用
7. 栈操作
寄存器AX, BX, CX, DX, SP, PC
位运算的本质每个bit是一个独立的元素不会影响其它bit结果可以看作是一个bit数组。
配合if/else可以实现bit置0/1取反。
逻辑运算and 用乘法模拟or 用加法模拟
乘法是加法和for模拟
-->