# 5. Go 程序是如何启动起来的?
运行 runtime/rt0_xxx.s 汇编文件
读取命令行参数:复制参数变量 argc 和 argv 到栈上;
TEXT runtime·rt0_go<ABIInternal>(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)
初始化 g0 执行栈:g0 是为了调度协程而产生的协程,是每个 Go 程序的第一个协程;
TEXT runtime·rt0_go<ABIInternal>(SB),NOSPLIT,$0 ... // 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)
运行时检查:
- 类型长度
- 指针操作
- 结构体字段偏移量
- atomic 原子操作
- CAS 操作
- 栈大小是否是 2 的幂次
TEXT runtime·rt0_go<ABIInternal>(SB),NOSPLIT,$0 ... CALL runtime·check(SB)
// runtime/runtime1.go func check() { var ( a int8 ... k unsafe.Pointer l *uint16 m [4]byte ) ... if unsafe.Sizeof(a) != 1 { throw("bad a") } ... }
判断系统字长 & CPU 核数:runtime.osinit
TEXT runtime·rt0_go<ABIInternal>(SB),NOSPLIT,$0 ... CALL runtime·osinit(SB)
// runtime/os_linux.go func osinit() { ncpu = getproccount() physHugePageSize = getHugePageSize() if iscgo { sigdelset(&sigsetAllExiting, 32) sigdelset(&sigsetAllExiting, 33) sigdelset(&sigsetAllExiting, 34) } osArchInit() }
参数初始化 runtime.args:对命令行中的参数进行处理,参数数量复制给 argc(int32),参数值赋值给 argv(**byte)
TEXT runtime·rt0_go<ABIInternal>(SB),NOSPLIT,$0 ... MOVL 16(SP), AX // copy argc MOVL AX, 0(SP) MOVQ 24(SP), AX // copy argv MOVQ AX, 8(SP) CALL runtime·args(SB)
初始化调度器 runtime.schedinit:
- 全局栈空间内存分配
- 堆内存空间的初始化
- 初始化当前系统线程
- 算法初始化(map、hash)
- 加载命令行参数到 os.Args
- 加载操作系统环境变量
- gc 的参数初始化
- 设置 process 的数量
TEXT runtime·rt0_go<ABIInternal>(SB),NOSPLIT,$0 ... CALL runtime·schedinit(SB)
// runtime/proc.go func schedinit() { ... lockInit(&sched.lock, lockRankSched) ... // The world starts stopped. ... goargs() goenvs() parsedebugvars() gcinit() ... }
创建主协程:执行 runtime.main 并将其放入调度器等待调度;
TEXT runtime·rt0_go<ABIInternal>(SB),NOSPLIT,$0 ... // create a new goroutine to start program MOVQ $runtime·mainPC(SB), AX // 去 runtime.main PUSHQ AX PUSHQ $0 // arg size CALL runtime·newproc(SB) // 创建一个协程 POPQ AX POPQ AX // start this M CALL runtime·mstart(SB) // 启动调度器 ... DATA runtime·mainPC+0(SB)/8,$runtime·main<ABIInternal>(SB)
主协程执行主函数 runtime.main,它会执行:
- runtime.init
- 启动 gc 垃圾回收器
- 用户包依赖的 init 方法
- 用户主函数 main.main
// runtime/proc.go // init 方法会率先执行 func init() { go forcegchelper() } func main() { // 1. 执行 runtime.init doInit(&runtime_inittask) // 2. 启动 gc gcenable() ... // 3. 执行用户包依赖的 init 方法 doInit(&main_inittask) ... // 4.执行用户主函数 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() ... }