go 引导、启动与初始化

本文是《循序渐进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

可以得到如下结果:


entrypoint.png

如图所示:入口(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)。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"

得到如下结果:


image.png

应该是有几个没有用到而已。
按照《Go语言学习笔记》的总结:

runtime.init 主要是执行runtime内的init函数。

3.2.4.2 main_init

类似的操作,看下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” 提问留言,或者加入知识星球“链人成长” 与我深度链接~

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容