【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)