通过 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)

Posts in this Series