Go sheduler 开始调度循环


func mstart1() {

// 启动过程时 _g_ = m0.g0
    _g_ := getg()

    if _g_ != _g_.m.g0 {
        throw("bad runtime·mstart")
    }

    // Record the caller for use as the top of stack in mcall and
    // for terminating the thread.
    // We're never coming back to mstart1 after we call schedule,
    // so other calls can reuse the current frame.
// 一旦调用 schedule() 函数,永不返回
// 所以栈帧可以被复用
    save(getcallerpc(), getcallersp())
    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()
    }

// 执行启动函数。初始化过程中,fn == nil
    if fn := _g_.m.mstartfn; fn != nil {
        fn()
    }

    if _g_.m != &m0 {
        acquirep(_g_.m.nextp.ptr())
        _g_.m.nextp = 0
    }
// 进入调度循环。永不返回
    schedule()
}

更新到g0的pc 和sp 中,前者指向 mstart1 函数栈上参数的位置,后者则指向 gosave 函数 。

func save(pc, sp uintptr) {
    _g_ := getg()

    _g_.sched.pc = pc
    _g_.sched.sp = sp
    _g_.sched.lr = 0
    _g_.sched.ret = 0
    _g_.sched.g = guintptr(unsafe.Pointer(_g_))
    // We need to ensure ctxt is zero, but can't have a write
    // barrier here. However, it should always already be zero.
    // Assert that.
    if _g_.sched.ctxt != nil {
        badctxt()
    }
}

接下来,进入 schedule 函数

// 执行一轮调度器的工作:找到一个 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)
}

调度器的工作内容就是,选择一个在全局队列(每61次选一次全局)或者本地队列或者其他队列中偷取的方式,选择一个gorountine出来,让后调用execute方法执行它。
func execute(gp *g, inheritTime bool) {
g := getg()

// Assign gp.m before entering _Grunning so running Gs have an
// M.

//将 gp 和 m 关联起来
g.m.curg = gp
gp.m = g.m
//gp的状态切换成_Grunnable
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 完成从g0到gp的真正转换

//cpu执行权的转让以及栈的来回切换
//执行流切换的本质是 cpu寄存器以及函数调用栈的切换
//高级语言搞不定只能用汇编
gogo(&gp.sched)
}
gogo的本质就是替换sp和pc为gp的,执行gp的pc指向的方法。


image.png

你可能感兴趣的:(Go sheduler 开始调度循环)