// morestack but not preserving ctxt. // 这里noctxt表示调用方法没有context,即没有闭包或者receiver TEXT runtime·morestack_noctxt(SB),NOSPLIT,$0 MOVL $0, DX // 清空DX的低32位,DX寄存器用于保存函数上下文 JMP runtime·morestack(SB) // 跳转到morestack方法,这里用的是JMP,不是CALL
/* * support for morestack */
// Called during function prolog when more stack is needed. // // The traceback routines see morestack on a g0 as being // the top of a stack (for example, morestack calling newstack // calling the scheduler calling newm calling gc), so we must // record an argument size. For that purpose, it has no arguments. TEXT runtime·morestack(SB),NOSPLIT,$0-0 // Cannot grow scheduler stack (m->g0). get_tls(CX) // 这里get_tls(r)是一个宏:MOVQ TLS, r MOVQ g(CX), BX // 保存当前的g到BX MOVQ g_m(BX), BX // 保存m到BX MOVQ m_g0(BX), SI // 保存g0到SI CMPQ g(CX), SI // 如果当前处于g0栈 JNE 3(PC) // PC+3 CALL runtime·badmorestackg0(SB) // g0栈不允许扩张 CALL runtime·abort(SB)
// Cannot grow signal stack (m->gsignal). MOVQ m_gsignal(BX), SI // gsignal用于处理信号量的栈 CMPQ g(CX), SI JNE 3(PC) CALL runtime·badmorestackgsignal(SB) // gsignal栈不允许扩张 CALL runtime·abort(SB)
// 检查是否需要抢占,当发现一个协程需要被抢占时,会将其g.stackguard0设置成stackPreempt,从而触发morestack的执行 preempt := atomic.Loaduintptr(&gp.stackguard0) == stackPreempt // 触发了抢占 if preempt { // We are interested in preempting user Go code, not runtime code. // If we're holding locks, mallocing, or preemption is disabled, don't // preempt. if thisg.m.locks != 0 || thisg.m.mallocing != 0 || thisg.m.preemptoff != "" || thisg.m.p.ptr().status != _Prunning { // 还原gp.stackguard0 // gp->preempt is set, so it will be preempted next time. gp.stackguard0 = gp.stack.lo + _StackGuard // restore state from Gobuf; longjmp gogo(&gp.sched) // never return } }
sp := gp.sched.sp if sys.ArchFamily == sys.AMD64 || sys.ArchFamily == sys.I386 || sys.ArchFamily == sys.WASM { // The call to morestack cost a word. sp -= sys.PtrSize } // 再次检查抢占 if preempt { // g0不允许被抢占 if gp == thisg.m.g0 { throw("runtime: preempt g0") } if thisg.m.p == 0 && thisg.m.locks == 0 { throw("runtime: g is running but p is not") } // Synchronize with scang. // 更新状态为_Gwaiting casgstatus(gp, _Grunning, _Gwaiting) // gc相关,抢占g扫描 if gp.preemptscan { for !castogscanstatus(gp, _Gwaiting, _Gscanwaiting) { // Likely to be racing with the GC as // it sees a _Gwaiting and does the // stack scan. If so, gcworkdone will // be set and gcphasework will simply // return. } if !gp.gcscandone { // gcw is safe because we're on the // system stack. gcw := &gp.m.p.ptr().gcw // 扫描gp的栈 scanstack(gp, gcw) if gcBlackenPromptly { gcw.dispose() } gp.gcscandone = true } gp.preemptscan = false gp.preempt = false casfrom_Gscanstatus(gp, _Gscanwaiting, _Gwaiting) // This clears gcscanvalid. casgstatus(gp, _Gwaiting, _Grunning) gp.stackguard0 = gp.stack.lo + _StackGuard gogo(&gp.sched) // never return }
// Act like goroutine called runtime.Gosched. casgstatus(gp, _Gwaiting, _Grunning) // 这里执行抢占,实际上就是调用schedule方法,该方法不会返回 gopreempt_m(gp) // never return }
// The goroutine must be executing in order to call newstack, // so it must be Grunning (or Gscanrunning). // 设置g的状态 casgstatus(gp, _Grunning, _Gcopystack)
// The concurrent GC will not scan the stack while we are doing the copy since // the gp is in a Gcopystack status. // coypstack会创建一个新的栈,然后把旧的栈的内容拷到新的栈中 copystack(gp, newsize, true) if stackDebug >= 1 { print("stack grow done\n") } // 可以开始跑了 casgstatus(gp, _Gcopystack, _Grunning) gogo(&gp.sched) // gogo开始跑了 }
// Always runs without a P, so write barriers are not allowed. // //go:nowritebarrierrec funcsysmon() { lock(&sched.lock) sched.nmsys++ checkdead() unlock(&sched.lock)
// If a heap span goes unused for 5 minutes after a garbage collection, // we hand it back to the operating system. scavengelimit := int64(5 * 60 * 1e9)
if debug.scavenge > 0 { // Scavenge-a-lot for testing. forcegcperiod = 10 * 1e6 scavengelimit = 20 * 1e6 }
lastscavenge := nanotime() nscavenge := 0
lasttrace := int64(0) idle := 0// how many cycles in succession we had not wokeup somebody delay := uint32(0) for { if idle == 0 { // start with 20us sleep... delay = 20 } elseif idle > 50 { // start doubling the sleep after 1ms... delay *= 2 } if delay > 10*1000 { // up to 10ms delay = 10 * 1000 } usleep(delay) ... // 注释很清楚了 // retake P's blocked in syscalls // and preempt long running G's if retake(now) != 0 { idle = 0 } else { idle++ } ... } }
funcretake(now int64)uint32 { n := 0 // 加锁 lock(&allpLock) // 遍历p列表 for i := 0; i < len(allp); i++ { _p_ := allp[i] if _p_ == nil { // This can happen if procresize has grown // allp but not yet created new Ps. continue } pd := &_p_.sysmontick s := _p_.status if s == _Psyscall { // 这里是处理系统调用时的P // 如果系统调用阻塞到一定时长,并且当前有其他g可执行时,考虑将_Psyscall的p夺回过来,标记为_Pidle ... } elseif s == _Prunning { // 如果当前P已经在同一个G上运行很久了,标记抢占 // Preempt G if it's running for too long. t := int64(_p_.schedtick) ifint64(pd.schedtick) != t { pd.schedtick = uint32(t) pd.schedwhen = now continue } if pd.schedwhen+forcePreemptNS > now { continue } // 抢占 preemptone(_p_) } } unlock(&allpLock) returnuint32(n) }
funcpreemptone(_p_ *p)bool { mp := _p_.m.ptr() if mp == nil || mp == getg().m { returnfalse } gp := mp.curg if gp == nil || gp == mp.g0 { returnfalse }
// 设置抢占标志位 gp.preempt = true
// Every call in a go routine checks for stack overflow by // comparing the current stack pointer to gp->stackguard0. // Setting gp->stackguard0 to StackPreempt folds // preemption into the normal stack overflow check. // 设置gp.stackguard0 = stackPreempt,从而能够触发morestack gp.stackguard0 = stackPreempt returntrue }