探索golang程序启动过程
go version == 1.9.3
GOARCH=“amd64”
GOOS=“darwin”
本文探索下go程序是如何启动起来的。
测试工程
|
|
使用gdb跟踪查看程序启动流程
通过设置断点,可以定位到每个方法的源码文件路径
|
|
针对不同的平台都有各自的特定的汇编文件,我这里通过rt0_darwin_amd64.s定位到runtime.rt0_go方法。先来看下asm_amd64.s的源码。文件很大,省略部分代码,留下初始化过程的重要步骤。
注意:汇编的runtime·schedinit(SB)写法需要转换为runtime.schedinit(即中间的点不同)。官方有解释:
In Go object files and binaries, the full name of a symbol is the package path followed by a period and the symbol name:
fmt.Printf
ormath/rand.Int
. Because the assembler’s parser treats period and slash as punctuation, those strings cannot be used directly as identifier names. Instead, the assembler allows the middle dot character U+00B7 and the division slash U+2215 in identifiers and rewrites them to plain period and slash. Within an assembler source file, the symbols above are written asfmt·Printf
andmath∕rand·Int
.
|
|
按顺序总结下runtime.rt0_go里几件重要的事:
- 检查运行平台的CPU,设置好程序运行需要相关标志。
- TLS的初始化。
- runtime.args、runtime.osinit、runtime.schedinit 三个方法做好程序运行需要的各种变量与调度器。
- runtime.newproc创建新的goroutine用于绑定用户写的main方法。
- runtime.mstart开始goroutine的调度。
下面接着针对上面几个runtime函数,粗略探索下干了什么事情。
runtime.args
只做了一件事,就是把二进制文件的绝对路径找出来,并存在os.executablePath里。
按照本文的测试工程:os.executablePath=$GOPATH/test/main
|
|
runtime.osinit
获取CPU核数与内存页大小。按照本文的测试工程:
- runtime.ncpu = 8
- runtime.physPageSize = 4096
|
|
runtime.schedinit
这个直接在代码中注释吧
|
|
runtime.newproc
runtime.mstart
mstart方法主要的执行路径是:
mstart -> mstart1 -> schedule -> execute
- mstart做一些栈相关的检查,然后就调用mstart1。
- mstart1先做一些初始化与M相关的工作,例如是信号栈和信号处理函数的初始化。最后调用schedule。
- schedule逻辑是这四个方法里最复杂的。简单来说,就是要找出一个可运行的G,不管是从P本地的G队列、全局调度器的G队列、GC worker、因IO阻塞的G、甚至从别的P里偷。然后传给execute运行。
- execute对传进来的G设置好相关的状态后,就加载G自身记录着的PC、SP等寄存器信息,恢复现场继续执行。