go 启动过程

Created

2024-10-28 13:25:54

Updated

2024-10-28 13:25:58

Tip

TODO中…. 勿看

1 找到程序入口函数

go build -o main main.go
readelf -h main
查看 Entry point address
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x456ca0
  Start of program headers:          64 (bytes into file)
  Start of section headers:          456 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         7
  Size of section headers:           64 (bytes)
  Number of section headers:         23
  Section header string table index: 3
# 查看go 程序执行的入口
readelf -s main|grep 456ca0
    1034: 0000000000456ca0     5 FUNC    GLOBAL DEFAULT    1 _rt0_amd64_linux
item description
rt0 runtime0
go/src/runtime/rt0_linux_amd64.s
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

#include "textflag.h"

TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8
    JMP _rt0_amd64(SB)

TEXT _rt0_amd64_linux_lib(SB),NOSPLIT,$0
    JMP _rt0_amd64_lib(SB)
我们可以用dlv调试看到入口
dlv exec main
(dlv) l
> _rt0_amd64_linux() /usr/local/go/src/runtime/rt0_linux_amd64.s:8 (PC: 0x4567c0)
Warning: debugging optimized function
     3: // license that can be found in the LICENSE file.
     4:
     5: #include "textflag.h"
     6:
     7: TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8
=>   8:     JMP _rt0_amd64(SB)
     9:
    10: TEXT _rt0_amd64_linux_lib(SB),NOSPLIT,$0
    11:     JMP _rt0_amd64_lib(SB)
go/src/runtime/asm_amd64.s
// _rt0_amd64 is common startup code for most amd64 systems when using
// internal linking. This is the entry point for the program from the
// kernel for an ordinary -buildmode=exe program. The stack holds the
// number of arguments and the C-style argv.
TEXT _rt0_amd64(SB),NOSPLIT,$-8
    MOVQ    0(SP), DI   // argc  参数个数
    LEAQ    8(SP), SI   // argv  调用命令时的参数
    JMP runtime·rt0_go(SB)

TEXT runtime·rt0_go(SB),NOSPLIT|TOPFRAME,$0
    // 将参数复制到栈上
    MOVQ    DI, AX      // argc
    MOVQ    SI, BX      // argv
    SUBQ    $(5*8), SP      // 3args 2auto
    ANDQ    $~15, SP
    MOVQ    AX, 24(SP)
    MOVQ    BX, 32(SP)

    // 创建go程序的第一个协程 g0,用来调度其他协程
    MOVQ    $runtime·g0(SB), DI
    LEAQ    (-64*1024+104)(SP), BX
    MOVQ    BX, g_stackguard0(DI)
    MOVQ    BX, g_stackguard1(DI)
    MOVQ    BX, (g_stack+stack_lo)(DI)
    MOVQ    SP, (g_stack+stack_hi)(DI)
    // ...
    // ...
    // ...
    // 运行时 检查
    CALL    runtime·check(SB)

    MOVL    24(SP), AX      // copy argc
    MOVL    AX, 0(SP)
    MOVQ    32(SP), AX      // copy argv
    MOVQ    AX, 8(SP)
    CALL    runtime·args(SB) // 设置参数
    CALL    runtime·osinit(SB) // cpu 核心等
    CALL    runtime·schedinit(SB) // 初始化调度器

    MOVQ    $runtime·mainPC(SB), AX     // runtime.main 函数地址
    PUSHQ   AX
    CALL    runtime·newproc(SB) // 启动一个协程
    POPQ    AX

    // start this M
    CALL    runtime·mstart(SB)  // GMP中的M

    CALL    runtime·abort(SB)   // mstart should never return
    RET

// mainPC is a function value for runtime.main, to be passed to newproc.
// The reference to runtime.main is made via ABIInternal, since the
// actual function (not the ABI0 wrapper) is needed by newproc.
DATA    runtime·mainPC+0(SB)/8,$runtime·main<ABIInternal>(SB)

2 验证一下参数

dlv exec main a b
# 打断点到JMP   runtime·rt0_go(SB), 路径看自己的改改
b /usr/local/go/src/runtime/asm_amd64.s:18 # 在18行, 看自己的代码实际情况
# 用si 可以一个指令一个指令的执行
c # 运行
regs # 查看寄存器
    Rsi = 0x00007ffcde282508 # 是参数 ( 存的是字符数组的地址)
    Rdi = 0x0000000000000003 # 参数个数, ok的
p unsafe.Pointer(*(*uintptr)(0x00007ffcde282508))
    unsafe.Pointer(0x7ffcde2827dc)
p *(*uint8)(0x00007FFCDE2827DC)
p *(*uint8)(0x00007FFCDE2827DD)
# 这里我一个字节一个字节的打 ,结果是
47 114 111 111 116 47 116 109 112 47 116 101 115 116 50 47 116 116 51 47 109 97 105 110 0 97 0 98
# ===> 路径有点哈哈.. 徒增自己的麻烦...
# ascii 0 表示空字符,在c 语言中 作为字符串的结尾
/root/tmp/test2/tt3/main[NUL]a[NUL]b[NUL]

3 runtime.check

src/runtime1.go#runtime.check()
func check() {
    var (
        a     int8
        b     uint8
        c     int16
        d     uint16
        e     int32
        f     uint32
        g     int64
        h     uint64
        i, i1 float32
        j, j1 float64
        k     unsafe.Pointer
        l     *uint16
        m     [4]byte
    )
    type x1t struct {
        x uint8
    }
    type y1t struct {
        x1 x1t
        y  uint8
    }
    var x1 x1t
    var y1 y1t
    // 各种类型长度检查
    if unsafe.Sizeof(a) != 1 {
        throw("bad a")
    }
    if unsafe.Sizeof(b) != 1 {
        throw("bad b")
    }
    if unsafe.Sizeof(c) != 2 {
        throw("bad c")
    }
    if unsafe.Sizeof(d) != 2 {
        throw("bad d")
    }
    if unsafe.Sizeof(e) != 4 {
        throw("bad e")
    }
    if unsafe.Sizeof(f) != 4 {
        throw("bad f")
    }
    if unsafe.Sizeof(g) != 8 {
        throw("bad g")
    }
    if unsafe.Sizeof(h) != 8 {
        throw("bad h")
    }
    if unsafe.Sizeof(i) != 4 {
        throw("bad i")
    }
    if unsafe.Sizeof(j) != 8 {
        throw("bad j")
    }
    if unsafe.Sizeof(k) != goarch.PtrSize {
        throw("bad k")
    }
    if unsafe.Sizeof(l) != goarch.PtrSize {
        throw("bad l")
    }
    if unsafe.Sizeof(x1) != 1 {
        throw("bad unsafe.Sizeof x1")
    }
    // 结构体偏移量和长度检查
    if unsafe.Offsetof(y1.y) != 1 {
        throw("bad offsetof y1.y")
    }
    if unsafe.Sizeof(y1) != 2 {
        throw("bad unsafe.Sizeof y1")
    }

    if timediv(12345*1000000000+54321, 1000000000, &e) != 12345 || e != 54321 {
        throw("bad timediv")
    }

    // cas ,atomic操作 检查
    var z uint32
    z = 1
    if !atomic.Cas(&z, 1, 2) {
        throw("cas1")
    }
    if z != 2 {
        throw("cas2")
    }

    z = 4
    if atomic.Cas(&z, 5, 6) {
        throw("cas3")
    }
    if z != 4 {
        throw("cas4")
    }

    z = 0xffffffff
    if !atomic.Cas(&z, 0xffffffff, 0xfffffffe) {
        throw("cas5")
    }
    if z != 0xfffffffe {
        throw("cas6")
    }

    m = [4]byte{1, 1, 1, 1}
    atomic.Or8(&m[1], 0xf0)
    if m[0] != 1 || m[1] != 0xf1 || m[2] != 1 || m[3] != 1 {
        throw("atomicor8")
    }

    m = [4]byte{0xff, 0xff, 0xff, 0xff}
    atomic.And8(&m[1], 0x1)
    if m[0] != 0xff || m[1] != 0x1 || m[2] != 0xff || m[3] != 0xff {
        throw("atomicand8")
    }

    //指针操作检查
    *(*uint64)(unsafe.Pointer(&j)) = ^uint64(0)
    if j == j {
        throw("float64nan")
    }
    if !(j != j) {
        throw("float64nan1")
    }

    *(*uint64)(unsafe.Pointer(&j1)) = ^uint64(1)
    if j == j1 {
        throw("float64nan2")
    }
    if !(j != j1) {
        throw("float64nan3")
    }

    *(*uint32)(unsafe.Pointer(&i)) = ^uint32(0)
    if i == i {
        throw("float32nan")
    }
    if i == i {
        throw("float32nan1")
    }

    *(*uint32)(unsafe.Pointer(&i1)) = ^uint32(1)
    if i == i1 {
        throw("float32nan2")
    }
    if i == i1 {
        throw("float32nan3")
    }

    testAtomic64()
    // 栈大小是否是2的幂次方
    if _FixedStack != round2(_FixedStack) {
        throw("FixedStack is not power-of-2")
    }

    if !checkASM() {
        throw("assembly checks failed")
    }
}

4 runtime.args()

设置参数
func args(c int32, v **byte) {
    argc = c
    argv = v
    sysargs(c, v)
}

5 runtime.osinit()

获取cpu核心数
func osinit() {
    ncpu = getncpu()
    physPageSize = getPageSize()
    osinit_hack()
}

6 runtime·schedinit

func schedinit() {
    lockInit(&sched.lock, lockRankSched)
    lockInit(&sched.sysmonlock, lockRankSysmon)
    lockInit(&sched.deferlock, lockRankDefer)
    lockInit(&sched.sudoglock, lockRankSudog)
    lockInit(&deadlock, lockRankDeadlock)
    lockInit(&paniclk, lockRankPanic)
    lockInit(&allglock, lockRankAllg)
    lockInit(&allpLock, lockRankAllp)
    lockInit(&reflectOffs.lock, lockRankReflectOffs)
    lockInit(&finlock, lockRankFin)
    lockInit(&trace.bufLock, lockRankTraceBuf)
    lockInit(&trace.stringsLock, lockRankTraceStrings)
    lockInit(&trace.lock, lockRankTrace)
    lockInit(&cpuprof.lock, lockRankCpuprof)
    lockInit(&trace.stackTab.lock, lockRankTraceStackTab)
    // Enforce that this lock is always a leaf lock.
    // All of this lock's critical sections should be
    // extremely short.
    lockInit(&memstats.heapStats.noPLock, lockRankLeafRank)

    // raceinit must be the first call to race detector.
    // In particular, it must be done before mallocinit below calls racemapshadow.
    gp := getg()
    if raceenabled {
        gp.racectx, raceprocctx0 = raceinit()
    }
    // 系统M线程的最大数量
    sched.maxmcount = 10000

    // The world starts stopped.
    worldStopped()

    moduledataverify()
    stackinit()  //栈初始化
    mallocinit()  // 内存分配器初始化
    godebug := getGodebugEarly()
    initPageTrace(godebug) // must run after mallocinit but before anything allocates
    cpuinit(godebug)       // must run before alginit
    // 算法初始化
    alginit()              // maps, hash, fastrand must not be used before this call
    fastrandinit()         // must run before mcommoninit
    mcommoninit(gp.m, -1)
    modulesinit()   // provides activeModules
    typelinksinit() // uses maps, activeModules
    itabsinit()     // uses activeModules
    stkobjinit()    // must run before GC starts

    sigsave(&gp.m.sigmask)
    initSigmask = gp.m.sigmask

    goargs() // 加载参数
    goenvs() // 加载环境变量
    secure()
    parsedebugvars()
    gcinit()

    // if disableMemoryProfiling is set, update MemProfileRate to 0 to turn off memprofile.
    // Note: parsedebugvars may update MemProfileRate, but when disableMemoryProfiling is
    // set to true by the linker, it means that nothing is consuming the profile, it is
    // safe to set MemProfileRate to 0.
    if disableMemoryProfiling {
        MemProfileRate = 0
    }

    lock(&sched.lock)
    sched.lastpoll.Store(nanotime())
    procs := ncpu
    if n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 {
        procs = n
    }
    if procresize(procs) != nil {
        throw("unknown runnable goroutine during bootstrap")
    }
    unlock(&sched.lock)

    // World is effectively started now, as P's can run.
    worldStarted()

    // For cgocheck > 1, we turn on the write barrier at all times
    // and check all pointer writes. We can't do this until after
    // procresize because the write barrier needs a P.
    if debug.cgocheck > 1 {
        writeBarrier.cgo = true
        writeBarrier.enabled = true
        for _, pp := range allp {
            pp.wbBuf.reset()
        }
    }

    if buildVersion == "" {
        // Condition should never trigger. This code just serves
        // to ensure runtime·buildVersion is kept in the resulting binary.
        buildVersion = "unknown"
    }
    if len(modinfo) == 1 {
        // Condition should never trigger. This code just serves
        // to ensure runtime·modinfo is kept in the resulting binary.
        modinfo = ""
    }
}

7 newproc

// Create a new g running fn.
// Put it on the queue of g's waiting to run.
// The compiler turns a go statement into a call to this.
func newproc(fn *funcval) {
    gp := getg()
    pc := getcallerpc()
    systemstack(func() {
        newg := newproc1(fn, gp, pc)

        pp := getg().m.p.ptr()
        runqput(pp, newg, true)

        if mainStarted {
            wakep()
        }
    })
}
// Create a new g in state _Grunnable, starting at fn. callerpc is the
// address of the go statement that created this. The caller is responsible
// for adding the new g to the scheduler.
func newproc1(fn *funcval, callergp *g, callerpc uintptr) *g {
    if fn == nil {
        fatal("go of nil func value")
    }

    mp := acquirem() // disable preemption because we hold M and P in local vars.
    pp := mp.p.ptr()
    newg := gfget(pp)
    if newg == nil {
        newg = malg(_StackMin)
        casgstatus(newg, _Gidle, _Gdead)
        allgadd(newg) // publishes with a g->status of Gdead so GC scanner doesn't look at uninitialized stack.
    }
    if newg.stack.hi == 0 {
        throw("newproc1: newg missing stack")
    }

    if readgstatus(newg) != _Gdead {
        throw("newproc1: new g is not Gdead")
    }

    totalSize := uintptr(4*goarch.PtrSize + sys.MinFrameSize) // extra space in case of reads slightly beyond frame
    totalSize = alignUp(totalSize, sys.StackAlign)
    sp := newg.stack.hi - totalSize
    spArg := sp
    if usesLR {
        // caller's LR
        *(*uintptr)(unsafe.Pointer(sp)) = 0
        prepGoExitFrame(sp)
        spArg += sys.MinFrameSize
    }

    memclrNoHeapPointers(unsafe.Pointer(&newg.sched), unsafe.Sizeof(newg.sched))
    newg.sched.sp = sp
    newg.stktopsp = sp
    newg.sched.pc = abi.FuncPCABI0(goexit) + sys.PCQuantum // +PCQuantum so that previous instruction is in same function
    newg.sched.g = guintptr(unsafe.Pointer(newg))
    gostartcallfn(&newg.sched, fn)
    newg.gopc = callerpc
    newg.ancestors = saveAncestors(callergp)
    newg.startpc = fn.fn
    if isSystemGoroutine(newg, false) {
        sched.ngsys.Add(1)
    } else {
        // Only user goroutines inherit pprof labels.
        if mp.curg != nil {
            newg.labels = mp.curg.labels
        }
        if goroutineProfile.active {
            // A concurrent goroutine profile is running. It should include
            // exactly the set of goroutines that were alive when the goroutine
            // profiler first stopped the world. That does not include newg, so
            // mark it as not needing a profile before transitioning it from
            // _Gdead.
            newg.goroutineProfiled.Store(goroutineProfileSatisfied)
        }
    }
    // Track initial transition?
    newg.trackingSeq = uint8(fastrand())
    if newg.trackingSeq%gTrackingPeriod == 0 {
        newg.tracking = true
    }
    casgstatus(newg, _Gdead, _Grunnable)
    gcController.addScannableStack(pp, int64(newg.stack.hi-newg.stack.lo))

    if pp.goidcache == pp.goidcacheend {
        // Sched.goidgen is the last allocated id,
        // this batch must be [sched.goidgen+1, sched.goidgen+GoidCacheBatch].
        // At startup sched.goidgen=0, so main goroutine receives goid=1.
        pp.goidcache = sched.goidgen.Add(_GoidCacheBatch)
        pp.goidcache -= _GoidCacheBatch - 1
        pp.goidcacheend = pp.goidcache + _GoidCacheBatch
    }
    newg.goid = pp.goidcache
    pp.goidcache++
    if raceenabled {
        newg.racectx = racegostart(callerpc)
        newg.raceignore = 0
        if newg.labels != nil {
            // See note in proflabel.go on labelSync's role in synchronizing
            // with the reads in the signal handler.
            racereleasemergeg(newg, unsafe.Pointer(&labelSync))
        }
    }
    if trace.enabled {
        traceGoCreate(newg, newg.startpc)
    }
    releasem(mp)

    return newg
}

8 runtime.main

src/runtime/proc.go
// The main goroutine.
func main() {
    mp := getg().m

    // Racectx of m0->g0 is used only as the parent of the main goroutine.
    // It must not be used for anything else.
    mp.g0.racectx = 0

    // Max stack size is 1 GB on 64-bit, 250 MB on 32-bit.
    if goarch.PtrSize == 8 {
        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
        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 mp != &m0 {
        throw("runtime.main not on m0")
    }

    // 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
    fn()  // 调用我们代码写的main函数了.
    if raceenabled {
        runExitHooks(0) // run hooks now, since racefini does not return
        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 runningPanicDefers.Load() != 0 {
        // Running deferred functions should not take long.
        for c := 0; c < 1000; c++ {
            if runningPanicDefers.Load() == 0 {
                break
            }
            Gosched()
        }
    }
    if panicking.Load() != 0 {
        gopark(nil, nil, waitReasonPanicWait, traceEvGoStop, 1)
    }
    runExitHooks(0)

    exit(0)
    for {
        var x *int32
        *x = 0
    }
}
fn := main_main 编译指示中说过
// main.main main包的main函数
//
//go:linkname main_main main.main
func main_main()

9 mstart

// mstart is the entry-point for new Ms.
// It is written in assembly, uses ABI0, is marked TOPFRAME, and calls mstart0.
func mstart()

// 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() {
    gp := getg()

    osStack := gp.stack.lo == 0
    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 := gp.stack.hi
        if size == 0 {
            size = 8192 * sys.StackGuardMultiplier
        }
        gp.stack.hi = uintptr(noescape(unsafe.Pointer(&size)))
        gp.stack.lo = gp.stack.hi - size + 1024
    }
    // Initialize stack guard so that we can start calling regular
    // Go code.
    gp.stackguard0 = gp.stack.lo + _StackGuard
    // This is the g0, so we can also call go:systemstack
    // functions, which check stackguard1.
    gp.stackguard1 = gp.stackguard0
    mstart1()

    // Exit this thread.
    if mStackIsSystemAllocated() {
        // Windows, Solaris, illumos, Darwin, AIX and Plan 9 always system-allocate
        // the stack, but put it in gp.stack before mstart,
        // so the logic above hasn't set osStack yet.
        osStack = true
    }
    mexit(osStack)
}
// 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() {
    gp := getg()

    if gp != gp.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.
    gp.sched.g = guintptr(unsafe.Pointer(gp))
    gp.sched.pc = getcallerpc()
    gp.sched.sp = getcallersp()

    asminit()
    minit()

    // Install signal handlers; after minit so that minit can
    // prepare the thread to be able to handle the signals.
    if gp.m == &m0 {
        mstartm0()
    }

    if fn := gp.m.mstartfn; fn != nil {
        fn()
    }

    if gp.m != &m0 {
        acquirep(gp.m.nextp.ptr())
        gp.m.nextp = 0
    }
    schedule()
}

10 schedule()

// One round of scheduler: 找到一个可运行的goroutine并运行它
// Never returns.
func schedule() {
    mp := getg().m

    if mp.locks != 0 {
        throw("schedule: holding locks")
    }

    if mp.lockedg != 0 {
        stoplockedm()
        execute(mp.lockedg.ptr(), false) // Never returns.
    }

    // We should not schedule away from a g that is executing a cgo call,
    // since the cgo call is using the m's g0 stack.
    if mp.incgo {
        throw("schedule: in cgo")
    }

top:
    pp := mp.p.ptr()
    pp.preempt = false

    // Safety check: if we are spinning, the run queue should be empty.
    // Check this before calling checkTimers, as that might call
    // goready to put a ready goroutine on the local run queue.
    if mp.spinning && (pp.runnext != 0 || pp.runqhead != pp.runqtail) {
        throw("schedule: spinning with local work")
    }
    // 找到一个可运行的goroutine
    gp, inheritTime, tryWakeP := 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 mp.spinning {
        resetspinning()
    }

    if sched.disable.user && !schedEnabled(gp) {
        // Scheduling of this goroutine is disabled. Put it on
        // the list of pending runnable goroutines for when we
        // re-enable user scheduling and look again.
        lock(&sched.lock)
        if schedEnabled(gp) {
            // Something re-enabled scheduling while we
            // were acquiring the lock.
            unlock(&sched.lock)
        } else {
            sched.disable.runnable.pushBack(gp)
            sched.disable.n++
            unlock(&sched.lock)
            goto top
        }
    }

    // If about to schedule a not-normal goroutine (a GCworker or tracereader),
    // wake a P if there is one.
    if tryWakeP {
        wakep()
    }
    if gp.lockedm != 0 {
        // Hands off own p to the locked m,
        // then blocks waiting for a new p.
        startlockedm(gp)
        goto top
    }
    // 执行
    execute(gp, inheritTime)
}

11 execute

// 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) {
    mp := getg().m

    if goroutineProfile.active {
        // Make sure that gp has had its stack written out to the goroutine
        // profile, exactly as it was when the goroutine profiler first stopped
        // the world.
        tryRecordGoroutineProfile(gp, osyield)
    }

    // Assign gp.m before entering _Grunning so running Gs have an
    // M.
    mp.curg = gp
    gp.m = mp
    casgstatus(gp, _Grunnable, _Grunning)
    gp.waitsince = 0
    gp.preempt = false
    gp.stackguard0 = gp.stack.lo + _StackGuard
    if !inheritTime {
        mp.p.ptr().schedtick++
    }

    // Check whether the profiler needs to be turned on or off.
    hz := sched.profilehz
    if mp.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)
}

12 gogo

src/runtime/asm_amd64.s
/*
 *  go-routine
 */

// func gogo(buf *gobuf)
// restore state from Gobuf; longjmp
TEXT runtime·gogo(SB), NOSPLIT, $0-8
    MOVQ    buf+0(FP), BX       // gobuf
    MOVQ    gobuf_g(BX), DX
    MOVQ    0(DX), CX       // make sure g != nil
    JMP gogo<>(SB)

TEXT gogo<>(SB), NOSPLIT, $0
    get_tls(CX)
    MOVQ    DX, g(CX)
    MOVQ    DX, R14     // set the g register
    MOVQ    gobuf_sp(BX), SP    // 将goroutine 结构中存的 自己的栈指针 设置到sp
    MOVQ    gobuf_ret(BX), AX
    MOVQ    gobuf_ctxt(BX), DX
    MOVQ    gobuf_bp(BX), BP
    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)
    // 这个pc, 就是如果你cpu执行一段时间这个goroutine的代码后,切出, 后来又拿到cpu执行,可以知道执行到哪里了
    MOVQ    gobuf_pc(BX), BX // goroutine中 下一个指令的地址
    JMP BX  // 执行这个指令

13 goexit

src/runtime/asm_amd64.s
// The top-most function running on a goroutine
// returns to goexit+PCQuantum.
TEXT runtime·goexit(SB),NOSPLIT|TOPFRAME,$0-0
    BYTE    $0x90   // NOP
    CALL    runtime·goexit1(SB) // does not return
    // traceback from goexit1 must hit code range of goexit
    BYTE    $0x90   // NOP
runtime/proc.go

func goexit1() {
    if raceenabled {
        racegoend()
    }
    if trace.enabled {
        traceGoEnd()
    }
    // 从当前的g 切换到g0的 栈, 然后执行该 goexit0 方法
    mcall(goexit0)
}
// goexit continuation on g0.
// 传递的是goroutine, goroutine运行完毕做哪些补充清理工作,修改状态等
func goexit0(gp *g) {
    mp := getg().m
    pp := mp.p.ptr()

    casgstatus(gp, _Grunning, _Gdead)
    gcController.addScannableStack(pp, -int64(gp.stack.hi-gp.stack.lo))
    if isSystemGoroutine(gp, false) {
        sched.ngsys.Add(-1)
    }
    gp.m = nil
    locked := gp.lockedm != 0
    gp.lockedm = 0
    mp.lockedg = 0
    gp.preemptStop = false
    gp.paniconfault = false
    gp._defer = nil // should be true already but just in case.
    gp._panic = nil // non-nil for Goexit during panic. points at stack-allocated data.
    gp.writebuf = nil
    gp.waitreason = waitReasonZero
    gp.param = nil
    gp.labels = nil
    gp.timer = nil

    if gcBlackenEnabled != 0 && gp.gcAssistBytes > 0 {
        // Flush assist credit to the global pool. This gives
        // better information to pacing if the application is
        // rapidly creating an exiting goroutines.
        assistWorkPerByte := gcController.assistWorkPerByte.Load()
        scanCredit := int64(assistWorkPerByte * float64(gp.gcAssistBytes))
        gcController.bgScanCredit.Add(scanCredit)
        gp.gcAssistBytes = 0
    }

    dropg()

    if GOARCH == "wasm" { // no threads yet on wasm
        gfput(pp, gp)
        schedule() // never returns
    }

    if mp.lockedInt != 0 {
        print("invalid m->lockedInt = ", mp.lockedInt, "\n")
        throw("internal lockOSThread error")
    }
    gfput(pp, gp)
    if locked {
        // The goroutine may have locked this thread because
        // it put it in an unusual kernel state. Kill it
        // rather than returning it to the thread pool.

        // Return to mstart, which will release the P and exit
        // the thread.
        if GOOS != "plan9" { // See golang.org/issue/22227.
            gogo(&mp.g0.sched)
        } else {
            // Clear lockedExt on plan9 since we may end up re-using
            // this thread.
            mp.lockedExt = 0
        }
    }
    // 最后重新开始调度, 选一个goroutine 来执行
    schedule()
}
Back to top