Go語言 系統(tǒng)初始化

2018-07-25 17:24 更新

整個程序啟動是從_rt0_amd64_darwin開始的,然后JMP到main,接著到_rt0_amd64。前面只有一點(diǎn)點(diǎn)匯編代碼,做的事情就是通過參數(shù)argc和argv等,確定棧的位置,得到寄存器。下面將從_rt0_amd64開始分析。

這里首先會設(shè)置好m->g0的棧,將當(dāng)前的SP設(shè)置為stackbase,將SP往下大約64K的地方設(shè)置為stackguard。然后會獲取處理器信息,放在全局變量runtime·cpuid_ecx和runtime·cpuid_edx中。接著,設(shè)置本地線程存儲。本地線程存儲是依賴于平臺實(shí)現(xiàn)的,比如說這臺機(jī)器上是調(diào)用操作系統(tǒng)函數(shù)thread_fast_set_cthread_self。設(shè)置本地線程存儲之后還會立即測試一下,寫入一個值再讀出來看是否正常。

本地線程存儲

這里解釋一下本地線程存儲。比如說每個goroutine都有自己的控制信息,這些信息是存放在一個結(jié)構(gòu)體G中。假設(shè)我們有一個全局變量g是結(jié)構(gòu)體G的指針,我們希望只有唯一的全局變量g,而不是g0,g1,g2...但是我們又希望不同goroutine去訪問這個全局變量g得到的并不是同一個東西,它們得到的是相對自己線程的結(jié)構(gòu)體G,這種情況下就需要本地線程存儲。g確實(shí)是一個全局變量,卻在不同線程有多份不同的副本。每個goroutine去訪問g時,都是對應(yīng)到自己線程的這一份副本。

設(shè)置好本地線程存儲之后,就可以為每個goroutine和machine設(shè)置寄存器了。這樣設(shè)置好了之后,每次調(diào)用get_tls(r),就會將當(dāng)前的goroutine的g的地址放到寄存器r中。你可以在源代碼中看到一些類似這樣的匯編:

    get_tls(CX)
    MOVQ    g(CX), AX //get_tls(CX)之后,g(CX)得到的就是當(dāng)前的goroutine的g

不同的goroutine調(diào)用get_tls,得到的g是本地的結(jié)構(gòu)體G的,結(jié)構(gòu)體中記錄goroutine的相關(guān)信息。

初始化順序

接下來的事情就非常直白,可以直接上代碼:

    CLD                // convention is D is always left cleared
    CALL    runtime·check(SB) //檢測像int8,int16,float等是否是預(yù)期的大小,檢測cas操作是否正常
    MOVL    16(SP), AX        // copy argc
    MOVL    AX, 0(SP)
    MOVQ    24(SP), AX        // copy argv
    MOVQ    AX, 8(SP)
    CALL    runtime·args(SB)    //將argc,argv設(shè)置到static全局變量中了
    CALL    runtime·osinit(SB)    //osinit做的事情就是設(shè)置runtime.ncpu,不同平臺實(shí)現(xiàn)方式不一樣
    CALL    runtime·hashinit(SB)    //使用讀/dev/urandom的方式從內(nèi)核獲得隨機(jī)數(shù)種子
    CALL    runtime·schedinit(SB)    //內(nèi)存管理初始化,根據(jù)GOMAXPROCS設(shè)置使用的procs等等

proc.c中有一段注釋,也說明了bootstrap的順序:

// The bootstrap sequence is:
//
// call osinit
// call schedinit
// make & queue new G
// call runtime·mstart
//
// The new G calls runtime·main.

先調(diào)用osinit,再調(diào)用schedinit,創(chuàng)建就緒隊(duì)列并新建一個G,接著就是mstart。這幾個函數(shù)都不太復(fù)雜。

調(diào)度器初始化

讓我們看一下runtime.schedinit函數(shù)。該函數(shù)其實(shí)是包裝了一下其它模塊的初始化函數(shù)。有調(diào)用mallocinit,mcommoninit分別對內(nèi)存管理模塊初始化,對當(dāng)前的結(jié)構(gòu)體M初始化。

接著調(diào)用runtime.goargs和runtime.goenvs,將程序的main函數(shù)參數(shù)argc和argv等復(fù)制到了os.Args中。

也是在這個函數(shù)中,根據(jù)環(huán)境變量GOMAXPROCS決定可用物理線程數(shù)目的:

    procs = 1;
    p = runtime·getenv("GOMAXPROCS");
    if(p != nil && (n = runtime·atoi(p)) > 0) {
        if(n > MaxGomaxprocs)
            n = MaxGomaxprocs;
        procs = n;
    }

回到前面的匯編代碼繼續(xù)看:

    // 新建一個G,當(dāng)它運(yùn)行時會調(diào)用main.main
    PUSHQ    $runtime·main·f(SB)        // entry
    PUSHQ    $0            // arg size
    CALL    runtime·newproc(SB)
    POPQ    AX
    POPQ    AX

    // start this M
    CALL    runtime·mstart(SB)

還記得前面章節(jié)講的go關(guān)鍵字的調(diào)用協(xié)議么?先將參數(shù)進(jìn)棧,再被調(diào)函數(shù)指針和參數(shù)字節(jié)數(shù)進(jìn)棧,接著調(diào)用runtime.newproc函數(shù)。所以這里其實(shí)就是新開個goroutine執(zhí)行runtime.main。

runtime.newproc會把runtime.main放到就緒線程隊(duì)列里面。本線程繼續(xù)執(zhí)行runtime.mstart,m意思是machine。runtime.mstart會調(diào)用到調(diào)度函數(shù)schedule

schedule函數(shù)絕不返回,它會根據(jù)當(dāng)前線程隊(duì)列中線程狀態(tài)挑選一個來運(yùn)行。由于當(dāng)前只有這一個goroutine,它會被調(diào)度,然后就到了runtime.main函數(shù)中來,runtime.main會調(diào)用用戶的main函數(shù),即main.main從此進(jìn)入用戶代碼。前面已經(jīng)寫過helloworld了,用gdb調(diào)試,一步一步的跟蹤觀察這個過程。

links


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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號