本文所使用的Golang爲1.14,dlv爲1.4.0。linux
package main import "fmt" func main() { fmt.Println("Hello") }
root@xiamin:~/study# dlv debug test.go Type 'help' for list of commands. (dlv) l > _rt0_amd64_linux() /root/go/src/runtime/rt0_linux_amd64.s:8 (PC: 0x465800) Warning: debugging optimized function 3: // license that can be found in the LICENSE file. 4: 5: #include "textflag.h" 6: 7: TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8 => 8: JMP _rt0_amd64(SB) 9: 10: TEXT _rt0_amd64_linux_lib(SB),NOSPLIT,$0 11: JMP _rt0_amd64_lib(SB)
能夠看到最開始是從_rt0_amd64_linux執行,而後直接跳轉到_rt0_amd64。執行si進入_rt0_amd64。bootstrap
(dlv) si > _rt0_amd64() /root/go/src/runtime/asm_amd64.s:15 (PC: 0x461c20) Warning: debugging optimized function 10: // _rt0_amd64 is common startup code for most amd64 systems when using 11: // internal linking. This is the entry point for the program from the 12: // kernel for an ordinary -buildmode=exe program. The stack holds the 13: // number of arguments and the C-style argv. 14: TEXT _rt0_amd64(SB),NOSPLIT,$-8 => 15: MOVQ 0(SP), DI // argc,將參數個數存入DI 16: LEAQ 8(SP), SI // argv,參數數組的地址存入SI 17: JMP runtime·rt0_go(SB)
繼續執行,runtime.rt0_go() /root/go/src/runtime/asm_amd64.s:89 (PC: 0x461c30)windows
runtime.rt0_go中代碼較多,但咱們只關注與調度相關的。數組
TEXT runtime·rt0_go(SB),NOSPLIT,$0 // 忽略處理命令行參數相關 // 爲全局變量g0設置一些棧相關的屬性 MOVQ $runtime·g0(SB), DI // 將全局變量g0的存入DI LEAQ (-64*1024+104)(SP), BX // bx = SP-(64*1024+104),g0的棧幀大小 MOVQ BX, g_stackguard0(DI) // g0.stackguard0 = bx MOVQ BX, g_stackguard1(DI) // g0.stackguard1 = bx MOVQ BX, (g_stack+stack_lo)(DI) // g0.stack.lo = bx 棧的低地址 MOVQ SP, (g_stack+stack_hi)(DI) // g0.stack.hi = sp 棧的高地址 // 忽略獲取cpu型號等相關與cgo初始化 // 線程本地存儲(tls)相關設置 LEAQ runtime·m0+m_tls(SB), DI // di = &m0.tls CALL runtime·settls(SB) // 設置tls,下面有詳細分析 // 驗證tls是否生效:經過tls設置一個數值,而後m0.tls[0]獲取,與設置的值對比。 get_tls(BX) // 獲取fs地址到bx MOVQ $0x123, g(BX) // 反編譯後 mov qword ptr fs:[0xfffffff8], 0x123,表示設置fs-8地址中的內容爲0x123,其實就是m0.tls[0]的地址。 MOVQ runtime·m0+m_tls(SB), AX // ax = m0.tls[0] CMPQ AX, $0x123 // 比較 JEQ 2(PC) CALL runtime·abort(SB) // m0.tls[0] = &g0; g0與m0相互綁定 get_tls(BX) // 獲取fs地址到bx LEAQ runtime·g0(SB), CX // cx = &g0 MOVQ CX, g(BX) // m0.tls[0] = &g0 LEAQ runtime·m0(SB), AX // ax = &m0 MOVQ CX, m_g0(AX) // m0.g0 = &g0 MOVQ AX, g_m(CX) // g0.m = &m0 // 忽略copy argc和argv的代碼 CALL runtime·args(SB) // 命令行參數相關,暫不關心 CALL runtime·osinit(SB) // 設置全局變量ncpu(cpu個數),全局變量physHugePageSize CALL runtime·schedinit(SB) // 調度器初始化 // 調用runtime·newproc建立goroutine,指向函數爲runtime·main MOVQ $runtime·mainPC(SB), AX // runtime·mainPC就是runtime·main PUSHQ AX // newproc的第二個參數,也就是goroutine要執行的函數。 PUSHQ $0 // newproc的第一個參數,表示要傳入runtime·main中參數的大小,此處爲0。 // 建立 main goroutine。非main goroutine也是此方法建立。 // go編譯會將語句 go foo() 編譯爲 runtime·newproc(SB) 並傳入參數。 CALL runtime·newproc(SB) POPQ AX POPQ AX CALL runtime·mstart(SB) // 進入調度循環 CALL runtime·abort(SB) // mstart應該永不返回,若是返回,則是程序出現錯誤了。 RET MOVQ $runtime·debugCallV1(SB), AX RET DATA runtime·mainPC+0(SB)/8,$runtime·main(SB) GLOBL runtime·mainPC(SB),RODATA,$8
runtime·settls中經過調用arch_prctl系統調用設置FS來實現線程本地存儲。函數
經過syscall指令調用系統調用ui
新建非m0的m時也會經過runtime·clone調用此函數。atom
TEXT runtime·settls(SB),NOSPLIT,$32 // 此時di = &m.tls[0] ADDQ $8, DI // ELF 須要使用 -8(FS),di+=8,執行完此指令後 di = &m.tls[1] MOVQ DI, SI // 將地址移動到si中,做爲系統調用的第二個參數 MOVQ $0x1002, DI // ARCH_SET_FS表示設置FS,做爲系統調用的第一個參數 MOVQ $SYS_arch_prctl, AX // rax存儲系統調用號 SYSCALL CMPQ AX, $0xfffffffffffff001 // 比較返回結果 JLS 2(PC) MOVL $0xf1, 0xf1 // crash RET
runtime.schedinit中包含了不少功能的初始化,本文暫且分析與調度相關的命令行
func schedinit() { _g_ := getg() // 未找到getg()的源代碼,經過註釋得知getg()返回當前g,此處 _g_爲&g0 .......... sched.maxmcount = 10000 // m的最大數量爲10000 .......... mcommoninit(_g_.m) // 此處_g_.m即爲m0,對m0的一些初始化工做,下面詳細分析 .......... // 獲取要初始化的p的數量,默認與cpu個數相同,若是指定了GOMAXPROCS,則爲GOMAXPROCS procs := ncpu if n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 { procs = n } // 初始化allp併爲allp中的元素初始化、賦值等,詳見下方 if procresize(procs) != nil { throw("unknown runnable goroutine during bootstrap") } .......... }
func mcommoninit(mp *m) { _g_ := getg() // 獲取當前g,也就是g0 // g0 stack won't make sense for user (and is not necessary unwindable). if _g_ != _g_.m.g0 { callers(1, mp.createstack[:]) // 調用棧相關 } lock(&sched.lock) if sched.mnext+1 < sched.mnext { throw("runtime: thread ID overflow") } mp.id = sched.mnext // 設置m的id sched.mnext++ // 加1,之後分配給下一個m checkmcount() // 檢查非空閒數量的m是否超過了10000 // rand相關 mp.fastrand[0] = uint32(int64Hash(uint64(mp.id), fastrandseed)) mp.fastrand[1] = uint32(int64Hash(uint64(cputicks()), ^fastrandseed)) if mp.fastrand[0]|mp.fastrand[1] == 0 { mp.fastrand[1] = 1 } // 新建一個32k棧大小的g,賦值給m0.gsignal。並使 m0.gsignal.m = *m0 mpreinit(mp) if mp.gsignal != nil { mp.gsignal.stackguard1 = mp.gsignal.stack.lo + _StackGuard } // 下面兩步將mp放入全局變量allm中,allm是個鏈表 mp.alllink = allm atomicstorep(unsafe.Pointer(&allm), unsafe.Pointer(mp)) unlock(&sched.lock) // Allocate memory to hold a cgo traceback if the cgo call crashes. if iscgo || GOOS == "solaris" || GOOS == "illumos" || GOOS == "windows" { mp.cgoCallers = new(cgoCallers) } }
mcommoninit基本上就是作一些m0的初始化。線程
// 傳入參數nprocs爲指望的全部p的個數 func procresize(nprocs int32) *p { old := gomaxprocs // gomaxprocs在本方法的末尾會被更改 if old < 0 || nprocs <= 0 { throw("procresize: invalid arg") } if trace.enabled { traceGomaxprocs(nprocs) } // 更新統計信息 now := nanotime() if sched.procresizetime != 0 { sched.totaltime += int64(old) * (now - sched.procresizetime) } sched.procresizetime = now // 初始化allp if nprocs > int32(len(allp)) { // Synchronize with retake, which could be running // concurrently since it doesn't run on a P. lock(&allpLock) if nprocs <= int32(cap(allp)) { allp = allp[:nprocs] } else { // 初始化一個臨時變量nallp,與現存的allp合併,而後將nallp賦值給全局變量allp nallp := make([]*p, nprocs) copy(nallp, allp[:cap(allp)]) allp = nallp } unlock(&allpLock) } // 初始化新添加到allp中的元素 for i := old; i < nprocs; i++ { pp := allp[i] if pp == nil { pp = new(p) } pp.init(i) // 會初始化p結構的屬性:id,status,mcache等 atomicstorep(unsafe.Pointer(&allp[i]), unsafe.Pointer(pp)) // 賦值 } _g_ := getg() // 初始化的時候 _g_.m.p = 0 因此走else if _g_.m.p != 0 && _g_.m.p.ptr().id < nprocs { // continue to use the current P _g_.m.p.ptr().status = _Prunning _g_.m.p.ptr().mcache.prepareForSweep() } else { // 此處省略一些初始化時不會進入的代碼 _g_.m.p = 0 _g_.m.mcache = nil p := allp[0] p.m = 0 p.status = _Pidle acquirep(p) // m.mcache = p.mcache;p和m相互綁定;p.status = _Prunning。下面有分析。 if trace.enabled { traceGoStart() } } // 釋放未使用的p的資源,好比調用runtime.GOMAXPROCS(num),會調用procresize。 // num小於當前p的數量時,會執行此處 for i := nprocs; i < old; i++ { p := allp[i] p.destroy() // can't free P itself because it can be referenced by an M in syscall } // Trim allp. if int32(len(allp)) != nprocs { lock(&allpLock) allp = allp[:nprocs] unlock(&allpLock) } // 將除了當前m綁定p的其他allp中的都以鏈表形式存入sched.pidle中 var runnablePs *p for i := nprocs - 1; i >= 0; i-- { p := allp[i] if _g_.m.p.ptr() == p { // 是不是當前g.m的p continue } p.status = _Pidle if runqempty(p) { pidleput(p) // 將p放入到空閒列表中 } else { p.m.set(mget()) p.link.set(runnablePs) runnablePs = p } } // 這裏會更改gomaxprocs的值 stealOrder.reset(uint32(nprocs)) var int32p *int32 = &gomaxprocs // make compiler check that gomaxprocs is an int32 atomic.Store((*uint32)(unsafe.Pointer(int32p)), uint32(nprocs)) return runnablePs }
總結一下procresize的工做:debug
acquirep(p)->wirep(_p_) :acquirep中的主要邏輯就是調用了wirep
func wirep(_p_ *p) { _g_ := getg() if _g_.m.p != 0 || _g_.m.mcache != nil { throw("wirep: already in go") } if _p_.m != 0 || _p_.status != _Pidle { id := int64(0) if _p_.m != 0 { id = _p_.m.ptr().id } print("wirep: p->m=", _p_.m, "(", id, ") p->status=", _p_.status, "\n") throw("wirep: invalid p state") } _g_.m.mcache = _p_.mcache // p的mcache賦值給m.mcache _g_.m.p.set(_p_) // 與下面的一行爲 p和m相互綁定 _p_.m.set(_g_.m) _p_.status = _Prunning // 更改p的狀態 }