Go语言学习 - HelloWorld

847 阅读2分钟

func main() {  
    fmt.Printf("Hello World")
}

这是我接触go的第一个程序, GMP是我接触go学的最新的东西, 这两者是怎么联系在一起的, GMP是怎么发挥作用的, 为什么init能先于任何函数运行. 了解一下boot-strap, 在此之前, 你需要有gdb工具: brew install gdb

lets get to work

开始了, 我们先编译这个简单的小程序: go build -o main main.go, 接着拿着编译出来的main, 开始debug. 我们的目的是需要知道这个二进制文件第一件执行的事是什么, 我们使用gdb工具打它, 文件, 接着查看它的详细信息:

$ gdb main
$ info files


我们最关心的内容是红色圈中的EntryPoint, 这是什么, 如果你看过dockerfile你会知道, 这种东西象征了第一个需要执行的指令, 来看看第一个需要执行的指令是什么, 是你写的main()吗, 通过info symbol查看这个EntryPoint对应的地址是什么


_rt0_amd64_darwin , 这是什么, 打开runtime里面有一大堆.s结尾的汇编文件:

  • 前往_rt0_amd64_darwin.s中的 _rt0_amd64_darwin(SB)函数

  • 这个函数让我们去一个叫做_rt0_amd64的函数, 这个函数在asm_amd64.s

  • 关于这两个函数是什么, 注释中说 在startup的情况下,我们就会首先执行_rt0_amd64(SB)函数, 也就是说你的程序上来毛都不干也会先做这个

  • 这个函数又将我们指引到runtime·rt0_go . ok我们的目的达成了, 这里就是bootstrap, 这里面包含了一些MOVQ LEAQ命令跳过, 一些关于CPU的设置跳过, 我们看看在这些预设完成以后, runtime要做什么

初始化

// 初始化
CALL  runtime·args(SB)
CALL  runtime·osinit(SB)
CALL  runtime·schedinit(SB)​

// 创建一个G放入待运行队列CALL  
runtime·newproc(SB)

​// 让主线程进入调度模式
CALL  runtime·mstart(SB)  
  • 初始化:

    • stackinit / mallocinit : 内存栈初始化

    • mcommoninit: 初始化一个m

    • goargs / goenv : 命令行参数以及环境变量初始化

    • gcinit : GC初始化

    • procresize: 根据我们指定的GOMAXPROCS数量扩容(初始化)全局可用P列表: allp, 并将allp中的p设置成pidle状态

  • 创建G:

    • 将主线程设置成一个G: 通过指定一个需要运行的函数, newproc将创建一个G,用于运行这个函数, 在这里这个函数是指: runtime.main函数, 并不是你写的main函数

    • 这个函数有一个注释: "The main goroutine", 代表这是主函数线程

  • 调度模式开始:

    • M开始运行, 我们在初始化中创建的M, 在这里会开始运行, 从队列中拿出G也就是main goroutine, 开始运行, 前往runtime.main

      • 启用监控线程sysmon

      • 执行runtime.init函数

      • 启用GC

      • 执行标准库, 第三方库中的init函数

      • 执行用户的init函数