【Free Style】深入Go语言模型6:Go进程启动过程分析
【摘要】 Go语言程序初始化过程总览在Go语言链接的时候会,会生成程序入口点符号,在cmd\link\internal\ld\lib.go中if *flagEntrySymbol == "" { switch Buildmode { case BuildmodeCShared, BuildmodeCArchive: *flagEntrySymbol = fm
如果我们链接的是可执行文件,那么生成的是_rt0_%s_%s
这样的字符串, 我们执行go env:
在我的Windows7-64机器上,找到GOARCH和GOOS,那么程序的入口点就是_rt0_amd64_windows
整个go进程初始化过程如下:
初始化
_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)