深入Go底层原理,重写Redis中间件实战

lkmeg · · 2292 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

 

download百度网盘

什么是plan9汇编

我们知道,CPU是只认二进制指令的,也就是一串的0101;人类无法记住这些二进制码,于是发明了汇编语言。汇编语言实际上是二进制指令的文本形式,它与指令可以一一对应。

每一种CPU指令都是不一样的,因此对应的汇编语言也就不一样。人类写完汇编语言后,把它转换成二进制码,就可以被机器执行了。转换的动作由编译器完成。

Go语言的编译器和汇编器都带了一个-S参数,可以查看生成的最终目标代码。通过对比目标代码和原始的Go语言或Go汇编语言代码的差异可以加深对底层实现的理解。

Go汇编语言实际上来源于plan9汇编语言,而plan9汇编语言最初来源于Go语言作者之一的Ken Thompson为plan9系统所写的C语言编译器输出的汇编伪代码。这里强烈推荐一下春晖大神的新书《Go语言高级编程》,即将上市,电子版的点击阅读原文可以看到地址,书中有一整个章节讲Go的汇编语言,非常精彩!

理解Go的汇编语言,哪怕只是一点点,都能对Go的运行机制有更深入的理解。比如我们以前讲的defer,如果从Go源码编译后的汇编代码来看,就能深刻地掌握它的底层原理。再比如,很多文章都会分析Go的函数参数传递都是值传递,如果把汇编代码秀出来,很容易就能得出结论。

汇编角度看函数调用及返回过程

假设我们有一个这样年幼无知的例子,求两个int的和,Go源码如下:

package main

func main() {
    _ = add(3,5)
}

func add(a, b int) int {
    return a+b
}

使用如下命令得到汇编代码:

go tool compile -S main.go

go tool compile命令用于调用Go语言提供的底层命令工具,其中-S参数表示输出汇编格式。

我们现在只关心add函数的汇编代码:

"".add STEXT nosplit size=19 args=0x18 locals=0x0
        0x0000 00000 (main.go:7)        TEXT    "".add(SB), NOSPLIT, $0-24
        0x0000 00000 (main.go:7)        FUNCDATA        $0, gclocals·54241e171da8af6ae173d69da0236748(SB)
        0x0000 00000 (main.go:7)        FUNCDATA        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0000 00000 (main.go:7)        MOVQ    "".b+16(SP), AX
        0x0005 00005 (main.go:7)        MOVQ    "".a+8(SP), CX
        0x000a 00010 (main.go:8)        ADDQ    CX, AX
        0x000d 00013 (main.go:8)        MOVQ    AX, "".~r2+24(SP)
        0x0012 00018 (main.go:8)        RET

看不懂没关系,我目前也不是全部都懂,但是对于理解一个函数调用的整体过程而言,足够了。

0x0000 00000 (main.go:7)        TEXT    "".add(SB), NOSPLIT, $0-24

这一行表示定义add这个函数,最后的数字$0-24,其中0表示函数栈帧大小为0;24表示参数及返回值的大小:参数是2个int型变量,返回值是1个int型变量,共24字节。

再看中间这四行:

0x0000 00000 (main.go:7)        MOVQ    "".b+16(SP), AX
        0x0005 00005 (main.go:7)        MOVQ    "".a+8(SP), CX
        0x000a 00010 (main.go:8)        ADDQ    CX, AX
        0x000d 00013 (main.go:8)        MOVQ    AX, "".~r2+24(SP)

代码片段中的第1行,将第2个参数b搬到AX寄存器;第2行将1个参数a搬到寄存器CX;第3行将ab相加,相加的结果搬到AX;最后一行,将结果搬到返回参数的地址,这段汇编代码非常简单,来看一下函数调用者和被调者的栈帧图:

(SP)指栈顶,b+16(SP)表示裸骑1的位置,从SP往上增加16个字节,注意,前面的b仅表示一个标号;同样,a+8(SP)表示实参0;~r2+24(SP)则表示返回值的位置。


有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

2292 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传