真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

golang中cgo的示例分析

這篇文章主要為大家展示了“golang中cgo的示例分析”,內(nèi)容簡(jiǎn)而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“golang中cgo的示例分析”這篇文章吧。

成都創(chuàng)新互聯(lián)主營(yíng)陽東網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營(yíng)網(wǎng)站建設(shè)方案,成都app開發(fā),陽東h5微信平臺(tái)小程序開發(fā)搭建,陽東網(wǎng)站營(yíng)銷推廣歡迎陽東等地區(qū)企業(yè)咨詢

GO調(diào)C基本原理CGO是實(shí)現(xiàn)Go與C互操作的方式,包括Go調(diào)C和C調(diào)Go兩個(gè)過程。其中Go調(diào)C的過程比較簡(jiǎn)單。對(duì)于一個(gè)在C中定義的函數(shù)add3,在Go中調(diào)用時(shí)需要顯式的使用C.add3調(diào)用。其中C是在程序中引入的一個(gè)偽包

代碼中的import “C”即為在Go中使用的偽包。這個(gè)包并不真實(shí)存在,也不會(huì)被Go的compile組件見到,它會(huì)在編譯前被CGO工具捕捉到,并做一些代碼的改寫和樁文件的生成。

CGO 提供了 golang 和 C 語言相互調(diào)用的機(jī)制。某些第三方庫可能只有 C/C++ 的實(shí)現(xiàn),完全用純 golang 的實(shí)現(xiàn)可能工程浩大,這時(shí)候 CGO 就派上用場(chǎng)了??梢酝?CGO 在 golang 在調(diào)用 C 的接口,C++ 的接口可以用 C 包裝一下提供給 golang 調(diào)用。被調(diào)用的 C 代碼可以直接以源代碼形式提供或者打包靜態(tài)庫或動(dòng)態(tài)庫在編譯時(shí)鏈接。推薦使用靜態(tài)庫的方式,這樣方便代碼隔離,編譯的二進(jìn)制也沒有動(dòng)態(tài)庫依賴方便發(fā)布也符合 golang 的哲學(xué)。

基本數(shù)值類型

golang 的基本數(shù)值類型內(nèi)存模型和 C 語言一樣,就是連續(xù)的幾個(gè)字節(jié)(1 / 2 / 4 / 8 字節(jié))。因此傳遞數(shù)值類型時(shí)可以直接將 golang 的基本數(shù)值類型轉(zhuǎn)換成對(duì)應(yīng)的 CGO 類型然后傳遞給 C 函數(shù)調(diào)用,反之亦然:

package main

/*

#include

static int32_t add(int32_t a, int32_t b) {

    return a + b;

}

*/

import "C"

import "fmt"

func main() {

    var a, b int32 = 1, 2

    var c int32 = int32(C.add(C.int32_t(a), C.int32_t(b)))

    fmt.Println(c) // 3

}

golang 和 C 的基本數(shù)值類型轉(zhuǎn)換對(duì)照表如下:

C語言類型 CGO類型 Go語言類型

char C.char byte

singed char C.schar int8

unsigned char C.uchar uint8

short C.short int16

unsigned short C.ushort uint16

int C.int int32

unsigned int C.uint uint32

long C.long int32

unsigned long C.ulong uint32

long long int C.longlong int64

unsigned long long int C.ulonglong uint64

float C.float float32

double C.double float64

size_t C.size_t uint

注意 C 中的整形比如 int 在標(biāo)準(zhǔn)中是沒有定義具體字長(zhǎng)的,但一般默認(rèn)認(rèn)為是 4 字節(jié),對(duì)應(yīng) CGO 類型中 C.int 則明確定義了字長(zhǎng)是 4 ,但 golang 中的 int 字長(zhǎng)則是 8 ,因此對(duì)應(yīng)的 golang 類型不是 int 而是 int32 。為了避免誤用,C 代碼最好使用 C99 標(biāo)準(zhǔn)的數(shù)值類型

golang 中切片用起來有點(diǎn)像 C 中的數(shù)組,但實(shí)際的內(nèi)存模型還是有點(diǎn)區(qū)別的。C 中的數(shù)組就是一段連續(xù)的內(nèi)存,數(shù)組的值實(shí)際上就是這段內(nèi)存的首地址。golang 切片的內(nèi)存模型如下所示(參考源碼 $GOROOT/src/runtime/chan.go

array len cap

  |

  v 

data

由于底層內(nèi)存模型的差異,不能直接將 golang 切片的指針傳給 C 函數(shù)調(diào)用,而是需要將存儲(chǔ)切片數(shù)據(jù)的內(nèi)部緩沖區(qū)的首地址及切片長(zhǎng)度取出傳傳遞:

package main

/*

#include

static void fill_255(char* buf, int32_t len) {

    int32_t i;

    for (i = 0; i < len; i++) {

        buf[i] = 255;

    }

}

*/

import "C"

import (

    "fmt"

    "unsafe"

)

func main() {

    b := make([]byte, 5)

    fmt.Println(b) // [0 0 0 0 0]

    C.fill_255((*C.char)(unsafe.Pointer(&b[0])), C.int32_t(len(b)))

    fmt.Println(b) // [255 255 255 255 255]

}

字符串

golang 的字符串和 C 中的字符串在底層的內(nèi)存模型也是不一樣的:

golang 字串符串并沒有用 '\0' 終止符標(biāo)識(shí)字符串的結(jié)束,因此直接將 golang 字符串底層數(shù)據(jù)指針傳遞給 C 函數(shù)是不行的。一種方案類似切片的傳遞一樣將字符串?dāng)?shù)據(jù)指針和長(zhǎng)度傳遞給 C 函數(shù)后,C 函數(shù)實(shí)現(xiàn)中自行申請(qǐng)一段內(nèi)存拷貝字符串?dāng)?shù)據(jù)然后加上未層終止符后再使用。更好的方案是使用標(biāo)準(zhǔn)庫提供的 C.CString() 將 golang 的字符串轉(zhuǎn)換成 C 字符串然后傳遞給 C 函數(shù)調(diào)用:

package main

/*

#include

#include

#include

static char* cat(char* str1, char* str2) {

    static char buf[256];

    strcpy(buf, str1);

    strcat(buf, str2);

    return buf;

}

*/

import "C"

import (

"fmt"

"unsafe"

)

func main() {

str1, str2 := "hello", " world"

// golang string -> c string

cstr1, cstr2 := C.CString(str1), C.CString(str2)

defer C.free(unsafe.Pointer(cstr1)) // must call

defer C.free(unsafe.Pointer(cstr2))

cstr3 := C.cat(cstr1, cstr2)

// c string -> golang string

str3 := C.GoString(cstr3)

fmt.Println(str3) // "hello world"

}

需要注意的是 C.CString() 返回的 C 字符串是在堆上新創(chuàng)建的并且不受 GC 的管理,使用完后需要自行調(diào)用 C.free() 釋放,否則會(huì)造成內(nèi)存泄露,而且這種內(nèi)存泄露用前文中介紹的 pprof 也定位不出來。

其他類型

golang 中其他類型(比如 map) 在 C/C++ 中并沒有對(duì)等的類型或者內(nèi)存模型也不一樣。傳遞的時(shí)候需要了解 golang 類型的底層內(nèi)存模型,然后進(jìn)行比較精細(xì)的內(nèi)存拷貝操作。傳遞 map 的一種方案是可以把 map 的所有鍵值對(duì)放到切片里,然后把切片傳遞給 C++ 函數(shù),C++ 函數(shù)再還原成 C++ 標(biāo)準(zhǔn)庫的 map 。由于使用場(chǎng)景比較少,這里就不贅述了。

總結(jié)

本文主要介紹了在 golang 中使用 CGO 調(diào)用 C/C++ 接口涉及的一些細(xì)節(jié)問題。C/C++ 比較底層的語言,需要自己管理內(nèi)存。使用 CGO 時(shí)需要對(duì) golang 底層的內(nèi)存模型有所了解。另外 goroutine 通過 CGO 進(jìn)入到 C 接口的執(zhí)行階段后,已經(jīng)脫離了 golang 運(yùn)行時(shí)的調(diào)度并且會(huì)獨(dú)占線程,此時(shí)實(shí)際上變成了多線程同步的編程模型。如果 C 接口里有阻塞操作,這時(shí)候可能會(huì)導(dǎo)致所有線程都處于阻塞狀態(tài),其他 goroutine 沒有機(jī)會(huì)得到調(diào)度,最終導(dǎo)致整個(gè)系統(tǒng)的性能大大較低。總的來說,只有在第三方庫沒有 golang 的實(shí)現(xiàn)并且實(shí)現(xiàn)起來成本比較高的情況下才需要考慮使用 CGO ,否則慎用。

可以使用go tool cgo在本地目錄生成這些樁文件

$go tool cgo main.go

.

|_____obj

| |_____cgo_.o

| |_____cgo_export.c

| |_____cgo_export.h

| |_____cgo_flags

| |_____cgo_gotypes.go

| |_____cgo_main.c

| |____main.cgo1.go

| |____main.cgo2.c

|____main.go

其中main.cgo1.go為主要文件,是用戶代碼main.go被cgo改寫之后的文件:

$cat _obj/main.cgo1.go

// Created by cgo - DO NOT EDIT

//line /Users/didi/goLang/src/github.com/xiazemin/cgo/exp1/main.go:1

package main

//line /Users/didi/goLang/src/github.com/xiazemin/cgo/exp1/main.go:11

import "fmt"

func main() {

var a, b int32 = 1, 2

var c int32 = int32(_Cfunc_add(_Ctype_int32_t(a), _Ctype_int32_t(b)))

fmt.Println(c)

}

這個(gè)文件才是Go的compile組件真正看到的用戶代碼??梢钥吹皆瓉砦募械膇mport “C”被去掉,而用戶寫的C.int被改寫為_Ctype_int,C.add3被改寫為_Cfunc_add3。關(guān)于這個(gè)特性有兩個(gè)點(diǎn)需要注意。一是在有import “C”的文件中,用戶的注釋信息全部丟失,使用的一些progma也不例外。二是在testing套件中import “C”不允許使用,表現(xiàn)為testing不支持CGO。但并不是沒有辦法在testing中使用CGO,可以利用上面的特性,在另外一個(gè)獨(dú)立的Go文件中定義C函數(shù),并使用import “C”;但是在使用testing的Go文件中直接使用_Cfunc_add3函數(shù)即可。_Cfunc_add3用戶雖然沒有顯示定義,但是CGO自動(dòng)產(chǎn)生了這一函數(shù)的定義。上面一系列的//line編譯制導(dǎo)語句用做關(guān)聯(lián)生成的Go與原來的用戶代碼的行號(hào)信息。

再次回到_Cfunc_add3函數(shù),并不是C中的add3函數(shù),是CGO產(chǎn)生的一個(gè)Go函數(shù)。它的定義在CGO產(chǎn)生的樁文件_cgo_gotypes.go中

$cat _obj/_cgo_gotypes.go

// Created by cgo - DO NOT EDIT

package main

import "unsafe"

import _ "runtime/cgo"

import "syscall"

var _ syscall.Errno

func _Cgo_ptr(ptr unsafe.Pointer) unsafe.Pointer { return ptr }

//go:linkname _Cgo_always_false runtime.cgoAlwaysFalse

var _Cgo_always_false bool

//go:linkname _Cgo_use runtime.cgoUse

func _Cgo_use(interface{})

type _Ctype_int int32

type _Ctype_int32_t _Ctype_int

type _Ctype_void [0]byte

//go:linkname _cgo_runtime_cgocall runtime.cgocall

func _cgo_runtime_cgocall(unsafe.Pointer, uintptr) int32

//go:linkname _cgo_runtime_cgocallback runtime.cgocallback

func _cgo_runtime_cgocallback(unsafe.Pointer, unsafe.Pointer, uintptr, uintptr)

//go:linkname _cgoCheckPointer runtime.cgoCheckPointer

func _cgoCheckPointer(interface{}, ...interface{})

//go:linkname _cgoCheckResult runtime.cgoCheckResult

func _cgoCheckResult(interface{})

//go:cgo_import_static _cgo_3a42ad434848_Cfunc_add

//go:linkname __cgofn__cgo_3a42ad434848_Cfunc_add _cgo_3a42ad434848_Cfunc_add

var __cgofn__cgo_3a42ad434848_Cfunc_add byte

var _cgo_3a42ad434848_Cfunc_add = unsafe.Pointer(&__cgofn__cgo_3a42ad434848_Cfunc_add)

//go:cgo_unsafe_args

func _Cfunc_add(p0 _Ctype_int32_t, p1 _Ctype_int32_t) (r1 _Ctype_int32_t) {

_cgo_runtime_cgocall(_cgo_3a42ad434848_Cfunc_add, uintptr(unsafe.Pointer(&p0)))

if _Cgo_always_false {

_Cgo_use(p0)

_Cgo_use(p1)

}

return

}

_Cfunc_add3的參數(shù)傳遞與正常的函數(shù)有些不同,其參數(shù)并不在棧上,而是在堆上。函數(shù)中的_Cgo_use,其實(shí)是runtime.cgoUse,用來告訴編譯器要把p0, p1, p2逃逸到堆上去,這樣才能較為安全的把參數(shù)傳遞到C的程序中去。(因?yàn)間o是動(dòng)態(tài)棧不安全)

$    go build -gcflags "-m" main.go

# command-line-arguments

/var/folders/r9/35q9g3d56_d9g0v59w9x2l9w0000gn/T/go-build789689150/command-line-arguments/_obj/_cgo_gotypes.go:14:6: can inline _Cgo_ptr

/var/folders/r9/35q9g3d56_d9g0v59w9x2l9w0000gn/T/go-build789689150/command-line-arguments/_obj/_cgo_gotypes.go:14:35: leaking param: ptr to result ~r1 level=0

/var/folders/r9/35q9g3d56_d9g0v59w9x2l9w0000gn/T/go-build789689150/command-line-arguments/_obj/_cgo_gotypes.go:27:6: _cgo_runtime_cgocall assuming arg#2 is unsafe uintptr

/var/folders/r9/35q9g3d56_d9g0v59w9x2l9w0000gn/T/go-build789689150/command-line-arguments/_obj/_cgo_gotypes.go:30:6: _cgo_runtime_cgocallback assuming arg#3 is unsafe uintptr

/var/folders/r9/35q9g3d56_d9g0v59w9x2l9w0000gn/T/go-build789689150/command-line-arguments/_obj/_cgo_gotypes.go:30:6: _cgo_runtime_cgocallback assuming arg#4 is unsafe uintptr

/var/folders/r9/35q9g3d56_d9g0v59w9x2l9w0000gn/T/go-build789689150/command-line-arguments/_obj/_cgo_gotypes.go:47:11: p0 escapes to heap

/var/folders/r9/35q9g3d56_d9g0v59w9x2l9w0000gn/T/go-build789689150/command-line-arguments/_obj/_cgo_gotypes.go:48:11: p1 escapes to heap

/var/folders/r9/35q9g3d56_d9g0v59w9x2l9w0000gn/T/go-build789689150/command-line-arguments/_obj/_cgo_gotypes.go:45:75: _Cfunc_add &p0 does not escape

./main.go:16: c escapes to heap

./main.go:16: main ... argument does not escape

函數(shù)中的__cgo_79f22807c129_Cfunc_add3是一個(gè)變量,記錄了一個(gè)C函數(shù)的地址(注意,這并不是實(shí)際要調(diào)用add3函數(shù)),是一個(gè)真正定義在C程序中的函數(shù)。在Go中,通過編譯制導(dǎo)語句//go:cgo_import_static在鏈接時(shí)拿到C中函數(shù)__cgo_79f22807c129_Cfunc_add3的地址,然后通過編譯制導(dǎo)語句//go:linkname把這個(gè)函數(shù)地址與Go中的byte型變量__cgofn_cgo_79f22807c129_Cfunc_add3的地址對(duì)齊在一起。之后再利用一個(gè)新的變量__cgo_79f22807c129_Cfunc_add3記錄這個(gè)byte型變量的地址。從而可以實(shí)現(xiàn)在Go中拿到C中函數(shù)的地址。做完,這些之后把C的函數(shù)地址和參數(shù)地址傳給cgocall函數(shù),進(jìn)行Go與C之間call ABI操作。當(dāng)然,cgocall里面會(huì)做一些調(diào)度相關(guān)的準(zhǔn)備動(dòng)作,后面有詳細(xì)說明。

__cgo_79f22807c129_Cfunc_add3如上文所述,是定義在main.cgo2.c中的一個(gè)函數(shù),其定義如下:

CGO_NO_SANITIZE_THREAD

void

_cgo_3a42ad434848_Cfunc_add(void *v)

{

struct {

int32_t p0;

int32_t p1;

int32_t r;

char __pad12[4];

} __attribute__((__packed__)) *a = v;

char *stktop = _cgo_topofstack();

__typeof__(a->r) r;

_cgo_tsan_acquire();

r = add(a->p0, a->p1);

_cgo_tsan_release();

a = (void*)((char*)a + (_cgo_topofstack() - stktop));

a->r = r;

}

在這個(gè)函數(shù)的定義中,并沒有顯式的參數(shù)拷貝;而是利用類型強(qiáng)轉(zhuǎn),在C中直接操作Go傳遞過來的參數(shù)地址。在這個(gè)函數(shù)中真正調(diào)用了用戶定義的add3函數(shù)。

            cgocall即_Cfunc_add3中的_cgo_runtime_cgocall函數(shù),是runtime中的一個(gè)從Go調(diào)C的關(guān)鍵函數(shù)。這個(gè)函數(shù)里面做了一些調(diào)度相關(guān)的安排。之所以有這樣的設(shè)計(jì),是因?yàn)镚o調(diào)入C之后,程序的運(yùn)行不受Go的runtime的管控。一個(gè)正常的Go函數(shù)是需要runtime的管控的,即函數(shù)的運(yùn)行時(shí)間過長(zhǎng)會(huì)導(dǎo)致goroutine的搶占,以及GC的執(zhí)行會(huì)導(dǎo)致所有的goroutine被拉齊。C程序的執(zhí)行,限制了Go的runtime的調(diào)度行為。為此,Go的runtime會(huì)在進(jìn)入到C程序之后,會(huì)標(biāo)記這個(gè)運(yùn)行C的線程排除在runtime的調(diào)度之后,以減少這個(gè)線程對(duì)Go的調(diào)度的影響。此外,由于正常的Go程序運(yùn)行在一個(gè)2K的棧上,而C程序需要一個(gè)無窮大的棧。這樣的設(shè)計(jì)會(huì)導(dǎo)致在Go的棧上執(zhí)行C函數(shù)會(huì)導(dǎo)致棧的溢出,因此在進(jìn)去C函數(shù)之前需要把當(dāng)前線程的棧從2K的棧切換到線程本身的系統(tǒng)棧上。棧切換發(fā)生在asmcgocall中,而線程的狀態(tài)標(biāo)記發(fā)生在cgocall中。

以上是“golang中cgo的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!


網(wǎng)站欄目:golang中cgo的示例分析
鏈接分享:http://weahome.cn/article/pjdpco.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部