Go語言 Go調(diào)用C

2018-07-25 16:23 更新

從這里開始,將深入挖掘關(guān)于運行時庫部分對于cgo的支持。還記得前面那個test.go嗎?這里將繼續(xù)以它為例子進行分析。

從Go中調(diào)用C的函數(shù)test,cgo生成的代碼調(diào)用是runtime.cgocall(_cgo_Cfunc_test, frame):

void
·_Cfunc_test(struct{uint8 x[8];}p)
{
    runtime·cgocall(_cgo_1b9ecf7f7656_Cfunc_test, &p);
}

其中cgocall的第一個參數(shù)_cgo_Cfunc_test是一個由cgo生成并由gcc編譯的函數(shù):

void
_cgo_1b9ecf7f7656_Cfunc_test(void *v)
{
    struct {
        int p0;
        char __pad4[4];
    } __attribute__((__packed__)) *a = v;
    test(a->p0);
}

runtime.cgocall將g鎖定到m,調(diào)用entersyscall,這樣不會阻塞其它的goroutine或者垃圾回收,然后調(diào)用runtime.asmcgocall(_cgo_Cfunc_test, frame)。

void
runtime·cgocall(void (*fn)(void*), void *arg)
{
    runtime·lockOSThread();
    runtime·entersyscall();
    runtime·asmcgocall(fn, arg);
    runtime·exitsyscall();

    endcgo();
}

將g鎖定到m是保證如果在cgo內(nèi)又回調(diào)了Go代碼,切換回來時還是在同一個棧中的。關(guān)于C調(diào)用Go,具體到下一節(jié)再分析。

runtime.entersyscall宣布代碼進入了系統(tǒng)調(diào)用,這樣調(diào)度器知道在我們運行外部代碼,于是它可以創(chuàng)建一個新的M來運行g(shù)oroutine。調(diào)用asmcgocall是不會分裂棧并且不會分配內(nèi)存的,因此可以安全地在"syscall call"時調(diào)用,不用考慮GOMAXPROCS計數(shù)。

runtime.asmcgocall是用匯編實現(xiàn)的,它會切換到m的g0棧,然后調(diào)用_cgo_Cfunc_test函數(shù)。由于m的g0棧不是分段棧,因此切換到m->g0棧(這個棧是操作系統(tǒng)分配的棧)后,可以安全地運行g(shù)cc編譯的代碼以及執(zhí)行_cgo_Cfunc_test(frame)函數(shù)。

_cgo_Cfunc_test使用從frame結(jié)構(gòu)體中取得的參數(shù)調(diào)用實際的C函數(shù)test,將結(jié)果記錄在frame中,然后返回到runtime.asmcgocall。

重獲控制權(quán)之后,runtime.asmcgocall切回之前的g(m->curg)的棧,并且返回到runtime.cgocall。

當runtime.cgocall重獲控制權(quán)之后,它調(diào)用exitsyscall,然后將g從m中解鎖。exitsyscall后m會阻塞直到它可以運行Go代碼而不違反$GOMAXPROCS限制。

以上就是Go調(diào)用C時,運行時庫方面所做的事情,是不是很簡單呢?因為總結(jié)起來就兩點,第一點是runtime.entersyscall,讓cgo產(chǎn)生的外部代碼脫離goroutine調(diào)度系統(tǒng)。第二點就是切換m的g0棧,這樣就不必擔憂分段棧方面的問題。

前面講到m的g0棧時,留了個疑問的。那就是新建M的函數(shù)newm只給m的g0棧分配了8K內(nèi)存,好像并不是一個“無窮”的棧,怎么回事呢?這里回答這個問題......不過我會再額外提兩個新問題,希望讀者跟著思考(好賤哦,哈哈)。

其實m的g0棧的大小并不在調(diào)用newm時分配的8K。在newm函數(shù)的最后一步是調(diào)用runtime·newosproc,這個函數(shù)會調(diào)用到操作系統(tǒng)的系統(tǒng)調(diào)用,分配一條系統(tǒng)線程。并且做了一個后處理過程--它將m的g0棧指針改掉了!m的g0棧指針會被重新設(shè)置為線程的棧,所以前面說m的g0棧是一個“無窮”的棧是正確的,那個分配8K內(nèi)存的地方只是一個煙霧彈迷惑人的。

好吧,提兩個疑問結(jié)束這一節(jié)內(nèi)容:

  1. m的g0棧對于每個m是有一個的,cgo調(diào)用會切換到這個棧中進行。那么,如果有多次cgo調(diào)用同時發(fā)生,共用同一個m的棧豈不會沖突?怎么處理?
  2. 這一節(jié)只分配到了Go調(diào)用C,那么如果Go調(diào)用C的代碼中,又回調(diào)了Go函數(shù),這時系統(tǒng)是如何處理的?


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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號