Go program 编译过程
package main
import "fmt"
func main() {
fmt.Println("Hello Go")
}
编译前端
-
词法分析
- 将源代码翻译成 Token(最小语义结构)
-
句法分析
- Token 序列经过处理,变成语法树 AST
-
语义分析
- 类型检查
- 类型推断
- 函数调用内联
- 逃逸分析
编译后端
-
中间代码生成(SSA)
- 处理不同平台的差异,生成中间代码
- 代码优化
-
机器码生成
- 生成 Plan9 汇编代码
-
链接
- 将各个包(.a 文件)进行链接,包括 runtime
查看从代码到 SSA 中间码的过程
# windows
$env:GOSSAFUNC="main"
# linux
export GOSSAFUNC="main"
执行
go build main.go
,得到 ssa.html 文件。
从 sourses -> ast -> … -> genssa
查看 Plan9 汇编代码
go build -gcflags -S main.go
Go program 的入口
tips: 第一行代码并非为用户自定义的 main 方法
-
首先进入
%GOROOT%\src\runtime
下的对应的
rt0_xxx.s
汇编文件,如在 windows 系统下,进入
rt0_windows_amd64.s
,执行
JMP _rt0_amd64(SB)
:
-
调用
_rt0_amd64(SB)
方法,该方法位于
%GOROOT%\src\runtime\asm_amd64.s
:
-
该方法将调用命令参数 argc,argc 放入寄存器,随后调用
runtime·rt0_go(SB)
:
- 读取命令行参数。复制参数数量 argc 和参数值 argv 到栈上,参数可能有也可能没有,取决于启动程序时参数是否输入。
-
初始化
g0
执行栈。启动
g0
协程,
g0
是 Go 程序的第一个协程,且不归调度器管理,是为了调度协程而产生的协程。 -
经过一系列判断后,开始运行时检测
runtime.check
,该检测方法位于
%GOROOT%\src\runtime\runtime1.go
。主要检测包括:
- 检查各种类型的长度
- 检查指针操作
- 检查结构体字段的偏移量
- 检查 atomic 原子操作
- 检查 CAS 操作
- 检查栈大小是否为 2 的幂次
- 初始化 runtime.args。对命令行中的参数进行处理,拷贝参数到 go 代码,参数数量赋值给 argc int32,参数值赋值给 argv **byte。
-
执行
runtime·osinit(SB)
判断 cpu 核数,用于后续调度器的初始化
runtime·schedinit(SB)
:- 全局占空间内存分配
- 加载命令行参数到 os.Args
- 堆内存空间的初始化
- 加载操作系统环境变量
- 初始化当前系统线程
- 垃圾回收器的参数初始化
- 算法初始化
- 设置 process 数量
-
取 runtime 主函数地址
MOVQ $runtime·mainPC(SB), AX
,创建启动主协程
runtime·newproc(SB)
,初始化调度器 M
runtime·mstart(SB)
,调度主协程用于运行 runtime.main,runtime 的 main 方法位于
%GOROOT%\src\runtime\proc.go
。 -
主协程执行主函数。执行 runtime 中的 init 方法,启动垃圾回收器,执行用户包依赖的 init 方法,最后执行用户自定义的主函数 main.main():
版权声明:本文为by6671715原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。