1. 程式人生 > >Golang原始碼學習:排程邏輯(一)初始化

Golang原始碼學習:排程邏輯(一)初始化

本文所使用的Golang為1.14,dlv為1.4.0。 ### 原始碼 ``` 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。 ``` (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) ### runtime.rt0_go 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 設定執行緒本地儲存 runtime·settls中通過呼叫arch_prctl系統呼叫設定FS來實現執行緒本地儲存。 通過syscall指令呼叫系統呼叫 - rax存放系統呼叫號,呼叫返回值也會放在rax中 - 當系統呼叫引數小於等於6個時,引數則須按順序放到暫存器 rdi,rsi,rdx,r10,r8,r9中。 - 如果系統呼叫的引數數量大於6個,需將引數儲存在一塊連續的記憶體中,並將地址存入rbx中。 **新建非m0的m時也會通過runtime·clone呼叫此函式。** ``` 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 排程初始化 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") } .......... } ``` #### schedinit->mcommoninit ``` 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的初始化。 #### schedinit->
procresize ``` // 傳入引數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的工作: - allp切片中p的數量小於期望p數量時,對allp進行擴容 - 使用new建立p並呼叫p.init初始化剛擴容出的,init中為p分配id和mcache - 初始化時,呼叫acquirep使allp[0]與m0相互繫結,並且m.mcache = p.mcache,p.status = _Prunning - allp切片中p的數量大於期望p數量時,呼叫p.destroy釋放未使用的p的資源 - 將除了allp[0]之外的p狀態設定為_Pidle並加入到全域性空閒列表sched.pidle中 - 更改gomaxprocs值為nprocs 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的狀