美文网首页go语言
go 引导、启动与初始化

go 引导、启动与初始化

作者: 链人成长chainerup | 来源:发表于2019-09-15 22:30 被阅读0次

    本文是《循序渐进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” 提问留言,或者加入知识星球“链人成长” 与我深度链接~

    相关文章

      网友评论

        本文标题:go 引导、启动与初始化

        本文链接:https://www.haomeiwen.com/subject/jciiyctx.html