源码分析go调度器三: main goroutine的执行
上一节创建好了main goroutine,并将其放入了allp[0]的runnext中,这一节分析main goroutine是如何被调度到cpu上执行的
执行完CALL runtime·newproc(SB)
后, 继续往下执行,调用mstart
// start this M CALL runtime·mstart(SB) CALL runtime·abort(SB) // mstart should never return RET 复制代码
mstart
调用mstart0
TEXT runtime·mstart(SB),NOSPLIT|TOPFRAME,$0 CALL runtime·mstart0(SB) RET / 复制代码
proc.go:1339
// mstart0 is the Go entry-point for new Ms. // This must not split the stack because we may not even have stack // bounds set up yet. // // May run during STW (because it doesn't have a P yet), so write // barriers are not allowed. // //go:nosplit //go:nowritebarrierrec func mstart0() { _g_ := getg() // _g_=g0 osStack := _g_.stack.lo == 0 //g0已经初始化了,所以osStack=false if osStack { // Initialize stack bounds from system stack. // Cgo may have left stack size in stack.hi. // minit may update the stack bounds. // // Note: these bounds may not be very accurate. // We set hi to &size, but there are things above // it. The 1024 is supposed to compensate this, // but is somewhat arbitrary. size := _g_.stack.hi if size == 0 { size = 8192 * sys.StackGuardMultiplier } _g_.stack.hi = uintptr(noescape(unsafe.Pointer(&size))) _g_.stack.lo = _g_.stack.hi - size + 1024 } // Initialize stack guard so that we can start calling regular // Go code. _g_.stackguard0 = _g_.stack.lo + _StackGuard // This is the g0, so we can also call go:systemstack // functions, which check stackguard1. _g_.stackguard1 = _g_.stackguard0 mstart1() // Exit this thread. if mStackIsSystemAllocated() { // Windows, Solaris, illumos, Darwin, AIX and Plan 9 always system-allocate // the stack, but put it in _g_.stack before mstart, // so the logic above hasn't set osStack yet. osStack = true } mexit(osStack) } 复制代码
mstart0
没干啥,继续看mstart1
proc.go:1380
// The go:noinline is to guarantee the getcallerpc/getcallersp below are safe, // so that we can set up g0.sched to return to the call of mstart1 above. //go:noinline func mstart1() { _g_ := getg() // _g_=g0 if _g_ != _g_.m.g0 { throw("bad runtime·mstart") } // Set up m.g0.sched as a label returning to just // after the mstart1 call in mstart0 above, for use by goexit0 and mcall. // We're never coming back to mstart1 after we call schedule, // so other calls can reuse the current frame. // And goexit0 does a gogo that needs to return from mstart1 // and let mstart0 exit the thread. _g_.sched.g = guintptr(unsafe.Pointer(_g_)) //保存g0的调度信息 _g_.sched.pc = getcallerpc() //保存mstart0调用mstart1的返回地址 _g_.sched.sp = getcallersp() //保存mstart0的栈顶地址 asminit() minit() // Install signal handlers; after minit so that minit can // prepare the thread to be able to handle the signals. if _g_.m == &m0 { mstartm0() } if fn := _g_.m.mstartfn; fn != nil { fn() } if _g_.m != &m0 { acquirep(_g_.m.nextp.ptr()) _g_.m.nextp = 0 } schedule() } 复制代码
接下来是schedule
proc.go:3291
// One round of scheduler: find a runnable goroutine and execute it. // Never returns. func schedule() { (...) execute(gp, inheritTime) } 复制代码
中间过程省略,这篇文章有详细分析
接下来执行execute
proc.go:2670
// Schedules gp to run on the current M. // If inheritTime is true, gp inherits the remaining time in the // current time slice. Otherwise, it starts a new time slice. // Never returns. // // Write barriers are allowed because this is called immediately after // acquiring a P in several places. // //go:yeswritebarrierrec func execute(gp *g, inheritTime bool) { _g_ := getg() // _g_=g0 // Assign gp.m before entering _Grunning so running Gs have an // M. _g_.m.curg = gp //将上一节的newg和m关联起来 gp.m = _g_.m casgstatus(gp, _Grunnable, _Grunning) gp.waitsince = 0 gp.preempt = false gp.stackguard0 = gp.stack.lo + _StackGuard if !inheritTime { _g_.m.p.ptr().schedtick++ } // Check whether the profiler needs to be turned on or off. hz := sched.profilehz if _g_.m.profilehz != hz { setThreadCPUProfiler(hz) } if trace.enabled { // GoSysExit has to happen when we have a P, but before GoStart. // So we emit it here. if gp.syscallsp != 0 && gp.sysblocktraced { traceGoSysExit(gp.sysexitticks) } traceGoStart() } gogo(&gp.sched) } 复制代码
接下来调用gogo完成栈的切换以及执行权的转让
asm_amd64.s:257
// func gogo(buf *gobuf) // restore state from Gobuf; longjmp TEXT runtime·gogo(SB), NOSPLIT, $0-8 MOVQ buf+0(FP), BX // gobuf=上一节newg.sched BX=gobuf MOVQ gobuf_g(BX), DX // DX=newg.g(就是newg自己) MOVQ 0(DX), CX // make sure g != nil JMP gogo<>(SB) TEXT gogo<>(SB), NOSPLIT, $0 get_tls(CX) MOVQ DX, g(CX) //把newg放入线程本地存储,以后可以通过线程本地存储获取正在执行的g MOVQ DX, R14 // set the g register MOVQ gobuf_sp(BX), SP // restore SP SP=newg.sched.sp,完成栈的切换 MOVQ gobuf_ret(BX), AX MOVQ gobuf_ctxt(BX), DX MOVQ gobuf_bp(BX), BP // BP=newg.sched.bp MOVQ $0, gobuf_sp(BX) // clear to help garbage collector 下面几个清空寄存器,有利于gc MOVQ $0, gobuf_ret(BX) MOVQ $0, gobuf_ctxt(BX) MOVQ $0, gobuf_bp(BX) MOVQ gobuf_pc(BX), BX // BX=newg.sched.pc 这里指向runtime.main函数 JMP BX //跳转到runtime.main执行 复制代码
利用
newg.sched
来完成栈的切换跳转到
runtime.main
处执行
proc.go:145
// The main goroutine. func main() { g := getg() // g=newg // Racectx of m0->g0 is used only as the parent of the main goroutine. // It must not be used for anything else. g.m.g0.racectx = 0 // Max stack size is 1 GB on 64-bit, 250 MB on 32-bit. // Using decimal instead of binary GB and MB because // they look nicer in the stack overflow failure message. if sys.PtrSize == 8 { //64位系统栈最大值可为1G maxstacksize = 1000000000 } else { maxstacksize = 250000000 } // An upper limit for max stack size. Used to avoid random crashes // after calling SetMaxStack and trying to allocate a stack that is too big, // since stackalloc works with 32-bit sizes. maxstackceiling = 2 * maxstacksize // Allow newproc to start new Ms. mainStarted = true if GOARCH != "wasm" { // no threads on wasm yet, so no sysmon // For runtime_syscall_doAllThreadsSyscall, we // register sysmon is not ready for the world to be // stopped. // 现在执行在newg上,所以需要切换到g0栈执行m的创建,并在m中运行sysmon,且不需要关联P atomic.Store(&sched.sysmonStarting, 1) systemstack(func() { newm(sysmon, nil, -1) }) } // Lock the main goroutine onto this, the main OS thread, // during initialization. Most programs won't care, but a few // do require certain calls to be made by the main thread. // Those can arrange for main.main to run in the main thread // by calling runtime.LockOSThread during initialization // to preserve the lock. lockOSThread() if g.m != &m0 { throw("runtime.main not on m0") } m0.doesPark = true // Record when the world started. // Must be before doInit for tracing init. runtimeInitTime = nanotime() if runtimeInitTime == 0 { throw("nanotime returning zero") } if debug.inittrace != 0 { inittrace.id = getg().goid inittrace.active = true } doInit(&runtime_inittask) // Must be before defer. // Defer unlock so that runtime.Goexit during init does the unlock too. needUnlock := true defer func() { if needUnlock { unlockOSThread() } }() gcenable() main_init_done = make(chan bool) if iscgo { if _cgo_thread_start == nil { throw("_cgo_thread_start missing") } if GOOS != "windows" { if _cgo_setenv == nil { throw("_cgo_setenv missing") } if _cgo_unsetenv == nil { throw("_cgo_unsetenv missing") } } if _cgo_notify_runtime_init_done == nil { throw("_cgo_notify_runtime_init_done missing") } // Start the template thread in case we enter Go from // a C-created thread and need to create a new thread. startTemplateThread() cgocall(_cgo_notify_runtime_init_done, nil) } doInit(&main_inittask) // Disable init tracing after main init done to avoid overhead // of collecting statistics in malloc and newproc inittrace.active = false close(main_init_done) needUnlock = false unlockOSThread() if isarchive || islibrary { // A program compiled with -buildmode=c-archive or c-shared // has a main, but it is not executed. return } fn := main_main // make an indirect call, as the linker doesn't know the address of the main package when laying down the runtime fn() //执行main函数 if raceenabled { racefini() } // Make racy client program work: if panicking on // another goroutine at the same time as main returns, // let the other goroutine finish printing the panic trace. // Once it does, it will exit. See issues 3934 and 20018. if atomic.Load(&runningPanicDefers) != 0 { // Running deferred functions should not take long. for c := 0; c < 1000; c++ { if atomic.Load(&runningPanicDefers) == 0 { break } Gosched() } } if atomic.Load(&panicking) != 0 { gopark(nil, nil, waitReasonPanicWait, traceEvGoStop, 1) } exit(0) //因为是main函数,所以执行完退出 for { var x *int32 *x = 0 } } 复制代码
创建一个m线程,单独执行sysmon,不需要P
执行main包以及import的包的初始化
执行用户的main.main函数
作者:zxx
链接:https://juejin.cn/post/7015897686045884446