go 的 Runtime
Runtime
作为程序的一部分打包进二进制产物Runtime
随用户程序一起运行Runtime
与用户程序没有明显界限,直接通过函数调用
Go Runtime
的能力:
-
内存管理:它能够进行帮助我们分配内存,比如堆内存/栈内存等
-
垃圾回收能力
-
超强的并发能力(协程调度)
-
有一定的屏蔽系统调用能力,
go
是一门跨OS
的语言,这个能力是Runtime
提供的 -
go
关键字是Runtime
提供的关键字 函数 go
newproc
new
newobject
make
makemap
,makechan
,makeslice
...<-
chansend1
,chanrecv1
go 程序是如何编译的
go
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
使用命令 go build -n
可以查看编译过程,这个指令的作用是不实际编译它,只是输出它的变易过程
bash
#
# limit-go
#
mkdir -p $WORK/b001/
cat >$WORK/b001/importcfg << 'EOF' # internal
# ① 文件引入包的地方
# import config
packagefile fmt=/Users/uccs/Library/Caches/go-build/9d/9d949eb387278de330b58e7ecbd6f130e51e64b0cd062f60fb7f69fad2c37c08-d # fmt 包的路径
packagefile runtime=/Users/uccs/Library/Caches/go-build/d3/d35337e57cdb7ec3bc75cf5cc9526a5dd8c2cac47cf9bab7c63f0e2a1dd9ebcd-d # runtime 包的路径
EOF
cd /Users/uccs/Desktop/project/tools/limit-go
# ② 执行 go 的 compile 命令
/usr/local/opt/go/libexec/pkg/tool/darwin_amd64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=>" -p main -lang=go1.21 -complete -buildid NPqG56mfdgtvjXh3cGFS/NPqG56mfdgtvjXh3cGFS -goversion go1.21.3 -c=4 -nolocalimports -importcfg $WORK/b001/importcfg -pack ./main.go
/usr/local/opt/go/libexec/pkg/tool/darwin_amd64/buildid -w $WORK/b001/_pkg_.a # internal
cat >$WORK/b001/importcfg.link << 'EOF' # internal
# ③ 执行 compile 命令后生成的一堆文件
packagefile limit-go=$WORK/b001/_pkg_.a
packagefile fmt=/Users/uccs/Library/Caches/go-build/9d/9d949eb387278de330b58e7ecbd6f130e51e64b0cd062f60fb7f69fad2c37c08-d
packagefile runtime=/Users/uccs/Library/Caches/go-build/d3/d35337e57cdb7ec3bc75cf5cc9526a5dd8c2cac47cf9bab7c63f0e2a1dd9ebcd-d
packagefile errors=/Users/uccs/Library/Caches/go-build/a6/a6b999568116b1b19d9204becc8ed5686d44eaa2dfbc7520a4fc3a0599c161f9-d
packagefile internal/fmtsort=/Users/uccs/Library/Caches/go-build/a7/a730efeb5a119d046d78935bc03a3ad2d212e19b1f178fe19b8682833211cfba-d
packagefile io=/Users/uccs/Library/Caches/go-build/bf/bf725efbcf3a96ed4510a4bfc7b9bb28ee1d60f62f99a2599f6d386f8fe8f433-d
packagefile math=/Users/uccs/Library/Caches/go-build/4a/4a9091add7da0dfae4c714cc84fe3d7059f182275f79231cdfcd9bae71ea4379-d
packagefile os=/Users/uccs/Library/Caches/go-build/eb/eb0e34a6a890ab326d4b88762deb41b547ba2f554a86f01adef7171130494f48-d
packagefile reflect=/Users/uccs/Library/Caches/go-build/fe/fe1d170bb3f71666c6ca69285a73d3bee470fa4e1c9f48e3d8d9f1d8a418a53c-d
packagefile sort=/Users/uccs/Library/Caches/go-build/82/82adbe8e360083bd9e5a60077c5a647e4bbbae49adbea005017d44ec318750d0-d
packagefile strconv=/Users/uccs/Library/Caches/go-build/57/57d0eba198b828f90d005e40a6c1c22289680468f45a469401f649638c812898-d
packagefile sync=/Users/uccs/Library/Caches/go-build/ce/ce53b6ab314126807707e24de3ee1dd56322c7e3b91c433ed0d208df9e51649d-d
packagefile unicode/utf8=/Users/uccs/Library/Caches/go-build/1d/1de56338243a23ad198d173ce5b6f3cf301ccfd4828ec217119e208e21b27a2d-d
packagefile internal/abi=/Users/uccs/Library/Caches/go-build/16/16f37713a7fa27904f42fa3ed1dbe862bf81d0c72bd0a1b38b36e2f94331be1a-d
packagefile internal/bytealg=/Users/uccs/Library/Caches/go-build/dc/dc487fe880bdec21754d595cf49acabce298368bc87380c41a5543cb7e742650-d
packagefile internal/coverage/rtcov=/Users/uccs/Library/Caches/go-build/e2/e259c5cf3c25ca7661db00e75b78c7f965245bedefc5e663bc68a5c45299fd1a-d
packagefile internal/cpu=/Users/uccs/Library/Caches/go-build/23/23bce7bdce8ba5590b9de76c20f919ea8ac3ecc7c43a5a09cd73a618735478c8-d
packagefile internal/goarch=/Users/uccs/Library/Caches/go-build/ae/ae6f27645ca02cd0a1e3995f4c05f7d783598bb7d3761513c659fd49eb187c1e-d
packagefile internal/godebugs=/Users/uccs/Library/Caches/go-build/22/221c1c6a2920cfc261ee242057b2d19d50508e1424a9373a3135044fd7f60824-d
packagefile internal/goexperiment=/Users/uccs/Library/Caches/go-build/55/55b8ec0fe0f74044d60047c074dac977577bc04ba5b7010ac1441fd69498c7bb-d
packagefile internal/goos=/Users/uccs/Library/Caches/go-build/b7/b7a2966bdde59f126d4b096834b362988db6ce07a3c29ffe6c14c37f5f5254b4-d
packagefile runtime/internal/atomic=/Users/uccs/Library/Caches/go-build/d2/d255ab5ec0f88cb395f79e4ea6b5638bf1d45549c03edcb4d46efb3887dd9c63-d
packagefile runtime/internal/math=/Users/uccs/Library/Caches/go-build/f6/f698b3ea1b8ecd800ead11dec7ecbf1684d6d6b3b4981b1b7047d38c33f32f8e-d
packagefile runtime/internal/sys=/Users/uccs/Library/Caches/go-build/37/37129d69476f12fec8ac3b920b665455dcdbc99c8d9a6d3d798d9a660f2696a8-d
packagefile internal/reflectlite=/Users/uccs/Library/Caches/go-build/39/39eed692bdf8ab45264cdfaa29ccf5d34029838ff0894086dd2ebd334d9f3e75-d
packagefile math/bits=/Users/uccs/Library/Caches/go-build/cf/cf6e5a72cd5709aecc627f48de82c928b14042f64ef105e54cc146d8b92a38d8-d
packagefile internal/itoa=/Users/uccs/Library/Caches/go-build/18/187330c6da05306533dbdc909c3777f9e8b81fb3b5151a8379e97ac6e7a1ebb8-d
packagefile internal/poll=/Users/uccs/Library/Caches/go-build/98/9898da8b84f86056f43b3f928394cf36998c076c6a2b76cf43c8af18c5a59d63-d
packagefile internal/safefilepath=/Users/uccs/Library/Caches/go-build/c1/c1675768be92a9ef26018894d92185870eb3eac31a4c1a88d939e56eb10f8dbd-d
packagefile internal/syscall/execenv=/Users/uccs/Library/Caches/go-build/1f/1f5b502d4235cea5d89967d55242eb307dd29b379b1396580a35594391ea5780-d
packagefile internal/syscall/unix=/Users/uccs/Library/Caches/go-build/51/51de91caecab68364694a5552a7620dce46cd7b3bd84871445fb313d29093fe6-d
packagefile internal/testlog=/Users/uccs/Library/Caches/go-build/7e/7e3b6f183732548ac8b075ffb24c67e5194eced94579e23dc231200a02a6efe3-d
packagefile io/fs=/Users/uccs/Library/Caches/go-build/d0/d0a1b5259344935c41cf177400f04253aeca321a1175255f76d4311264cff692-d
packagefile sync/atomic=/Users/uccs/Library/Caches/go-build/e7/e79a0bad378846779234909aa0a1cbd7fecdad4e23c925e019281783df45e1e1-d
packagefile syscall=/Users/uccs/Library/Caches/go-build/71/711660abd17c2c345f77e44978a4e13946f1e0004dec357425d62a14aa7acd50-d
packagefile time=/Users/uccs/Library/Caches/go-build/b6/b67da8eed4f067251323c9d9adad19a276195c4df5fd7c2dc62512a9a10b6c37-d
packagefile internal/unsafeheader=/Users/uccs/Library/Caches/go-build/b9/b9c4a61deef9c40fad8ca93466c77e4499cd49cf51dea13330f9919281b1ebc9-d
packagefile unicode=/Users/uccs/Library/Caches/go-build/8f/8fc9006eeb3b049e224651f51abbe5e8f32c1c56136ad340fb667ce8c2b53708-d
packagefile internal/race=/Users/uccs/Library/Caches/go-build/23/232609feea6a346964a0d1450379227ef10aa22861915e979143612885ad2ac8-d
packagefile internal/oserror=/Users/uccs/Library/Caches/go-build/82/82cf7ae7a533a2d9241b6e5d105da4ab4d48b17f96b66030a551ff723fa1f01d-d
packagefile path=/Users/uccs/Library/Caches/go-build/2a/2ae3945be4388190aa3f8037f296b0c2e236a15d752d491fb53ae1294cdb20cb-d
modinfo "0w\xaf\f\x92t\b\x02A\xe1\xc1\a\xe6\xd6\x18\xe6path\tlimit-go\nmod\tlimit-go\t(devel)\t\nbuild\t-buildmode=exe\nbuild\t-compiler=gc\nbuild\tCGO_ENABLED=1\nbuild\tCGO_CFLAGS=\nbuild\tCGO_CPPFLAGS=\nbuild\tCGO_CXXFLAGS=\nbuild\tCGO_LDFLAGS=\nbuild\tGOARCH=amd64\nbuild\tGOOS=darwin\nbuild\tGOAMD64=v1\n\xf92C1\x86\x18 r\x00\x82B\x10A\x16\xd8\xf2"
EOF
mkdir -p $WORK/b001/exe/
cd .
# ④ 将这些用到的文件打包成一个二进制文件 a.out
/usr/local/opt/go/libexec/pkg/tool/darwin_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=VINFY1yfalFx5VnyZHPS/NPqG56mfdgtvjXh3cGFS/NPqG56mfdgtvjXh3cGFS/VINFY1yfalFx5VnyZHPS -extld=cc $WORK/b001/_pkg_.a
/usr/local/opt/go/libexec/pkg/tool/darwin_amd64/buildid -w $WORK/b001/exe/a.out # internal
# ⑤ 将 a.out 重命名为 limit-go
mv $WORK/b001/exe/a.out limit-go
go
的编译过程如下:
- 词法分析:将源码翻译成
token
,token
是代码中的最小语义结构 - 句法分析:
token
经过处理,变成语法树(ast
) - 语义分析:
- 类型检查
- 类型推断
- 查看类型是否匹配
- 函数调用内联
- 逃逸分析(变量是放在堆上还是栈上)
- 代码优化,每一步都会做
- 中间码生成:(与平台无关的汇编代码)
- 为了处理不同平台的差异,先生产中间代码(
SSA
) - 查看中间码
-
设置环境变量,指示编译器输出某一个函数的中间码
bashexport GOSSAFUNC=main
-
运行
go build
命令,会看到输出一个ssa.html
的文件
-
- 为了处理不同平台的差异,先生产中间代码(
- 机器码生成
-
先生成
plan9
的汇编代码(与平台相关)-
查看
plan9
的汇编代码bashgo build -gcflags -S main.go
-
-
最后编译成机器码
-
输出的机器码为
-d
结尾的文件
-
- 将各个包进行链接生成一个可执行文件,包括
runtime
go 程序是如何运行的
rt0_linux_amd64.s
是 go
的启动文件,它是 go
的入口文件,它会调用 main.main
函数
文件名的意思:
rt
是runtime
的缩写0
代表是runtime
的第一个文件linux
代表是linux
平台amd64
代表是amd64
架构
_rt0_amd64_linux -> _rt0_amd64
找到函数 _rt0_amd64
,它是汇编语言函数
-
这个函数的作用是读取命令行参数,并将
argc
和argv
放到栈上nasmTEXT _rt0_amd64(SB),NOSPLIT,$-8 MOVQ 0(SP), DI // argc LEAQ 8(SP), SI // argv JMP runtime·rt0_go(SB)
-
初始化
g0
执行栈(这是在runtime.rt0_go
函数中完成的)-
g0
是go
第一个协程,g0
是一母协程 -
g0
是为了调度协程而产生的协程nasmTEXT runtime·rt0_go(SB),NOSPLIT|NOFRAME|TOPFRAME,$0 // xxxx MOVQ $runtime·g0(SB), DI LEAQ (-64*1024)(SP), BX MOVQ BX, g_stackguard0(DI) MOVQ BX, g_stackguard1(DI) MOVQ BX, (g_stack+stack_lo)(DI) MOVQ SP, (g_stack+stack_hi)(DI)
-
运行时检测(调用
go
语言的runtime.check
)- 检查各种类型的长度
- 检查指针操作
- 监测结构体字段的偏移量
- 检查
atomic
原子操作 - 检查
CAS
操作 - 检查栈大小是否是
2
的幂次
nasmTEXT runtime·rt0_go(SB),NOSPLIT|NOFRAME|TOPFRAME,$0 // xxx CALL runtime·check(SB)
-
参数初始化(调用
go
语言的runtime.args
)- 对命令行中的参数进行处理
- 将参数数量赋值给
argc int32
- 将参数值赋值给
argv **byte
- 将参数数量赋值给
nasmTEXT runtime·rt0_go(SB),NOSPLIT|NOFRAME|TOPFRAME,$0 // xxx CALL runtime·args(SB)
- 对命令行中的参数进行处理
-
判断系统的字长和核数(调用
go
语言的runtime.osinit
),后续调度器初始化需要使用
nasmTEXT runtime·rt0_go(SB),NOSPLIT|NOFRAME|TOPFRAME,$0 // xxx CALL runtime·osinit(SB)
-
调度器初始化(调用
go
语言的runtime.schedinit
)- 全局栈空间内存分配
- 加载命令行参数到
os.Args
- 堆内存空间的初始化
- 加载操作系统环境变量
- 初始化当前系统线程
- 垃圾回收器的参数初始化
- 算法初始化(
map
、hash
) - 设置
process
数量
goTEXT runtime·rt0_go(SB),NOSPLIT|NOFRAME|TOPFRAME,$0 // xxx CALL runtime·schedinit(SB)
-
创建一个新的协程
nasmTEXT runtime·rt0_go(SB),NOSPLIT|NOFRAME|TOPFRAME,$0 MOVQ $runtime·mainPC(SB), AX // entry
-
初始化
M
- 初始化一个
M
用来调用主协程
nasmTEXT runtime·rt0_go(SB),NOSPLIT|NOFRAME|TOPFRAME,$0 CALL runtime·mstart(SB)
- 初始化一个
-
调用
go
语言的main
nasmDATA runtime·mainPC+0(SB)/8,$runtime·main<ABIInternal>(SB)
-
-
runtime.main
是Runtime
的入口函数,在这里面会调用main.main
函数go:linkname
标签在链接时会跳转到main.main
函数,这个main.mian
函数就是我们写的main
函数
go//go:linkname main_main main.main func main_main() func main(){ fn := main_main fn() }
主协程执行主函数主要做了 4
件事:
- 执行
runtime
包中的init
方法 - 启动
GC
垃圾收集器 - 执行用户依赖的
init
方法 - 执行用户主函数
main.main()
go 包管理方法
-
初始化项目
go mod init <git repo address>
-
替换本地文件
replace github.com/xxx/xxx => /xxx/xxx
-
缓存到本地
bashgo mod vendor # 将依赖包缓存到本地 go build mod vendeor # 编译项目
-
无法访问
github
,使用goproxy.cn
作为代理bashgo env -w GOPROXY=https://goproxy.cn,direct