Go程序启动过程

作者: 027f63d16800 | 来源:发表于2018-01-07 01:05 被阅读102次

    事实上,编译好的可执⾏⽂件真正执⾏⼊⼜并⾮我们所写的 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源码剖析

    相关文章

      网友评论

        本文标题:Go程序启动过程

        本文链接:https://www.haomeiwen.com/subject/lwnunxtx.html