Go程序启动过程

释放双眼,带上耳机,听听看~!

事实上,编译好的可执⾏⽂件真正执⾏⼊⼜并⾮我们所写的 main.main 函数,因为编译器

总是会插⼊⼀段引导代码,完成诸如命令⾏参数、运⾏时初始化等⼯作,然后才会进⼊⽤

户逻辑。

程序的入口因平台而异:

rt0_android_arm.s rt0_dragonfly_amd64.s rt0_linux_amd64.s ...rt0_darwin_386.s rt0_freebsd_386.s rt0_linux_arm.s ...rt0_darwin_amd64.s rt0_freebsd_amd64.s rt0_linux_arm64.s ...

rt0_linux_amd64.s:

TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8
   LEAQ   8(SP), SI ; argv
   MOVQ   0(SP), DI ; argc
   MOVQ   $main(SB), AX     ;move address of main to ax
   JMP    AX
   
   TEXT main(SB),NOSPLIT,$-8
   MOVQ   $runtime·rt0_go(SB), AX   ;跳转到runtime.rt0.go执行
   JMP    AX

asm_amd64.s:

TEXT runtime·rt0_go(SB),NOSPLIT,$0
    // copy arguments forward on an even stack
    MOVQ    DI, AX      // argc
    MOVQ    SI, BX      // argv
    SUBQ    $(4*8+7), SP        // 2args 2auto
    ANDQ    $~15, SP
    MOVQ    AX, 16(SP)
    MOVQ    BX, 24(SP)
   ..
ok:
    ; set the per-goroutine and per-mach "registers"
    get_tls(BX)
    LEAQ    runtime·g0(SB), CX  ;将g0的地址保存到CX
    MOVQ    CX, g(BX)   ;设置 g(BX)为g0
    LEAQ    runtime·m0(SB), AX  

    // save m->g0 = g0
    MOVQ    CX, m_g0(AX)    ;设置m.g0    // save m0 to g0->m
    MOVQ    AX, g_m(CX) ;设置g.m
    ...
    ;调用初始化函数
    MOVL    16(SP), AX      // copy argc
    MOVL    AX, 0(SP)
    MOVQ    24(SP), AX      // copy argv
    MOVQ    AX, 8(SP)    CALL    runtime·args(SB)        ;    CALL    runtime·osinit(SB)      ;    CALL    runtime·schedinit(SB)   ;    // create a new goroutine to start program
    MOVQ    $runtime·mainPC(SB), AX     // entry
    PUSHQ   AX
    PUSHQ   $0          // arg size
    ;创建一个新的goroutine并加入到等待队列,该goroutine执行runtime.mainPC所指向的函数    CALL    runtime·newproc(SB)
    POPQ    AX
    POPQ    AX

    ;该函数内部会调用调度程序,从而调度到刚刚创建的goroutine执行    CALL    runtime·mstart(SB)

    MOVL    $0xf1, 0xf1  // crash
    RET

;声明全局的变量mainPC为runtime.main函数的地址,该变量为read only
DATA    runtime·mainPC+0(SB)/8,$runtime·main(SB)    
GLOBL   runtime·mainPC(SB),RODATA,$8

runtime1.go:

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

os_windows.go:

func osinit() {
    ...
    ncpu = getproccount()   //获取cpu核数
    ...
}

proc.go:

    // The bootstrap sequence is:
    //
    //  call osinit
    //  call schedinit
    //  make & queue new G
    //  call runtime·mstart
    //
    // The new G calls runtime·main.
    func schedinit() {        // raceinit must be the first call to race detector.
        // In particular, it must be done before mallocinit below calls racemapshadow.
        _g_ := getg()   //获取的是g0
        if raceenabled {
            _g_.racectx, raceprocctx0 = raceinit()
        }        //最大系统线程数量限制
        sched.maxmcount = 10000
    
        tracebackinit()
        moduledataverify()        //栈、内存分配器和调度器的相关初始化
        stackinit()
        mallocinit()
        mcommoninit(_g_.m)
      
        alginit()       // maps must not be used before this call
        modulesinit()   // provides activeModules
        typelinksinit() // uses maps, activeModules
        itabsinit()     // uses activeModules
    
        msigsave(_g_.m)
        initSigmask = _g_.m.sigmask    
        //处理命令行参数和环境变量
        goargs()
        goenvs()        
        //处理 GODEBUG、GOTRACEBACK 调试相关的环境变量设置
        parsedebugvars()      
        //垃圾回收器初始化
        gcinit()
    
        sched.lastpoll = uint64(nanotime())        //通过 CPU核心数和GOMAXPROCS环境变量确定P的数量,P用于调度g到m上
        procs := ncpu        if n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 {
            procs = n
        }        if procs > _MaxGomaxprocs {
            procs = _MaxGomaxprocs
        }        if procresize(procs) != nil {            throw("unknown runnable goroutine during bootstrap")
        }    
        if buildVersion == "" {            // Condition should never trigger. This code just serves
            // to ensure runtime·buildVersion is kept in the resulting binary.
            buildVersion = "unknown"
        }
    }
    // Called to start an M.
    //go:nosplit
    func mstart() {
        ....
        mstart1()
    }
    ```
func mstart1() {
     ...    //调度goroutine
    schedule()
}
    // The main goroutine.
    func main() {
        g := getg() //当前获取的g是刚刚在rt0_go内创建的goroutine
    
        // 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.
        //执行栈最大限制:1GB on 64-bit,250MB on 32-bit
        if sys.PtrSize == 8 {   //64-bit下指针长度是8个字节
            maxstacksize = 1000000000
        } else {
            maxstacksize = 250000000
        }    
        // Allow newproc to start new Ms.
        mainStarted = true
    
        //启动系统后台监控(定期垃圾回收以及并发任务的调度等)
        systemstack(func() {
            newm(sysmon, nil)
        })    
        // 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")
        }    
        //执行runtime包内的所有初始化函数 init
        runtime_init() // must be before defer
        if nanotime() == 0 {            throw("nanotime returning zero")
        }    
        // Defer unlock so that runtime.Goexit during init does the unlock too.
        needUnlock := true
        defer func() {            if needUnlock {
                unlockOSThread()
            }
        }()    
        // Record when the world started. Must be after runtime_init
        // because nanotime on some platforms depends on startNano.
        runtimeInitTime = nanotime()    
        //启动垃圾回收器的后台操作
        gcenable()
    
        main_init_done = make(chan bool)        //执行用户包(包括标准库)的初始化函数 init,程序所有的包的init函数都会在这个函数内被全部执行
        fn := main_init // make an indirect call, as the linker doesn't know the address of the main package when laying down the runtime
        fn()
        close(main_init_done
        needUnlock = false
        unlockOSThread()
    
        //执行用户逻辑入口 main.main 函数
        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()
       ...        //执行结束,程序正常退出
        exit(0)
    }

• 所有 init 函数都在同⼀个 goroutine 内执⾏

• 所有 init 函数结束后才会执⾏ main.main 函数

参考

  • 雨痕的 Go 1.5源码剖析

作者:咖啡加方糖
链接:https://www.jianshu.com/p/e534465c9ff8

【转自慕课】https://www.imooc.com

Go

【golang】HashMap原理和实现

2022-3-3 2:48:43

Go

Go基础系列:构建go程序

2022-3-3 2:52:00

搜索