天天看點

12-go程序啟動過程

bootstrap順序

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

           

src\runtime\asm_amd64.s

go程序的啟動過程在檔案

src\runtime\asm_amd64.s

中可以看到。

1. main入口

彙編main入口比較簡單,直接跳轉到runtime·rt0_go。

對于amd64系統,不同的連結方式入口不同。有兩種方式。

// _rt0_amd64 is common startup code for most amd64 systems when using
// internal linking. This is the entry point for the program from the
// kernel for an ordinary -buildmode=exe program. The stack holds the
// number of arguments and the C-style argv.
TEXT _rt0_amd64(SB),NOSPLIT,$-8
	MOVQ	0(SP), DI	// argc
	LEAQ	8(SP), SI	// argv
	JMP	runtime·rt0_go(SB)

// main is common startup code for most amd64 systems when using
// external linking. The C startup code will call the symbol "main"
// passing argc and argv in the usual C ABI registers DI and SI.
TEXT main(SB),NOSPLIT,$-8
	JMP	runtime·rt0_go(SB)
           

2. runtime·rt0_go

rt0_go 調用一系列runtime函數,進行初始化。包括

  1. runtime·check
  2. runtime·args
  3. runtime·osinit
  4. runtime·schedinit
  5. runtime·newproc

    建立一個goroutine,執行對象函數為

    runtime.main

  6. runtime·mstart
TEXT runtime·rt0_go(SB),NOSPLIT,$0
    // 1. 将參數入棧。
    // 2. 查找cpu資訊。
    //      1.如果沒有cpu資訊,如果有_cgo_init就調用
    // 3. 設定m->g0 = g0
    // 4. 設定m0 to g0->m
    // 5. CALL	runtime·check(SB)	
    // 6. CALL	runtime·args(SB)
	// 7. CALL	runtime·osinit(SB)
	// 8. CALL	runtime·schedinit(SB)
	// 9. CALL  runtime·newproc建立一個goroutine啟動程序。
	//      create a new goroutine to start program
	//      0.MOVQ	$runtime·mainPC(SB), AX		// entry == runtime·main
	//          将函數入口runtime·main放入AX,作為newproc執行函數
	//          DATA	runtime·mainPC+0(SB)/8,$runtime·main(SB)
	//      1.通過調用runtime·newproc啟動一個goroutine
	//      2.CALL	runtime·newproc(SB)
	// 10.CALL	runtime·mstart 啟動一個M,執行 runtime·main
	//      1.CALL	runtime·mstart(SB)
	//      
	//
           

3. osinit()

擷取CPU個數。

func osinit() {
	ncpu = getproccount()   //通過封裝系統調用,擷取CPU個數
}
           

4. schedinit()

進行子產品初始化,符号初始化,記憶體初始化,gc初始化,p初始化等。

func schedinit() {
    //1. 擷取目前的goroutine
    _g_ := getg()
    
    //2. tracebackinit()
    tracebackinit()
    
    //3.子產品資料校驗
    moduledataverify()
        //type moduledata struct { 定義了二進制映像檔案資訊,由連結器填寫。包括:代碼段、隻讀資料,資料段等資訊
        
    //4. 棧初始化
    stackinit()
    
    //5. 記憶體配置設定器初始化
    mallocinit()
    
    //6.
    mcommoninit(_g_.m)
    
    //7. cpu初始化,
    //  1.cpu個數;2.從參數擷取cpu特性
    cpuinit()
    
    //8. 初始化AES hash算法
    alginit()
    
    //9. 子產品初始化
    //  src\runtime\symtab.go
    //  當一個module被動态連結器首次加載時,.init_array被調用。
    //  在一個Go程序聲明周期内,有兩種場景會發生module的加載。
    //  1.以-buildmode=shared 模式編譯,程序初始化會加載。
    //  2.-buildmode=plugin模式編譯,程序運作時加載。
    //  限制:1.modulesinit一次隻能有一個goroutine調用。
    modulesinit()
    
    //10. 建立符号表
    typelinksinit()
    
    //11. itab初始化
    itabsinit()
    
    //12. 空函數
    msigsave(_g_.m)
    
    //13. 将參數都放入切片argslice
    // windows下什麼也不做
    goargs()
    
    //14. 将所有參數放入切邊envs
    goenvs()
    
    //15. 擷取所有GODEBUG參數。
    //  1.更新memprofilerate參數
    //  2.設定GOTRACEBACK級别
    //  3.預設進行cgocheck=1,invalidptr=1
    //  4.根據參數設定debug參數dbgvars
    parsedebugvars()
    
    //16. gc初始化
    //  1.memstats.triggerRatio = 7 / 8.0
    //  2.預設觸發GC的堆大小為4M。可以調整。
    /// 3.根據GOGC參數設定GC比例,
    //      比如100->4M*1
    //      比如200->4M*2
    //  4.以通過設定 GOGC=off 來徹底關閉 GC
    gcinit()
    
    //17. 設定proc個數
    預設為cpu個數(procs)。
    如果設定了環境變量,"GOMAXPROCS",則設定為環境變量設定值。
    
    //18. 改變p個數
    procresize(procs)
        //整個過程所有處理都是暫停了stop the world,并且sched被加鎖
        //allp是一個slice儲存所有的p。根據需要增長allp大小
        //初始化新建立的p
        //将所有可以運作的p放入,sched.runq隊列頭部
        
           

5. runtime.newproc

實際調用newproc1(具體見

[13-goroutine建立過程](https://blog.csdn.net/svasticah/article/details/94345696)

)。

  1. 優先從本地(目前線程p對象),或者全局(sched)的gfree隊列擷取一個空閑g對象。
  2. 沒有空閑g對象,則新建立一個g對象。 設定狀态為

    _Grunnable

  3. 以50%機率作為下個執行對象,否則放到待執行隊列p.runq。如果隊列已滿,從本地隊列抽取一半g對象放入全局隊列。
  4. 喚醒一個閑置線程,如果沒有閑置的,則建立一個線程。

6. runtime·mstart

通過

schedule()

實際執行之前準備好的goroutine,即

runtime.main

func mstart1() {
    // 1.asminit()空函數
    // 2.minit() m初始化,主要是信号初始化
    // 3. mstartm0()//對于windows單獨啟動一個額外的M用于callback
    // 4. fn() //如果有mstartfn,則執行。
    // 5. schedule()  //找到一個可以運作的的goroutine并執行。
            1.如果處于gc等待狀态sched.gcwaiting,則stop the world.
                1.gcstopm()
                    1.releasep() 将p與m解除關聯
                    2.stopm() 停止執行目前的m,直到新的work可用
                2.goto top  //循環等待gcwaiting是否結束
            2.trace
            3.擷取背景一個goroutine
                findRunnableGCWorker()
            4.如果擷取失敗,以一定幾率從全局隊列中擷取一個goroutine
                1.幾率schedtick%61 == 0
                2.從全局隊列擷取,globrunqget()
                3.擷取過程會加鎖, lock(&sched.lock)
            5.如果仍然沒有可以運作goroutine,則阻塞等待。
                findrunnable()
                    1. 處于sched.gcwaiting狀态,循環等待goto top
                    2. 如果處于喚醒等待狀态,則準備一個goroutine
                    3. 如果有cgo處于yeild,則調用cgo
                    4. 再次嘗試擷取一個本地goroutine,成功則傳回。
                    5. 再次嘗試從全局隊列中擷取goroutine,成功則傳回。
                    6. Poll network.
                        通過poll等待網絡。
            6. 找到一個goroutine,則執行runtime·gogo
}
           

7.執行一個goroutine

// func gogo(buf *gobuf)
// restore state from Gobuf; longjmp
TEXT runtime·gogo(SB), NOSPLIT, $16-8
	MOVQ	buf+0(FP), BX		// gobuf
	MOVQ	gobuf_g(BX), DX
	MOVQ	0(DX), CX		// make sure g != nil
	get_tls(CX)
	MOVQ	DX, g(CX)
	MOVQ	gobuf_sp(BX), SP	// restore SP
	MOVQ	gobuf_ret(BX), AX
	MOVQ	gobuf_ctxt(BX), DX
	MOVQ	gobuf_bp(BX), BP
	MOVQ	$0, gobuf_sp(BX)	// clear to help garbage collector
	MOVQ	$0, gobuf_ret(BX)
	MOVQ	$0, gobuf_ctxt(BX)
	MOVQ	$0, gobuf_bp(BX)
	MOVQ	gobuf_pc(BX), BX
	JMP	BX
           

8. runtime.main

src\runtime\proc.go

// The main goroutine.

  1. 建立一個線程,執行sysmon服務。
  2. 執行main.init
  3. 執行main.main
func main() {
    1. newm(sysmon, nil)
        對于非wasm(WebAssembly )環境,建立一個m,執行sysmon。
        1. allocm() 建立一個m對象
        2. 對于cgo,通過asmcgocall(_cgo_thread_start, unsafe.Pointer(&ts))系統調用建立一個線程
        3. 非cgo,通過pthread_create方式建立線程。其他通過clone方式建立(linux)
        4. sysmon
            1. 常駐背景
            2. trigger libc interceptors if needed
            3. poll network if not polled for more than 10ms
            4. 奪回被系統調用阻塞的p,并且搶占長期運作的g
            5. 判斷是否需要做gc
            6. 偶爾清理一下heap,
                1.在gc後5分鐘沒有使用,就歸還給作業系統,scavengelimit := int64(5 * 60 * 1e9)
    2. runtime_init()
        執行預設.init函數
    3. gcenable()
        啟動gc goroutine
    4. main_init
        執行main.init
    5. main_main
        執行main.main
}
           

9. 總結

  1. 建立

    runtime.main()

    goroutine

    通過

    runtime.newproc

    建立一個

    goroutine

    ,執行對象為

    runtime.main()

  2. 執行

    runtime.main()

    通過

    runtime.mstart()

    調用

    m.mstartfn

    runtime.main()

  3. 啟動sysmon線程

    runtime.main()

    中調用

    newm()

    建立一個線程,執行背景服務

    sysmon

  4. runtime.main() goroutine中執行.init()。
  5. runtime.main() goroutine中建立一個gc goroutine。
  6. runtime.main() goroutine中執行main.init()。
  7. runtime.main() goroutine中執行main.main(),真正進入業務程序。

10. 主要過程

  1. newm()

    建立一個系統線程sysmon。

  2. newproc()

    建立一個g對象,用來執行

    runtime.main

  3. mstart()

    用來通過

    runtime.main

    來進入

    main