W3Cschool
恭喜您成為首批注冊用戶
獲得88經驗值獎勵
cgo不僅僅支持從Go調用C,它還同樣支持從C中調用Go的函數(shù),雖然這種情況相對前者較少使用。
//export GoF
func GoF(arg1, arg2 int, arg3 string) int64 {
}
使用export標記可以將Go函數(shù)導出提供給C調用:
extern int64 GoF(int arg1, int arg2, GoString arg3);
下面讓我們看看它是如何實現(xiàn)的。假定上面的函數(shù)GoF是在Go語言的一個包p內的,為了能夠讓gcc編譯的C代碼調用Go的函數(shù)p.GoF,cgo生成下面一個函數(shù):
GoInt64 GoF(GoInt p0, GoInt p1, GoString p2)
{
struct {
GoInt p0;
GoInt p1;
GoString p2;
GoInt64 r0;
} __attribute__((packed)) a;
a.p0 = p0;
a.p1 = p1;
a.p2 = p2;
crosscall2(_cgoexp_95935062f5b1_GoF, &a, 40);
return a.r0;
}
這個函數(shù)由cgo生成,提供給gcc編譯。函數(shù)名不是p.GoF,因為gcc沒有包的概念。由gcc編譯的C函數(shù)可以調用這個GoF函數(shù)。
GoF調用crosscall2(_cgoexp_GoF, frame, framesize)。crosscall2是用匯編代碼實現(xiàn)的,它是一個兩參數(shù)的適配器,作用是從gcc函數(shù)調用6c函數(shù)(6c和gcc使用的調用協(xié)議還是有些區(qū)別的)。crosscall2實現(xiàn)了從一個ABI的gcc函數(shù)調用,到6c的函數(shù)調用ABI。所以上面代碼中實際上相當于調用_cgoexp_GoF(frame,framesize)。注意此時是仍然運行在mg的g0棧并且不受GOMAXPROCS限制的。因此,這個代碼不能直接調用任意的Go代碼并且不能分配內存或者用盡m->g0的棧。
_cgoexp_GoF調用runtime.cgocallback(p.GoF, frame, framesize):
#pragma textflag 7
void
_cgoexp_95935062f5b1_GoF(void *a, int32 n)
{
runtime·cgocallback(·GoF, a, n);
}
這個函數(shù)是由6c編譯的,而不是gcc,因此可以引用到比如runtime.cgocallback和p.GoF這種名字。
runtime·cgocallback也是一個用匯編實現(xiàn)的函數(shù)。它從m->g0的棧切換回原來的goroutine的棧,并在這個棧中調用runtime.cgocallbackg(p.GoF, frame, framesize)。
這中間會涉及到一些保存棧寄存器之類的細節(jié)操作比較復雜。因為這個過程相當于我們接管了m->curg的執(zhí)行,但是卻并沒有完全恢復到之前的運行環(huán)境(只是借m->curg這個goroutine運行Go代碼),所以我們需要保存當前環(huán)境到以便之后再次返回到m->g0棧。
好了,runtime.cgocallbackg現(xiàn)在是運行在一個真實的goroutine棧中(不是m->g0棧)。不過現(xiàn)在我們只是切換到了goroutine棧,此刻還是處于syscall狀態(tài)的。因此這個函數(shù)會先調用runtime.exitsyscall,接著才是執(zhí)行Go代碼。當它調用runtime.exitsyscall,這會阻塞這條goroutine直到滿足$GOMAXPROCS限制條件。一旦從exitsyscall返回,則可以安全地執(zhí)行像調用內存分配或者是調用Go的回調函數(shù)p.GoF。
void
runtime·cgocallbackg(FuncVal *fn, void *arg, uintptr argsize)
{
runtime·exitsyscall(); // coming out of cgo call
// Invoke callback.
reflect·call(fn, arg, argsize);
runtime·entersyscall(); // going back to cgo call
}
后面的過程就不用分析了,跟前面的過程是一個正好相反的過程。在runtime.cgocallback重獲控制權之后,它切換回m->g0棧,從棧中恢復之前的m->g0.sched.sp值,然后返回到_cgoexp_GoF。_cgoexp_GoF立即返回到crosscall2,它會恢復被調者為gcc保存的寄存器并返回到GoF,最后返回到C的調用函數(shù)中。
無論是Go調用C,還是C調用Go,其需要解決的核心問題其實都是提供一個C/Go的運行環(huán)境來執(zhí)行相應的代碼。Go的代碼執(zhí)行環(huán)境就是goroutine以及Go的runtime,而C的執(zhí)行環(huán)境需要一個不使用分段的棧,并且執(zhí)行C代碼的goroutine需要暫時地脫離調度器的管理。要達到這些要求,運行時提供的支持就是切換棧,以及runtime.entersyscall。
在Go中調用C函數(shù)時,runtime.cgocall中調用entersyscall脫離調度器管理。runtime.asmcgocall切換到m的g0棧,于是得到C的運行環(huán)境。
在C中調用Go函數(shù)時,crosscall2解決gcc編譯到6c編譯之間的調用協(xié)議問題。cgocallback切換回goroutine棧。runtime.cgocallbackg中調用exitsyscall恢復Go的運行環(huán)境。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: