本文是《循序渐进go语言》第三篇-go 引导、启动与初始化。本文将结合一个最简单的例子,看下go底层是如何启动的,又是如何执行的。
1 一个小例子
package main
func main() {
println("hello")
}
如此简单的例子,底层是如何执行的呢?
2 gdb出场
先编译一下文件:
go build test.go
生成可执行文件 test
然后我们使用 gdb进行调试。
gdb test
3 流程分析
3.1 入口在哪儿?
首先我们找一下程序的入口:
在gdb环境下,执行如下指令
info files
可以得到如下结果:
如图所示:入口(Entry point )在 0x1047f80。
那我们在entry Point 这个地方打一个断点。
b *0x1047f80
结果是
file /Users/zhangpeng/golang/go/src/runtime/rt0_darwin_amd64.s, line 8
没错,这就是程序的入口,并不是我们想的main.main 函数。
3.2 rt0_darwin_amd64.s 干了什么?
代码比较少,直接贴出来吧
TEXT _rt0_amd64_darwin(SB),NOSPLIT,$-8
LEAQ 8(SP), SI // argv
MOVQ 0(SP), DI // argc
MOVQ $main(SB), AX
JMP AX
设置参数,执行main(SB) 也比较简洁
TEXT main(SB),NOSPLIT,$-8
MOVQ $runtime·rt0_go(SB), AX
JMP AX
那我们看下$runtime·rt0_go 具体的位置在哪儿
(gdb) b runtime.rt0_go
Breakpoint 2 at 0x10447b0: file /Users/zhangpeng/golang/go/src/runtime/asm_amd64.s, line 12.
这儿包含了初始化与执行的核心逻辑。我们先列一下主流程【次要代码省略】,然后一个一个看。
TEXT runtime·rt0_go(SB),NOSPLIT,$0
...
nocgo:
...
BL runtime·check(SB) // 只是做了一些简单的check, 这儿不做分析
...
BL runtime·args(SB)
BL runtime·osinit(SB)
BL runtime·schedinit(SB)
// create a new goroutine to start program
// 创建main goroutine 用于执行runtime.main
MOVD $runtime·mainPC(SB), R0 // entry
...
BL runtime·newproc(SB)
...
// start this M 让当前线程开始执行main goroutine
BL runtime·mstart(SB)
...
看看它们具体在哪儿
(gdb) b runtime.args
Breakpoint 4 at 0x102f540: file /Users/zhangpeng/golang/go/src/runtime/runtime1.go, line 61.
(gdb) b runtime.osinit
Breakpoint 5 at 0x101fd90: file /Users/zhangpeng/golang/go/src/runtime/os_darwin.go, line 48.
(gdb) b runtime.schedinit
Breakpoint 6 at 0x10251d0: file /Users/zhangpeng/golang/go/src/runtime/proc.go, line 463.
(gdb) b runtime.mainPC
Breakpoint 7 at 0x106c928
(gdb) b runtime.mstart
Breakpoint 8 at 0x1026b60: file /Users/zhangpeng/golang/go/src/runtime/proc.go, line 1133.
(gdb) b runtime.main
Breakpoint 9 at 0x1023d20: file /Users/zhangpeng/golang/go/src/runtime/proc.go, line 106.
(gdb) b main.main
Breakpoint 10 at 0x104bfe0: file /Users/zhangpeng/goProject/test.go, line 3.
(gdb) b runtime.newproc
Breakpoint 14 at 0x102af10: file /Users/zhangpeng/golang/go/src/runtime/proc.go, line 2842.
3.2.1 runtime·args
只是将参数复制了一下。
3.2.2 runtime·osinit
做了两件事情:获取cpu核数, 设置pagesize 。 没有做其他事情。
3.2.3 runtime·schedinit
其主要的作用是新定义一个go routine, 去调用runtime.main。
在这个方法里面,做了好多的初始化,这儿我们先简单罗列一下,后续分析到具体模块时,应该还会再回来看这部分。
// 栈、内存分配器、调度器相关初始化
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()
3.2.4 执行runtime.main
核心代码在 /Users/zhangpeng/golang/go/src/runtime/proc.go, line 106.
func main() {
println("welcome to zp'go source world~~~")
g := getg()
...
runtime_init() // must be before defer
...
gcenable() // gc启动
...
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()
...
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()
...
}
3.2.4.1 runtime_init
如《Go语言学习笔记》中所述,我们看看最终生成的代码中有多少runtime.init
go tool objdump -s "runtime\.init\b"
结果是
TEXT runtime.init.1(SB) /Users/zhangpeng/golang/go/src/runtime/cpuflags_amd64.go
...
TEXT runtime.init.2(SB) /Users/zhangpeng/golang/go/src/runtime/mstats.go
...
TEXT runtime.init.3(SB) /Users/zhangpeng/golang/go/src/runtime/panic.go
...
TEXT runtime.init.4(SB) /Users/zhangpeng/golang/go/src/runtime/proc.go
...
TEXT runtime.init.5(SB) /Users/zhangpeng/golang/go/src/runtime/signal_unix.go
...
TEXT runtime.init(SB) /Users/zhangpeng/golang/go/src/runtime/write_err.go
...
看了下,总共上面六处。
我又反查了runtime 文件目录下的init函数
指令
cd $HOME/golang/go/src/runtime
git grep " init() " | grep -v "test"
得到如下结果:
应该是有几个没有用到而已。
按照《Go语言学习笔记》的总结:
runtime.init 主要是执行runtime内的init函数。
3.2.4.2 main_init
类似的操作,看下main.init 到底做了什么?
我们的例子比较简单,《Go语言学习笔记》 上举例一个例子,你可以自己去copy一下代码,然后执行途中的命令,就会发现main.init 做了好几次。
直接给结论吧
main.init 调用非runtime包的初始化参数,包括引用的第三方类库、标准库的init函数。
3.2.4.3 main_main
最后的最后,开始执行main包的main函数。
4 总结
本文使用gdb工具,对一个简单的go demo做了调试。从go引导启动,初始化,一直追到main.main函数。 希望对你有用~
5 参考文献
(1)《Go语言学习笔记》
(2) go 源码
6 其他
本文是《循序渐进go语言》的第三篇-《go 引导、启动与初始化》。
如果有疑问,可以直接留言,也可以关注公众号 “链人成长chainerup” 提问留言,或者加入知识星球“链人成长” 与我深度链接~