通过 hello world 寻找 golang 启动过程
知其然,也要知其所以然,从今天开始研究一下golang的底层实现,首先从其启动开始;
找到启动点
1. 写一个hello world.
1package main
2
3import (
4 "fmt"
5)
6
7func main() {
8 fmt.Println("hello world")
9}
2.编译后使用gdb找到entry point
1$ gdb hello
2 .....
3 file type mach-o-x86-64.
4 Entry point: 0x1052720
5 0x0000000001001000 - 0x0000000001093074 is .text
6 0x0000000001093080 - 0x00000000010e19cd is __TEXT.__rodata
7 0x00000000010e19e0 - 0x00000000010e1ae2 is __TEXT.__symbol_stub1
8 0x00000000010e1b00 - 0x00000000010e2764 is __TEXT.__typelink
9 0x00000000010e2768 - 0x00000000010e27d0 is __TEXT.__itablink
10 0x00000000010e27d0 - 0x00000000010e27d0 is __TEXT.__gosymtab
11 0x00000000010e27e0 - 0x000000000115c6ff is __TEXT.__gopclntab
12 0x000000000115d000 - 0x000000000115d158 is __DATA.__nl_symbol_ptr
13 0x000000000115d160 - 0x0000000001169c9c is __DATA.__noptrdata
14 0x0000000001169ca0 - 0x0000000001170610 is .data
15 0x0000000001170620 - 0x000000000118be50 is .bss
16 0x000000000118be60 - 0x000000000118e418 is __DATA.__noptrbss
17(gdb) info symbol 0x1052720
18_rt0_amd64_darwin in section .text
19(gdb)
可以从entry point 找到入口函数 _rt0_amd64_darwin,可以在源码中搜索一下函数名称,定位函数位置 runtime/rt0_darwin_amd64.s:7,具体如下所示
1TEXT _rt0_amd64_darwin(SB),NOSPLIT,$-8
2 JMP _rt0_amd64(SB)
该函数跳转到 _rt0_amd64, _rt0_amd64是一段针对amd64系统的公共启动代码。
1TEXT _rt0_amd64(SB),NOSPLIT,$-8
2 MOVQ 0(SP), DI // argc
3 LEAQ 8(SP), SI // argv
4 JMP runtime·rt0_go(SB)
其中 MOVQ 用来操作数据,而LEAQ 用来操作地址,所以
MOVQ 0(SP), DI 是将argc 放到DI寄存器
LEAQ 8(SP), SI 是将 argv 的地址放到SI寄存器
然后跳转到runtime·rt0_go(SB)(go1.12.5/src/runtime/asm_amd64.s:87)
接下来的流程用下图表示:
参数设置
1TEXT runtime·rt0_go(SB),NOSPLIT,$0
2 // copy arguments forward on an even stack
3 //将argc 和 argv 复制到指定寄存器中
4 MOVQ DI, AX // argc
5 MOVQ SI, BX // argv
6 SUBQ $(4*8+7), SP // 2args 2auto
7 // sp 16 字节对齐
8 ANDQ $~15, SP
9 // 将 argc 复制到 sp+16 , argv 复制到 sp+24
10 MOVQ AX, 16(SP)
11 MOVQ BX, 24(SP)
g0 初始化
1 // create istack out of the given (operating system) stack.
2 // _cgo_init may update stackguard.
3 // g0 定义在 go1.12.5/src/runtime/proc.go:81
4 // g0.stackguard0 = rsp-64*1024+104
5 // g0.stackguard1 = g0.stackguard0
6 // g0.stack.lo = g0.stackguard0
7 // g0.stack.hi = rsp
8 MOVQ $runtime·g0(SB), DI
9 LEAQ (-64*1024+104)(SP), BX
10 MOVQ BX, g_stackguard0(DI)
11 MOVQ BX, g_stackguard1(DI)
12 MOVQ BX, (g_stack+stack_lo)(DI)
13 MOVQ SP, (g_stack+stack_hi)(DI)
设置g0的栈信息,设置了栈的地址开始与结束位置,分配大约64k空间。
cgo_init
判断是否存在 _cgo_init ,如果有就执行,执行完之后重新设置g0的栈地址
tls
1#ifdef GOOS_plan9
2 // skip TLS setup on Plan 9
3 JMP ok
4#endif
5#ifdef GOOS_solaris
6 // skip TLS setup on Solaris
7 JMP ok
8#endif
9#ifdef GOOS_darwin
10 // skip TLS setup on Darwin
11 JMP ok
12#endif
13
14 LEAQ runtime·m0+m_tls(SB), DI
15 //settls 位于 go1.12.5/src/runtime/sys_linux_amd64.s:606
16 CALL runtime·settls(SB)
17
18 // store through it, to make sure it works
19 get_tls(BX)
20 MOVQ $0x123, g(BX)
21 MOVQ runtime·m0+m_tls(SB), AX
22 CMPQ AX, $0x123
23 JEQ 2(PC)
24 CALL runtime·abort(SB)
在plan 9, solaris ,darwin 上都直接跳过tls的设置。
runtime.args
1 MOVL 16(SP), AX // copy argc
2 MOVL AX, 0(SP)
3 MOVQ 24(SP), AX // copy argv
4 MOVQ AX, 8(SP)
5 CALL runtime·args(SB)
runtime.args 位于 go1.12.5/src/runtime/runtime1.go:60 主要作用是读取参数以及获取环境变量;
runtime.osinit
主要设置cpu 数量
runtime.schedinit
位于 go1.12.5/src/runtime/proc.go:526 主要作用 初始化堆栈, 参数,gc , sched。
接下来主要是创建一个goroutine,然后放到队列中,启动mstart 进行调度 运行第一个goroutine(runtime.main)