likes
comments
collection
share

Go程序是如何编译运行的

作者站长头像
站长
· 阅读数 38

何为Runtime

简介:语言在运行的时候,作为支撑语言运行的部分。 也就是程序的运行环境

常见的runtime例如:

  • Java:虚拟机
  • JaveScript: 浏览器内核或者node的内核

Go程序是如何编译运行的

Go的Runtime的特点:

  • go是没有虚拟机的概念的
  • Runtime 作为程序的一部分打包进二进制产物
  • Runtime 随用户程序一起运行
  • Runtime 与用户程序没有明显界限,直接通过函数调用

Go程序是如何编译运行的

Go 的Runtime能力

  • 内存管理能力(分配堆内存,栈内存,变量等)
  • 垃圾回收能力(GC)
  • 超强的并发能力(协程调度)
  • 其他特点
    • Runtime有一定的屏蔽系统调用能力(go build详解)

    • 一些go的关键字其实是Runtime下的函数:

Go程序是如何编译运行的

总结:

Go 的 Runtime 负责内存管理,垃圾回收,协程调度 Go 的 Runtime 被编译成用户程序的一部分,一起运行

Go程序是如何编译的

使用命令 go build -n 可以输出程序的编译过程,并不实际编译 新建main.go文件

package main

import "fmt"

func main() {
  fmt.Println("hello word")
}

Go程序是如何编译运行的

在项目中并未引入runtime包,由此可以看出runtime是自动引入一起编译的。 先使用compile.exe 编译成 .a文件 再通过link.exe链接打包成目标文件 也就是 main.exe

Go 编译过程

Go程序是如何编译运行的

词法分析

  • 将源代码翻译成 Token
  • Token 是代码中的最小语义结构

句法分析

  • Token 序列经过处理,变成语法树

Go程序是如何编译运行的

语义分析

  • 类型检查
  • 类型推断
  • 查看类型是否匹配
  • 函数调用内联
  • 逃逸分析

中间码生成

  • 为了处理不同平台的差异,先生成中间代码(SSA)
  • 查看从代码到SSA中间码的整个过程

windows:$env:GOSSAFUNC="main" linux:export GOSSAFUNC=main

重新执行go build ,会生成 ssa.html

Go程序是如何编译运行的

机器码生成

  • 先生成Plan9汇编代码
  • 最后编译成机器码
  • 输出的机器码为.a文件

查看Plan9汇编代码

go build -gcflags -S main.go

链接

  • 将各个包进行链接,包括runtime

Go程序是如何编译运行的

Go程序是如何运行的

Go程序的入口

  • main方法就是入口吗?

  • runtime/rt0_xxx.s

  • 读取命令行参数

    • 复制参数数量 argc 和 和参数值 argv 到栈上
  • 初始化 g0 执行栈

    • g0 是为了调度协程而产生的协程
    • g0 是每一个Go程序的第一个协程
  • 运行时检测

    • 检查各种类型的长度
    • 检查指针操作
    • 检查结构体字段偏移量
    • 检查atomic原子操作
    • 检查CAS操作
    • 检查栈大小是否是2的幂
  • 参数初始化 runtime.args

    • 对命令行中的参数进行处理
    • 参数数量赋值给 argc int32
    • 参数值赋值给 argv **byte
  • 调度器初始化 runtime.schedinit

    • 全局栈空间内存分配
    • 堆内存空间的初始化
    • 初始化当前系统线程
    • 算法初始化(map,hash)
    • 加载命令行参数到 os.Args
    • 加载操作系统环境变量
    • 垃圾回收器的参数初始化
    • 设置process数量
  • 创建主协程

    • 创建一个新的协程,执行runtime.main
    • 放入调度器等待调度
  • 初始化 M

    • 初始化一个M 用来调度主协程
  • 主协程执行主函数

    • 执行 runtime 包中的 init 方法
    • 启动 GC 垃圾收集器
    • 执行用户包依赖的 init 方法
    • 执行用户主函数 main.main()

Go SDK -> runtime -> rt0_xxx.s就是Go程序真正的入口

例如:

window 64位的入口为 rt0_windows_amd64.s linux 64位的入口为 rt0_linux_amd64.s

在源码中不难发现 64位操作系统都会调用 asm_amd64.s中的_rt0_amd64(SB)

Go程序是如何编译运行的

而这个方法主要是将 argc 和 argv 放进寄存器中,这两个是调用命令时候的参数 例如 :go build -n ...

之后调用 runtime.rt0_go

Go程序是如何编译运行的

将参数放入栈中

Go程序是如何编译运行的

创建一个 叫做 g0 的协程 (Go 语言运行是创建的第一个协程,并且不由调度器管理)

Go程序是如何编译运行的

执行go语言的check方法

Go程序是如何编译运行的

将 参数 copy 到go语言代码中 判断系统字长:调度器初始化需要用到 执行调度器初始化

Go程序是如何编译运行的

获取runtime.main方法的地址,并启动一个新的协程用来执行该方法

Go程序是如何编译运行的

初始化调度器m,使协程真正执行

runtime.proc.go -> 进入了go语言中的main方法

Go程序是如何编译运行的

初始化一些东西

Go程序是如何编译运行的

打开垃圾回收器

Go程序是如何编译运行的

用户包依赖的初始化

Go程序是如何编译运行的

Go程序是如何编译运行的

在链接阶段会 链接到用户的 main方法

总结

  • Go 启动时经历了检查、各种初始化、初始化协程调度的过程
  • main.main() 也是在协程中运行的

问题

  • 调度器是什么
  • 为什么初始化M
  • 为什么不是直接执行 main.main(),而是将其放入调度器
转载自:https://juejin.cn/post/7126725536088326174
评论
请登录