国产gaysexchina男同gay,japanrcep老熟妇乱子伦视频,吃奶呻吟打开双腿做受动态图,成人色网站,国产av一区二区三区最新精品

Go 語(yǔ)言 內(nèi)部機(jī)制

2023-03-22 14:59 更新

原文鏈接:https://chai2010.cn/advanced-go-programming-book/ch2-cgo/ch2-05-internal.html


2.5 內(nèi)部機(jī)制

對(duì)于剛剛接觸 CGO 用戶來(lái)說(shuō),CGO 的很多特性類似魔法。CGO 特性主要是通過(guò)一個(gè)叫 cgo 的命令行工具來(lái)輔助輸出 Go 和 C 之間的橋接代碼。本節(jié)我們嘗試從生成的代碼分析 Go 語(yǔ)言和 C 語(yǔ)言函數(shù)直接相互調(diào)用的流程。

2.5.1 CGO 生成的中間文件

要了解 CGO 技術(shù)的底層秘密首先需要了解 CGO 生成了哪些中間文件。我們可以在構(gòu)建一個(gè) cgo 包時(shí)增加一個(gè) -work 輸出中間生成文件所在的目錄并且在構(gòu)建完成時(shí)保留中間文件。如果是比較簡(jiǎn)單的 cgo 代碼我們也可以直接通過(guò)手工調(diào)用 go tool cgo 命令來(lái)查看生成的中間文件。

在一個(gè) Go 源文件中,如果出現(xiàn)了 import "C" 指令則表示將調(diào)用 cgo 命令生成對(duì)應(yīng)的中間文件。下圖是 cgo 生成的中間文件的簡(jiǎn)單示意圖:


圖 2-4 cgo 生成的中間文件

包中有 4 個(gè) Go 文件,其中 nocgo 開頭的文件中沒(méi)有 import "C" 指令,其它的 2 個(gè)文件則包含了 cgo 代碼。cgo 命令會(huì)為每個(gè)包含了 cgo 代碼的 Go 文件創(chuàng)建 2 個(gè)中間文件,比如 main.go 會(huì)分別創(chuàng)建 main.cgo1.go 和 main.cgo2.c 兩個(gè)中間文件。然后會(huì)為整個(gè)包創(chuàng)建一個(gè) _cgo_gotypes.go Go 文件,其中包含 Go 語(yǔ)言部分輔助代碼。此外還會(huì)創(chuàng)建一個(gè) _cgo_export.h 和 _cgo_export.c 文件,對(duì)應(yīng) Go 語(yǔ)言導(dǎo)出到 C 語(yǔ)言的類型和函數(shù)。

2.5.2 Go 調(diào)用 C 函數(shù)

Go 調(diào)用 C 函數(shù)是 CGO 最常見的應(yīng)用場(chǎng)景,我們將從最簡(jiǎn)單的例子入手分析 Go 調(diào)用 C 函數(shù)的詳細(xì)流程。

具體代碼如下(main.go):

package main

//int sum(int a, int b) { return a+b; }
import "C"

func main() {
    println(C.sum(1, 1))
}

首先構(gòu)建并運(yùn)行該例子沒(méi)有錯(cuò)誤。然后通過(guò) cgo 命令行工具在_obj 目錄生成中間文件:

$ go tool cgo main.go

查看_obj 目錄生成中間文件:

$ ls _obj | awk '{print $NF}'
_cgo_.o
_cgo_export.c
_cgo_export.h
_cgo_flags
_cgo_gotypes.go
_cgo_main.c
main.cgo1.go
main.cgo2.c

其中 _cgo_.o、_cgo_flags 和 _cgo_main.c 文件和我們的代碼沒(méi)有直接的邏輯關(guān)聯(lián),可以暫時(shí)忽略。

我們先查看 main.cgo1.go 文件,它是 main.go 文件展開虛擬 C 包相關(guān)函數(shù)和變量后的 Go 代碼:

package main

//int sum(int a, int b) { return a+b; }
import _ "unsafe"

func main() {
    println((_Cfunc_sum)(1, 1))
}

其中 C.sum(1, 1) 函數(shù)調(diào)用被替換成了 (_Cfunc_sum)(1, 1)。每一個(gè) C.xxx 形式的函數(shù)都會(huì)被替換為 _Cfunc_xxx 格式的純 Go 函數(shù),其中前綴 _Cfunc_ 表示這是一個(gè) C 函數(shù),對(duì)應(yīng)一個(gè)私有的 Go 橋接函數(shù)。

_Cfunc_sum 函數(shù)在 cgo 生成的 _cgo_gotypes.go 文件中定義: 

//go:cgo_unsafe_args
func _Cfunc_sum(p0 _Ctype_int, p1 _Ctype_int) (r1 _Ctype_int) {
    _cgo_runtime_cgocall(_cgo_506f45f9fa85_Cfunc_sum, uintptr(unsafe.Pointer(&p0)))
    if _Cgo_always_false {
        _Cgo_use(p0)
        _Cgo_use(p1)
    }
    return
}

_Cfunc_sum 函數(shù)的參數(shù)和返回值 _Ctype_int 類型對(duì)應(yīng) C.int 類型,命名的規(guī)則和 _Cfunc_xxx 類似,不同的前綴用于區(qū)分函數(shù)和類型。

其中 _cgo_runtime_cgocall 對(duì)應(yīng) runtime.cgocall 函數(shù),函數(shù)的聲明如下:

func runtime.cgocall(fn, arg unsafe.Pointer) int32

第一個(gè)參數(shù)是 C 語(yǔ)言函數(shù)的地址,第二個(gè)參數(shù)是存放 C 語(yǔ)言函數(shù)對(duì)應(yīng)的參數(shù)結(jié)構(gòu)體的地址。

在這個(gè)例子中,被傳入 C 語(yǔ)言函數(shù) _cgo_506f45f9fa85_Cfunc_sum 也是 cgo 生成的中間函數(shù)。函數(shù)在 main.cgo2.c 定義:

void _cgo_506f45f9fa85_Cfunc_sum(void *v) {
    struct {
        int p0;
        int p1;
        int r;
        char __pad12[4];
    } __attribute__((__packed__)) *a = v;
    char *stktop = _cgo_topofstack();
    __typeof__(a->r) r;
    _cgo_tsan_acquire();
    r = sum(a->p0, a->p1);
    _cgo_tsan_release();
    a = (void*)((char*)a + (_cgo_topofstack() - stktop));
    a->r = r;
}

這個(gè)函數(shù)參數(shù)只有一個(gè) void 泛型的指針,函數(shù)沒(méi)有返回值。真實(shí)的 sum 函數(shù)的函數(shù)參數(shù)和返回值均通過(guò)唯一的參數(shù)指針類實(shí)現(xiàn)。

_cgo_506f45f9fa85_Cfunc_sum 函數(shù)的指針指向的結(jié)構(gòu)為:

    struct {
        int p0;
        int p1;
        int r;
        char __pad12[4];
    } __attribute__((__packed__)) *a = v;

其中 p0 成員對(duì)應(yīng) sum 的第一個(gè)參數(shù),p1 成員對(duì)應(yīng) sum 的第二個(gè)參數(shù),r 成員,__pad12 用于填充結(jié)構(gòu)體保證對(duì)齊 CPU 機(jī)器字的整倍數(shù)。

然后從參數(shù)指向的結(jié)構(gòu)體獲取調(diào)用參數(shù)后開始調(diào)用真實(shí)的 C 語(yǔ)言版 sum 函數(shù),并且將返回值保持到結(jié)構(gòu)體內(nèi)返回值對(duì)應(yīng)的成員。

因?yàn)?Go 語(yǔ)言和 C 語(yǔ)言有著不同的內(nèi)存模型和函數(shù)調(diào)用規(guī)范。其中 _cgo_topofstack 函數(shù)相關(guān)的代碼用于 C 函數(shù)調(diào)用后恢復(fù)調(diào)用棧。_cgo_tsan_acquire 和 _cgo_tsan_release 則是用于掃描 CGO 相關(guān)的函數(shù)則是對(duì) CGO 相關(guān)函數(shù)的指針做相關(guān)檢查。

C.sum 的整個(gè)調(diào)用流程圖如下:


圖 2-5 調(diào)用 C 函數(shù)

其中 runtime.cgocall 函數(shù)是實(shí)現(xiàn) Go 語(yǔ)言到 C 語(yǔ)言函數(shù)跨界調(diào)用的關(guān)鍵。更詳細(xì)的細(xì)節(jié)可以參考 https://golang.org/src/cmd/cgo/doc.go 內(nèi)部的代碼注釋和 runtime.cgocall 函數(shù)的實(shí)現(xiàn)。

2.5.3 C 調(diào)用 Go 函數(shù)

在簡(jiǎn)單分析了 Go 調(diào)用 C 函數(shù)的流程后,我們現(xiàn)在來(lái)分析 C 反向調(diào)用 Go 函數(shù)的流程。同樣,我們現(xiàn)構(gòu)造一個(gè) Go 語(yǔ)言版本的 sum 函數(shù),文件名同樣為 main.go

package main

//int sum(int a, int b);
import "C"

//export sum
func sum(a, b C.int) C.int {
    return a + b
}

func main() {}

CGO 的語(yǔ)法細(xì)節(jié)不再贅述。為了在 C 語(yǔ)言中使用 sum 函數(shù),我們需要將 Go 代碼編譯為一個(gè) C 靜態(tài)庫(kù):

$ go build -buildmode=c-archive -o sum.a main.go

如果沒(méi)有錯(cuò)誤的話,以上編譯命令將生成一個(gè) sum.a 靜態(tài)庫(kù)和 sum.h 頭文件。其中 sum.h 頭文件將包含 sum 函數(shù)的聲明,靜態(tài)庫(kù)中將包含 sum 函數(shù)的實(shí)現(xiàn)。

要分析生成的 C 語(yǔ)言版 sum 函數(shù)的調(diào)用流程,同樣需要分析 cgo 生成的中間文件:

$ go tool cgo main.go

_obj 目錄還是生成類似的中間文件。為了查看方便,我們刻意忽略了無(wú)關(guān)的幾個(gè)文件:

$ ls _obj | awk '{print $NF}'
_cgo_export.c
_cgo_export.h
_cgo_gotypes.go
main.cgo1.go
main.cgo2.c

其中 _cgo_export.h 文件的內(nèi)容和生成 C 靜態(tài)庫(kù)時(shí)產(chǎn)生的 sum.h 頭文件是同一個(gè)文件,里面同樣包含 sum 函數(shù)的聲明。

既然 C 語(yǔ)言是主調(diào)用者,我們需要先從 C 語(yǔ)言版 sum 函數(shù)的實(shí)現(xiàn)開始分析。C 語(yǔ)言版本的 sum 函數(shù)在生成的 _cgo_export.c 文件中(該文件包含的是 Go 語(yǔ)言導(dǎo)出函數(shù)對(duì)應(yīng)的 C 語(yǔ)言函數(shù)實(shí)現(xiàn)):

int sum(int p0, int p1)
{
    __SIZE_TYPE__ _cgo_ctxt = _cgo_wait_runtime_init_done();
    struct {
        int p0;
        int p1;
        int r0;
        char __pad0[4];
    } __attribute__((__packed__)) a;
    a.p0 = p0;
    a.p1 = p1;
    _cgo_tsan_release();
    crosscall2(_cgoexp_8313eaf44386_sum, &a, 16, _cgo_ctxt);
    _cgo_tsan_acquire();
    _cgo_release_context(_cgo_ctxt);
    return a.r0;
}

sum 函數(shù)的內(nèi)容采用和前面類似的技術(shù),將 sum 函數(shù)的參數(shù)和返回值打包到一個(gè)結(jié)構(gòu)體中,然后通過(guò) runtime/cgo.crosscall2 函數(shù)將結(jié)構(gòu)體傳給 _cgoexp_8313eaf44386_sum 函數(shù)執(zhí)行。

runtime/cgo.crosscall2 函數(shù)采用匯編語(yǔ)言實(shí)現(xiàn),它對(duì)應(yīng)的函數(shù)聲明如下:

func runtime/cgo.crosscall2(
    fn func(a unsafe.Pointer, n int32, ctxt uintptr),
    a unsafe.Pointer, n int32,
    ctxt uintptr,
)

其中關(guān)鍵的是 fn 和 a,fn 是中間代理函數(shù)的指針,a 是對(duì)應(yīng)調(diào)用參數(shù)和返回值的結(jié)構(gòu)體指針。

中間的 _cgoexp_8313eaf44386_sum 代理函數(shù)在 _cgo_gotypes.go 文件:

func _cgoexp_8313eaf44386_sum(a unsafe.Pointer, n int32, ctxt uintptr) {
    fn := _cgoexpwrap_8313eaf44386_sum
    _cgo_runtime_cgocallback(**(**unsafe.Pointer)(unsafe.Pointer(&fn)), a, uintptr(n), ctxt);
}

func _cgoexpwrap_8313eaf44386_sum(p0 _Ctype_int, p1 _Ctype_int) (r0 _Ctype_int) {
    return sum(p0, p1)
}

內(nèi)部將 sum 的包裝函數(shù) _cgoexpwrap_8313eaf44386_sum 作為函數(shù)指針,然后由 _cgo_runtime_cgocallback 函數(shù)完成 C 語(yǔ)言到 Go 函數(shù)的回調(diào)工作。

_cgo_runtime_cgocallback 函數(shù)對(duì)應(yīng) runtime.cgocallback 函數(shù),函數(shù)的類型如下:

func runtime.cgocallback(fn, frame unsafe.Pointer, framesize, ctxt uintptr)

參數(shù)分別是函數(shù)指針,函數(shù)參數(shù)和返回值對(duì)應(yīng)結(jié)構(gòu)體的指針,函數(shù)調(diào)用幀大小和上下文參數(shù)。

整個(gè)調(diào)用流程圖如下:


圖 2-6 調(diào)用導(dǎo)出的 Go 函數(shù)

其中 runtime.cgocallback 函數(shù)是實(shí)現(xiàn) C 語(yǔ)言到 Go 語(yǔ)言函數(shù)跨界調(diào)用的關(guān)鍵。更詳細(xì)的細(xì)節(jié)可以參考相關(guān)函數(shù)的實(shí)現(xiàn)。



以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)