事实上,编译好的可执⾏⽂件真正执⾏⼊⼜并⾮我们所写的 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
随时随地看视频