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

2018-07-25 16:23 更新

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

從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的第一個(gè)參數(shù)_cgo_Cfunc_test是一個(gè)由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,這樣不會(huì)阻塞其它的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代碼,切換回來(lái)時(shí)還是在同一個(gè)棧中的。關(guān)于C調(diào)用Go,具體到下一節(jié)再分析。

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

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

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

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

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

以上就是Go調(diào)用C時(shí),運(yùn)行時(shí)庫(kù)方面所做的事情,是不是很簡(jiǎn)單呢?因?yàn)榭偨Y(jié)起來(lái)就兩點(diǎn),第一點(diǎn)是runtime.entersyscall,讓cgo產(chǎn)生的外部代碼脫離goroutine調(diào)度系統(tǒng)。第二點(diǎn)就是切換m的g0棧,這樣就不必?fù)?dān)憂分段棧方面的問(wèn)題。

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

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

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

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


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

掃描二維碼

下載編程獅App

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

編程獅公眾號(hào)