阅读 107

源码分析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() } 复制代码

image.png 接下来是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) } 复制代码

image.png 接下来调用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执行 复制代码

  1. 利用newg.sched来完成栈的切换

  2. 跳转到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    } } 复制代码

  1. 创建一个m线程,单独执行sysmon,不需要P

  2. 执行main包以及import的包的初始化

  3. 执行用户的main.main函数


作者:zxx
链接:https://juejin.cn/post/7015897686045884446


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐