上图已经展示了这个过程:从Java的源代码编译成jar包或war包(字节码),最终运行在JVM中。
我们把Java源代码编译后的jar包或war包看成是工程师生产出来的产品,操作系统是一个平台,JVM就是中间商,那程序的整体性能也要受到中间商JVM的因素影响了。
我们把Go语言的源代码编译后,生成二进制文件,直接就可以在操作系统上运行,没有中间商。
优点:
每种编程语言都有自己的Runtime, 把这个单词拆开来看,Run=运行,Time=时间,简称:运行时。
Go语言的Runtime作用:
Go语言的运行时,是和源代码最终编译生成到二进制文件中的。当我们启动二进制文件的时候,运行时也就是一并启动了。
package mainimport "fmt"func main() {fmt.Println("面向加薪学习-从0到Go语言微服务架构师")
}
在命令行执行 go build -n(-n含义代表:打印编译时会用到的所有命令,但不真正执行)
编译过程1

从上图可以看到:
编译过程2

总结:看到上面的过程已经把runtime包放到我们的二进制文件中了。
在编译原理中,有一个名词:AST(抽象语法树) = Abstract Syntax Tree
1. 把源代码变成文本,然后把每个单词拆分出来
2. 把每个单词变成语法树
3. 类型检查、类型推断、类型匹配、函数调用、逃逸分析
执行export GOSSAFUNC=main,代表你要看main函数的ssa代码,然后执行go build,会生成ssa.html

Go语言启动的时候,Runtime到底发生了什么?
可以到runtime目录中找到rt0_darwin_amd64.s找到这个文件(由于我的电脑是mac,所以找到了这个,其他平台可以找各自的),这是一个汇编文件。
rt0_darwin_amd64.s
TEXT _rt0_amd64_darwin(SB),NOSPLIT,$-8JMP _rt0_amd64(SB)
asm_amd64.s
TEXT _rt0_amd64(SB),NOSPLIT,$-8MOVQ 0(SP), DI // argcLEAQ 8(SP), SI // argvJMP runtime·rt0_go(SB)
接下来在同名文件中找到
TEXT runtime·rt0_go(SB),NOSPLIT|TOPFRAME,$0
它执行
上面信息就是初始化一个协程G0(这是一个根协程,此时还没有调度器,也就是说不受调度器控制)
接下来是各种平台的检测和判断
CALL runtime·check(SB)
查找代码 在runtime1.go,很亲切的Go语言函数了吧。里面是各种检查。看看都干了啥。
func check() {unsafe.Sizeof(...)unsafe.Offsetof(...)atomic.Cas(...)atomic.Or8()unsafe.Pointer()if _FixedStack != round2(_FixedStack){...} ...
}
上面代码执行了:
接下来
CALL runtime·args(SB)
func args(c int32, v **byte) {argc = cargv = vsysargs(c, v)
}
下面看一下启动顺序:
osinit(操作系统的初始化) -> schedinit(调度器的初始化) -> make & queue new G(新建一个队列G) -> mstart(启动)
CALL runtime·osinit(SB)
func osinit() {ncpu = getncpu()physPageSize = getPageSize()
}
runtime/proc.go
CALL runtime·schedinit(SB)
func schedinit() {...stackinit() //栈空间内存分配mallocinit() //堆内存空间初始化cpuinit() // must run before alginitalginit() // maps, hash, fastrand must not be used before this callfastrandinit() // must run before mcommoninitmcommoninit(_g_.m, -1)modulesinit() // provides activeModulestypelinksinit() // uses maps, activeModulesitabsinit() // uses activeModulesstkobjinit() // must run before GC starts...goargs()goenvs()parsedebugvars()gcinit()
}
可以看到上面的代码的操作:
//拿到主函数的地址 ,是$runtime·main的地址,这里还没到我们写的main函数呢MOVQ $runtime·mainPC(SB), AX PUSHQ AX//启动一个新协程CALL runtime·newproc(SB) POPQ AX//启动一个M(可以把M看成是一个中间人,它联系Goroutine和Processor)CALL runtime·mstart(SB)
从上面看到,此时系统里拥有:
runtime.main在runtime/proc.go中(这个是runtime中的main方法,还没到我们自己写的main函数)
// The main goroutine.
func main() {g := getg()...doInit(&runtime_inittask)gcenable()fn := main_main fn()
}
从上面看到: