实际上, 编译好的二进制文件的执行入口并非我们所写的main.main函数, 因为编译器会插入一段引导代码,用来完成准备操作,eg命令行参数 运行时初始化等
命令行 go build -gcflags "-N -l" -o xxx xxx.go 编译后使用gdb查看发现在创建main goroutine之前会调用初始化函数
runtime.args()
runtime.osinit()
runtime.schedinit()
func args(c int32, v **byte) {
argc = c
argv = v
sysargs(c, v)
}
整理命令行参数, 没什么好看的
func osinit() {
ncpu = getproccount()
physHugePageSize = getHugePageSize()
osArchInit()
}
确定了 cpu core的数量, 也没什么好看
为啥说主要看他,因为几乎我们所要关注的所有运行时环境初始化构造都是在这里被调用的
所以说他做了啥事? 1.线程的最大数量限制; 2.初始化栈 内存分配器 调度器; 3. 处理命令行参数和环境变量; 4. 垃圾回收器初始化; 5.通过cpu core和gomaxprocs环境变量确定p的数量; 6. 调整p的数量
procl.go
func schedinit() {
// 设置了线程的最大数量限制
sched.maxmcount = 10000
// 栈初始化
tracebackinit()
stackinit()
// 内存分配器初始化
mallocinit()
// 调度器初始化
mcommoninit(_g_.m)
// 处理命令行参数和环境变量
goargs()
goenvs()
// 处理GODEBUG调试相关环境变量
parsedebugvars()
// 垃圾回收初始化
gcinit()
...
sched.lastpoll = uint64(nanotime())
// 通过cpu core 和 gomaxprocs 环境变量确定p数量
procs := ncpu
if n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 {
procs = n
}
// 调整p的数量
if procresize(procs) != nil {
throw("unknown runnable goroutine during bootstrap")
}
...
}
关于内存, 垃圾回收, 并发调度器等后续在学习, 至此需要执行runtime.main 并非用户自己的main.main
proc.go
func main() {
...
// 执行栈的最大限制, 1gb on 64bit; 250m on 32bit
if sys.PtrSize == 8 {
maxstacksize = 1000000000
} else {
maxstacksize = 250000000
}
...
// 启动系统后台监控,(定期垃圾回收,以及并发任务调度相关信息)
if GOARCH != "wasm" { // no threads on wasm yet, so no sysmon
systemstack(func() {
newm(sysmon, nil)
})
}
...
// 执行runtime包内所有初始化函数
runtime_init()
...
// 启动垃圾回收器后台操作
gcenable()
...
// 执行所有用户包(包括标准库)初始化函数init
main_init()
...
// 执行用户逻辑入口
main_mian()
...
// 执行结束返回退出状态码
exit(0)
}
与之相关的就是runtime_init 与 main_init 两个函数, 他们都是有编译器动态生成的
实际上通过反汇编工具可以看到, runtime内相关多个init函数被赋予唯一符号名, 然后再由runtime.init进行同一调用
至于main.init 情况基本一直, 区别在于他负责调用非runtime包的初始化函数,调用的是自定义包和标准库中的
1. 所有init都在同一个goroutine内执行
2. 所有init函数结束后才会执行main.main函数
3. 强烈建议init只做该做的事情: 局部初始化
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。