深入Go底層原理,重寫Redis中介軟體實戰

語言: CN / TW / HK

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)則表示返回值的位置。

有疑問加站長微信聯絡(非本文作者)