funcmstart1() {// 启动过程时 _g_ = m0.g0 _g_ :=getg()if _g_ != _g_.m.g0 {throw("bad runtime·mstart") }// Record top of stack for use by mcall.// Once we call schedule we're never coming back,// so other calls can reuse this stack space.// // 一旦调用 schedule() 函数,永不返回// 所以栈帧可以被复用gosave(&_g_.m.g0.sched) _g_.m.g0.sched.pc =^uintptr(0) // make sure it is never usedasminit()minit()// ……………………// 执行启动函数。初始化过程中,fn == nilif fn := _g_.m.mstartfn; fn !=nil {fn() }if _g_.m.helpgc !=0 { _g_.m.helpgc =0stopm() } elseif _g_.m !=&m0 {acquirep(_g_.m.nextp.ptr()) _g_.m.nextp =0 }// 进入调度循环。永不返回schedule()}
// void gosave(Gobuf*)
// save state in Gobuf; setjmp
TEXT runtime·gosave(SB), NOSPLIT, $0-8
// 将 gobuf 赋值给 AX
MOVQ buf+0(FP), AX // gobuf
// 取参数地址,也就是 caller 的 SP
LEAQ buf+0(FP), BX // caller's SP
// 保存 caller's SP,再次运行时的栈顶
MOVQ BX, gobuf_sp(AX)
MOVQ 0(SP), BX // caller's PC
// 保存 caller's PC,再次运行时的指令地址
MOVQ BX, gobuf_pc(AX)
MOVQ $0, gobuf_ret(AX)
MOVQ BP, gobuf_bp(AX)
// Assert ctxt is zero. See func save.
MOVQ gobuf_ctxt(AX), BX
TESTQ BX, BX
JZ 2(PC)
CALL runtime·badctxt(SB)
// 获取 tls
get_tls(CX)
// 将 g 的地址存入 BX
MOVQ g(CX), BX
// 保存 g 的地址
MOVQ BX, gobuf_g(AX)
RET
// 执行一轮调度器的工作:找到一个 runnable 的 goroutine,并且执行它
// 永不返回
func schedule() {
// _g_ = 每个工作线程 m 对应的 g0,初始化时是 m0 的 g0
_g_ := getg()
// ……………………
top:
// ……………………
var gp *g
var inheritTime bool
// ……………………
if gp == nil {
// Check the global runnable queue once in a while to ensure fairness.
// Otherwise two goroutines can completely occupy the local runqueue
// by constantly respawning each other.
// 为了公平,每调用 schedule 函数 61 次就要从全局可运行 goroutine 队列中获取
if _g_.m.p.ptr().schedtick%61 == 0 && sched.runqsize > 0 {
lock(&sched.lock)
// 从全局队列最大获取 1 个 gorutine
gp = globrunqget(_g_.m.p.ptr(), 1)
unlock(&sched.lock)
}
}
// 从 P 本地获取 G 任务
if gp == nil {
gp, inheritTime = runqget(_g_.m.p.ptr())
if gp != nil && _g_.m.spinning {
throw("schedule: spinning with local work")
}
}
if gp == nil {
// 从本地运行队列和全局运行队列都没有找到需要运行的 goroutine,
// 调用 findrunnable 函数从其它工作线程的运行队列中偷取,如果偷不到,则当前工作线程进入睡眠
// 直到获取到 runnable goroutine 之后 findrunnable 函数才会返回。
gp, inheritTime = findrunnable() // blocks until work is available
}
// This thread is going to run a goroutine and is not spinning anymore,
// so if it was marked as spinning we need to reset it now and potentially
// start a new spinning M.
if _g_.m.spinning {
resetspinning()
}
if gp.lockedm != nil {
// Hands off own p to the locked m,
// then blocks waiting for a new p.
startlockedm(gp)
goto top
}
// 执行 goroutine 任务函数
// 当前运行的是 runtime 的代码,函数调用栈使用的是 g0 的栈空间
// 调用 execute 切换到 gp 的代码和栈空间去运行
execute(gp, inheritTime)
}
// 调度 gp 在当前 M 上运行
// 如果 inheritTime 为真,gp 执行当前的时间片
// 否则,开启一个新的时间片
//
//go:yeswritebarrierrec
func execute(gp *g, inheritTime bool) {
// g0
_g_ := getg()
// 将 gp 的状态改为 running
casgstatus(gp, _Grunnable, _Grunning)
gp.waitsince = 0
gp.preempt = false
gp.stackguard0 = gp.stack.lo + _StackGuard
if !inheritTime {
// 调度器调度次数增加 1
_g_.m.p.ptr().schedtick++
}
// 将 gp 和 m 关联起来
_g_.m.curg = gp
gp.m = _g_.m
// …………………………
// gogo 完成从 g0 到 gp 真正的切换
// CPU 执行权的转让以及栈的切换
// 执行流的切换从本质上来说就是 CPU 寄存器以及函数调用栈的切换,
// 然而不管是 go 还是 c 这种高级语言都无法精确控制 CPU 寄存器的修改,
// 因而高级语言在这里也就无能为力了,只能依靠汇编指令来达成目的
gogo(&gp.sched)
}
TEXT runtime·gogo(SB), NOSPLIT, $16-8
// 0(FP) 表示第一个参数,即 buf = &gp.sched
MOVQ buf+0(FP), BX // gobuf
// ……………………
MOVQ buf+0(FP), BX
nilctxt:
// DX = gp.sched.g
MOVQ gobuf_g(BX), DX
MOVQ 0(DX), CX // make sure g != nil
get_tls(CX)
// 将 g 放入到 tls[0]
// 把要运行的 g 的指针放入线程本地存储,这样后面的代码就可以通过线程本地存储
// 获取到当前正在执行的 goroutine 的 g 结构体对象,从而找到与之关联的 m 和 p
// 运行这条指令之前,线程本地存储存放的是 g0 的地址
MOVQ DX, g(CX)
// 把 CPU 的 SP 寄存器设置为 sched.sp,完成了栈的切换
MOVQ gobuf_sp(BX), SP // restore SP
// 恢复调度上下文到CPU相关寄存器
MOVQ gobuf_ret(BX), AX
MOVQ gobuf_ctxt(BX), DX
MOVQ gobuf_bp(BX), BP
// 清空 sched 的值,因为我们已把相关值放入 CPU 对应的寄存器了,不再需要,这样做可以少 GC 的工作量
MOVQ $0, gobuf_sp(BX) // clear to help garbage collector
MOVQ $0, gobuf_ret(BX)
MOVQ $0, gobuf_ctxt(BX)
MOVQ $0, gobuf_bp(BX)
// 把 sched.pc 值放入 BX 寄存器
MOVQ gobuf_pc(BX), BX
// JMP 把 BX 寄存器的包含的地址值放入 CPU 的 IP 寄存器,于是,CPU 跳转到该地址继续执行指令
JMP BX