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函數,進行初始化。包括
- runtime·check
- runtime·args
- runtime·osinit
- runtime·schedinit
-
runtime·newproc
建立一個goroutine,執行對象函數為
。runtime.main
- 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)
)。
- 優先從本地(目前線程p對象),或者全局(sched)的gfree隊列擷取一個空閑g對象。
- 沒有空閑g對象,則新建立一個g對象。 設定狀态為
_Grunnable
- 以50%機率作為下個執行對象,否則放到待執行隊列p.runq。如果隊列已滿,從本地隊列抽取一半g對象放入全局隊列。
- 喚醒一個閑置線程,如果沒有閑置的,則建立一個線程。
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.
- 建立一個線程,執行sysmon服務。
- 執行main.init
- 執行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. 總結
- 建立
runtime.main()
goroutine
通過
建立一個runtime.newproc
,執行對象為goroutine
runtime.main()
- 執行
通過runtime.main()
調用runtime.mstart()
即m.mstartfn
。runtime.main()
- 啟動sysmon線程
中調用runtime.main()
建立一個線程,執行背景服務newm()
sysmon
- runtime.main() goroutine中執行.init()。
- runtime.main() goroutine中建立一個gc goroutine。
- runtime.main() goroutine中執行main.init()。
- runtime.main() goroutine中執行main.main(),真正進入業務程序。
10. 主要過程
-
newm()
建立一個系統線程sysmon。
-
newproc()
建立一個g對象,用來執行
。runtime.main
-
mstart()
用來通過
來進入runtime.main
。main