mirror of
https://github.com/chai2010/advanced-go-programming-book.git
synced 2025-05-24 12:32:21 +00:00
ch3-02: done
This commit is contained in:
parent
72aa19f8a8
commit
81b9e232cd
@ -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)
|
||||
|
@ -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汇编语言的学习。
|
||||
|
||||

|
||||
基础的数据传输指令有MOV、LEA、PUSH、POP等几个。其中MOV指令可以用于将字面值移动到寄存器、字面值移到内存、寄存器之间的数据传输、寄存器和内存之间的数据传输。需要注意的是,MOV传输指令的内存操作数只能有一个,可以通过某个临时寄存器要达到类似目的。LEA指令将标参数准格式中的内存地址加载到寄存器(而不是加载内存位置的内容)。PUSH和POP分别是压栈和出栈指令,通用寄存器中的SP为栈指针,栈是向低地址方向增长的。
|
||||
|
||||

|
||||
基础算术指令有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模拟
|
||||
|
||||
-->
|
||||
|
Loading…
x
Reference in New Issue
Block a user