本文基于 go version go1.14.3 darwin/amd64
, 不同操作系统启动流程有差异
Go语言启动时首先需要初始化自己的运行时(runtime), 入口文件为 src/runtime/rt0_darwin_amd64.s
1 2
| TEXT _rt0_amd64_darwin(SB),NOSPLIT,$-8 JMP _rt0_amd64(SB)
|
紧接着,跳转到 src/runtime/asm_amd64.s
中的 _rt0_amd64
函数.
1 2 3 4
| TEXT _rt0_amd64(SB),NOSPLIT,$-8 MOVQ 0(SP), DI // argc LEAQ 8(SP), SI // argv JMP runtime·rt0_go(SB)
|
函数前两行指令将 argc
, argc
两个操作系统传入的参数分别存储到了 DI
, SI
寄存器中,然后跳转到 runtime·rt0_go
中继续执行.
初始化函数
runtime·rt0_go
函数完成了go启动时所需的所有初始化工作
参数初始化
1 2 3 4 5 6 7
| // 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)
|
将存储的argc
, argv
参数分别放到 AX
, BX
寄存器中,同时减小栈指针且将栈指针进行16位对齐, 最后将参数从寄存器放回栈中.
初始化g0栈信息
1 2 3 4 5 6 7 8
| // 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)
|
此部分程序首先将全局变量 g0
地址存入寄存器 DI
中, 然后在系统线程的栈中为 g0
申请栈空间(64*1024 + 104),初始化g0的栈信息和stackgard.
CPU信息相关
1 2 3 4 5 6 7
| // find out information about the processor we're on MOVL $0, AX CPUID MOVL AX, SI CMPL AX, $0 JE nocpuinfo (...)
|
这段代码主要是与CPU相关的查找和初始化
TLS 与 m0 g0
1 2 3 4 5 6 7 8 9 10
| LEAQ runtime·m0+m_tls(SB), DI CALL runtime·settls(SB)
// store through it, to make sure it works get_tls(BX) MOVQ $0x123, g(BX) MOVQ runtime·m0+m_tls(SB), AX CMPQ AX, $0x123 JEQ 2(PC) CALL runtime·abort(SB)
|
调用 runtime·settls
函数来初始化主线程的TLS,目的是把 m0
与主线程进行关联,并检查是否正常执行.
1 2 3 4 5 6 7 8 9
| get_tls(BX) LEAQ runtime·g0(SB), CX MOVQ CX, g(BX) // 将g0地址保存到TLS中 LEAQ runtime·m0(SB), AX
// save m->g0 = g0 MOVQ CX, m_g0(AX) // save m0 to g0->m MOVQ AX, g_m(CX)
|
将TLS地址加载到 BX
寄存器中, 将 g0
的地址保存到TLS中, 也就是 m0.tls[0]=&g0
通过 m0.g0 = &g0
, g0.m = &m0
将 m0
与 g0
进行关联, 这样之后在主线程中通过 get_tls
就可以找到 g0
, 通过 g0.m
就可以找到 m0
, 这样就实现了 m0
、 g0
与主线程之间的关联.
运行时类型检查
check
函数主要是进行运行时类型检查, 源码在 src/runtime/runtime1.go
中,可自行参阅.
调度器相关
1
| CALL runtime·schedinit(SB)
|
schedinit
函数进行运行时组件的初始化工作,主要包括:
- 栈、内存分配器、调度器相关初始化
- 限制最大系统线程数量
- 初始化执行栈
- 初始化内存分配器
- 初始化当前系统线程
- 垃圾回收器初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| (...)
// 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)
CALL runtime·abort(SB) // mstart should never return RET (...)
|
调用 runtime·newproc
函数创建一个新的 goroutine 来启动 runtime.mainPC
所指向的函数 runtime.main
.
runtime·mstart
启动调度器的循环调度并对刚刚创建的 goroutine 进行调度.
runtime.main
runtime.main
函数中会加载用户main函数 main.main
, 并在同一个 goroutine 上执行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| func main() { g := getg()
... // 执行栈最大限制:1GB(64位系统)或者 250MB(32位系统) if sys.PtrSize == 8 { maxstacksize = 1000000000 } else { maxstacksize = 250000000 } ...
// 启动系统后台监控(定期垃圾回收、抢占调度等等) if GOARCH != "wasm" { // no threads on wasm yet, so no sysmon systemstack(func() { newm(sysmon, nil) }) } ...
// 初始化init // initTask 的实现见 src/cmd/compile/internal/gc/init.go 中 fninit doInit(&main_inittask)
// 启动GC清扫工作 gcenable() ...
// 执行用户 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) ... }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| _rt0_amd64_darwin -> _rt0_amd64 -> runtime·rt0_go -> runtime.main -> exit(0) ↓ ↓ runtime·check doInit ↓ ↓ runtime·args gcenable ↓ ↓ runtime·osinit main.main ↓ runtime·schedinit ↓ runtime·newproc ↓ runtime·mstart
|
本文链接: https://overstack.me/202007/go-program-startup-process.html
版权声明: 本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。