【Free Style】深入Go语言模型6:Go进程启动过程分析

举报
霜雯 发表于 2017/11/09 11:29:55 2017/11/09
【摘要】 Go语言程序初始化过程总览在Go语言链接的时候会,会生成程序入口点符号,在cmd\link\internal\ld\lib.go中if *flagEntrySymbol == "" { switch Buildmode { case BuildmodeCShared, BuildmodeCArchive: *flagEntrySymbol = fm

image.png

如果我们链接的是可执行文件,那么生成的是_rt0_%s_%s这样的字符串, 我们执行go env:
image.png

在我的Windows7-64机器上,找到GOARCH和GOOS,那么程序的入口点就是_rt0_amd64_windows

整个go进程初始化过程如下:
image.png

初始化

_rt0_amd64_windows

在rt0_windows_amd64.s中:

TEXT _rt0_amd64_windows(SB),NOSPLIT,$-8
    LEAQ    8(SP), SI // argv
    MOVQ    0(SP), DI // argc
    MOVQ    $main(SB), AX
    JMP    AX

这样就跳转到了main函数

TEXT main(SB),NOSPLIT,$-8
    MOVQ    $runtime·rt0_go(SB), AX
    JMP    AX

这样就跳转了rt0_go函数。

rt0_go函数

这个函数也是汇编代码,在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

    // create istack out of the given (operating system) stack.
    // _cgo_init may update stackguard.
    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)

    //然后会获取处理器信息,放在全局变量runtime·cpuid_ecx和runtime·cpuid_edx中
    // Load EAX=1 cpuid flags
    MOVQ    $1, AX
    CPUID
    MOVL    CX, runtime·cpuid_ecx(SB)
    MOVL    DX, runtime·cpuid_edx(SB)

nocpuinfo:    

    // if there is an _cgo_init, call it.
    MOVQ    _cgo_init(SB), AX
    TESTQ    AX, AX
    JZ    needtls
    // g0 already in DI
    MOVQ    DI, CX    // Win64 uses CX for first parameter
    MOVQ    $setg_gcc<>(SB), SI
    CALL    AX

    // update stackguard after _cgo_init
    MOVQ    $runtime·g0(SB), CX
    MOVQ    (g_stack+stack_lo)(CX), AX
    ADDQ    $const__StackGuard, AX
    MOVQ    AX, g_stackguard0(CX)
    MOVQ    AX, g_stackguard1(CX)

    LEAQ    runtime·m0+m_tls(SB), DI
    CALL    runtime·settls(SB)

    // store through it, to make sure it works
    //设置本地线程存储,主要是为了每个goroutine可以得到属于自己全局的对象副本
    get_tls(BX)
    MOVQ    $0x123, g(BX)
    MOVQ    runtime·m0+m_tls(SB), AX
    CMPQ    AX, $0x123
    JEQ 2(PC)
    MOVL    AX, 0    // abort
ok:
    // set the per-goroutine and per-mach "registers"
    get_tls(BX)
    LEAQ    runtime·g0(SB), CX
    MOVQ    CX, g(BX)
    LEAQ    runtime·m0(SB), AX

    // save m->g0 = g0
    MOVQ    CX, m_g0(AX)
    // save m0 to g0->m
    MOVQ    AX, g_m(CX)

    CLD                // convention is D is always left cleared
    CALL    runtime·check(SB)

    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
    CALL    runtime·newproc(SB)
    POPQ    AX
    POPQ    AX

    // start this M
    CALL    runtime·mstart(SB)

    MOVL    $0xf1, 0xf1  // crash
    RET

我们看看几个重点的函数的实现

runtime·schedinit

这个函数其实调用了一堆其他初始化函数:

// 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()
    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()   //将main函数参数argc和argv等复制到了os.Args
    parsedebugvars()
    gcinit()

    sched.lastpoll = uint64(nanotime())
    procs := ncpu
    if n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 {
        procs = n    //根据环境变量GOMAXPROCS决定可用物理线程数目
    }
    if procs > _MaxGomaxprocs {
        procs = _MaxGomaxprocs
    }
    if procre size(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"
    }
}

runtime·newproc和runtime·mstart

MOVQ    $runtime·mainPC(SB), AX        // entry
PUSHQ    AX
PUSHQ    $0            // arg size
CALL    runtime·newproc(SB)

DATA    runtime·mainPC+0(SB)/8,$runtime·main(SB)

将runtime·main函数的地址入栈,然后调用newproc创建一个goroutine来执行runtime.main,但是只是把任务放到就绪线程队列中,靠后面的mstart方法来进行调度goroutine。

main函数调用

从前面可以到,runtime main是通过goroutine调度起来的,我们先看runtime.main的实现
我们在runtime\proc.go中可以看到main函数的实现

// The main goroutine.
func main() {
    g := getg()

    // 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.
    if sys.PtrSize == 8 {
        maxstacksize = 1000000000
    } else {
        maxstacksize = 250000000
    }

    // Record when the world started.
    runtimeInitTime = nanotime()

    //newm新建一个结构体M,第一个参数是这个结构体M的入口函数,也就说会在一个新的物理线程中运行sysmon函数。
    //由此可见sysmon是一个地位非常高的后台任务,整个函数体一个死循环的形式,目前主要处理两个事件:对于网络的epoll以及抢占式调度的检测。
    //sysmon会根据系统当前的繁忙程度睡一小段时间,然后每隔10ms至少进行一次epoll并唤醒相应的goroutine。同时,
    //它还会检测是否有P长时间处于Psyscall状态或Prunning状态,并进行抢占式调度。
    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() // 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")
        }
        cgocall(_cgo_notify_runtime_init_done, nil)
    }

    //执行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()

    if isarchive || islibrary {
        // A program compiled with -buildmode=c-archive or c-shared
        // has a main, but it is not executed.
        return
    }

    //执行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()
    if raceenabled {
        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 issue 3934.
    if panicking != 0 {
        gopark(nil, nil, "panicwait", traceEvGoStop, 1)
    }

    exit(0)
    for {
        var x *int32
        *x = 0
    }
}

注意只有main包里面的main函数才是最终的入口点,其他包里面main函数里面都只是普通的函数,所以我们一直说main.main。
最后启动过程中出现的m,g对象都是goroutine中的管理对象,这个将在下一个章节专门讲:)


【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。