Golang标准库揭秘系列 | 初始化流程分析

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: Golang 初始化流程分析

前言


借助gdb来查看go的底层汇编,借此梳理和分析go程序的初始化流程,看看在初始化阶段go都做了哪些工作,对于理解go的工作机制很有帮助。目前是基于go 1.16.4进行的。


gdb调试


搭建gdb调试go程序 中已经探究并介绍了gdb的环境搭建、基本使用以及如何利用gdb来调试断点查看函数调用次序。


流程调试


网络异常,图片无法展示
|

如上图,是go程序初始化流程的整理,由于整个流程调用方法非常多,这里挑选较为核心部分进行梳理和分析,这里仅梳理初始化过程的调用流程和次序,用来熟悉初始化的工作机制,不涉及原理性分析,结合这张图的执行次序来说明,如下:

阶段

次序

语言环境

执行文件

执行函数

核心逻辑

1

1.1

汇编(.s)

$GOROOT/src/runtime/rt0_darwin_amd64.s

_rt0_amd64_darwin

汇编引导

1

1.2

汇编(.s)

$GOROOT/src/runtime/asm_amd64.s

_rt0_amd64

汇编引导

1

1.3

汇编(.s)

$GOROOT/src/runtime/asm_amd64.s

rt0_go

汇编引导

2

2.1

golang(.go)

$GOROOT/src/runtime/runtime1.go

args

整理命令行参数

2

2.2

golang(.go)

$GOROOT/src/runtime/os_darwin.go

osinit

确定CPU数量

2

2.3

golang(.go)

$GOROOT/src/runtime/proc.go

schedinit

初始化、参数、环境处理

2

2.4

golang(.go)

$GOROOT/src/runtime/proc.go

newproc

创建主goroutine即runtime.main对应的g

2

2.5

golang(.go)

$GOROOT/src/runtime/proc.go

mstart

启动调度循环

2

2.6

golang(.go)

$GOROOT/src/runtime/proc.go

main

调用main goroutine运行,但不是用户函数入口的main.main

3

2.3 => 2.3.1

golang(.go)

$GOROOT/src/runtime/proc.go

lockInit(xxx)

各类Lock的初始化

3

2.3 => 2.3.2

golang(.go)

$GOROOT/src/runtime/proc.go

sched.maxmcount = 10000

最大线程数

3

2.3 => 2.3.3

golang(.go)

$GOROOT/src/runtime/proc.go

stackinit()

栈初始化

3

2.3 => 2.3.4

golang(.go)

$GOROOT/src/runtime/proc.go

mallocinit()

内存管理器初始化

3

2.3 => 2.3.5

golang(.go)

$GOROOT/src/runtime/proc.go

mcommoninit()

调度器初始化

3

2.3 => 2.3.6

golang(.go)

$GOROOT/src/runtime/proc.go

goargs()

命令行参数处理

3

2.3 => 2.3.7

golang(.go)

$GOROOT/src/runtime/proc.go

goenvs()

环境变量处理

3

2.3 => 2.3.8

golang(.go)

$GOROOT/src/runtime/proc.go

parsedebugvars()

调试相关参数处理

3

2.3 => 2.3.9

golang(.go)

$GOROOT/src/runtime/proc.go

gcinit()

垃圾回收器初始化

3

2.6 => 2.6.1

golang(.go)

$GOROOT/src/runtime/proc.go

(64bit-1G 32bit-250M)

Stack栈的最大限制

3

2.6 => 2.6.2

golang(.go)

$GOROOT/src/runtime/proc.go

systemstack()

启动系统后台监控(垃圾回收,并发调度相关)

3

2.6 => 2.6.3

golang(.go)

$GOROOT/src/runtime/proc.go

runtime_init

runtime包内所有init函数初始化

3

2.6 => 2.6.4

golang(.go)

$GOROOT/src/runtime/proc.go

gcenable()

启动垃圾回收

3

2.6 => 2.6.5

golang(.go)

$GOROOT/src/runtime/proc.go

main_init

用户包内所有init函数初始化

3

2.6 => 2.6.6

golang(.go)

$GOROOT/src/runtime/proc.go

main_main

调用用户程序入口执行,由编译器动态生成

3

2.6 => 2.6.7

golang(.go)

$GOROOT/src/runtime/proc.go

exit(0)

执行结束


案例分析


我们创建一个简单的项目,在项目main函数中import一些依赖,查看下用户main_main的方法由编译器动态生成了什么内容。


项目目录及文件结构,大致如下:


% tree

.

├── funcs

│   └── func.go

├── main.go


funcs/func.go文件


package funcs


import "fmt"


func init(){

fmt.Println(" funcs init")

}


func Add(a int, b int) int  {

fmt.Println("Add method called.")

return a + b

}


main.go文件


package main


import (

//这里导入项目包

"program/funcs"

"fmt"

//这里导入外部包

_ "github.com/jinzhu/gorm/dialects/mysql"

)


func init()  {

fmt.Println("main init",funcs.Add(4,5))

}


func main() {

a, b := 1, 2

fmt.Println("result => ", funcs.Add(a, b))

}


通过go tool objdump -s “\.init\.0\b” [program]反编译查看该项目来查看编译情况,这里信息比较多,删减保留主要信息如下:


//===== 系统内部初始化开始 =====

TEXT internal/bytealg.init.0(SB) /usr/local/go/src/internal/bytealg/index_amd64.go

//省略...                          


TEXT runtime.init.0(SB) /usr/local/go/src/runtime/cpuflags_amd64.go

//省略...                                                          


TEXT os.init.0(SB) /usr/local/go/src/os/proc.go

//省略...                          

 proc.go:18            0x10eb852               eb8c                    JMP os.init.0(SB)                      

//省略...                              


//===== 项目包内初始化开始 =====


TEXT program/funcs.init.0(SB) /Users/guanjian/workspace/go/program/funcs/func.go

//省略...                  

 func.go:6             0x10facda               e8617fffff              CALL fmt.Println(SB)                    

//省略...    

 func.go:5             0x10facee               e96dffffff              JMP program/funcs.init.0(SB)

//省略...                                                              


//===== 外部包初始化开始 =====

TEXT github.com/go-sql-driver/mysql.init.0(SB) /Users/guanjian/go/pkg/mod/github.com/go-sql-driver/mysql@v1.5.0/driver.go

//省略...                                                              

 driver.go:83          0x12707e7               eb97                    JMP github.com/go-sql-driver/mysql.init.0(SB)                                                  

//省略...                                                                                        


TEXT main.init.0(SB) /Users/guanjian/workspace/go/program/main.go

//省略...                    

 main.go:10            0x128cec0               e83bdee6ff              CALL program/funcs.Add(SB)  

//省略...                  

 main.go:10            0x128cf75               e8c65ce6ff              CALL fmt.Println(SB)                    

//省略...        

 main.go:9             0x128cf96               e9e5feffff              JMP main.init.0(SB)                    

//省略...                    

//省略...


下面是将以上编译文件主要信息进行了整理,梳理了初始化顺序、包依赖关系、编译文件映射

网络异常,图片无法展示
|


初始化顺序


通过编译文件可以梳理得知,go程序的初始化大致可以分为两个部分,分别是Go环境初始化用户环境初始化,Go环境初始化指的是SDK内部执行流程的OS环境识别读取、参数初始化、相关底层支持函数的初始化准备等;用户环境初始化指的是项目中编写的代码逻辑,这里可以再细分为当前项目代码和外部导入包代码。加载顺序是先初始化Go环境,再根据用户代码逻辑从main.main作为入口按序进行初始化。


包依赖关系


包依赖关系的次序与真正的初始化顺序是相反的,在整个依赖链条上最被依赖的包及其init方法是最先被执行初始化的,相反,依赖下游的程序入口main.main的相关初始化操作是最靠后的。


编译文件映射


总结


  • 通过编译文件和gdb的调试可以得知,所有init函数都在同⼀个goroutine 内执⾏的,感兴趣可以参考 https://github.com/golang/go/blob/master/src/runtime/proc.godoInit的实现
  • 所有init函数结束后才会执⾏main.main函数,也就是说先完成初始化再进入程序入口,这点可以帮助我们更好地理解初始化与程序执行入口两者的次序关系,在编码时避免出现问题
  • import会产生对包的依赖,如果依赖包有init函数则会先执行,而init函数中的内容以及依赖也会有此效果,因此不控制使用init会产生隐藏地、不易察觉的依赖链并产生初始化一系列操作,所以慎用该功能,推荐的是只做当前局部作用域的初始化工作,以免带来不必要的问题隐患


参考


《Go语言学习笔记》 雨痕
搭建gdb调试go程序
golang底层 引导、初始化

相关实践学习
阿里云图数据库GDB入门与应用
图数据库(Graph Database,简称GDB)是一种支持Property Graph图模型、用于处理高度连接数据查询与存储的实时、可靠的在线数据库服务。它支持Apache TinkerPop Gremlin查询语言,可以帮您快速构建基于高度连接的数据集的应用程序。GDB非常适合社交网络、欺诈检测、推荐引擎、实时图谱、网络/IT运营这类高度互连数据集的场景。 GDB由阿里云自主研发,具备如下优势: 标准图查询语言:支持属性图,高度兼容Gremlin图查询语言。 高度优化的自研引擎:高度优化的自研图计算层和存储层,云盘多副本保障数据超高可靠,支持ACID事务。 服务高可用:支持高可用实例,节点故障迅速转移,保障业务连续性。 易运维:提供备份恢复、自动升级、监控告警、故障切换等丰富的运维功能,大幅降低运维成本。 产品主页:https://www.aliyun.com/product/gdb
相关文章
|
1月前
|
Go
Golang语言流程控制之条件判断篇
这篇文章介绍了Golang语言中的流程控制语句,包括if分支和switch语句的多种使用方式,如单分支、双分支、多分支条件判断以及switch的fallthrough语法。
26 4
|
1月前
|
Unix Go
Golang语言标准库time之日期和时间相关函数
这篇文章是关于Go语言日期和时间处理的文章,介绍了如何使用Go标准库中的time包来处理日期和时间。
30 3
|
1月前
|
Go
Golang语言流程控制之for循环结构篇
这篇文章详细介绍了Golang语言中的for循环结构,包括for循环的基本写法、引入、原理、死循环案例,for range键值循环的使用,以及break、continue、goto和return关键字在循环控制中的运用,并提供了练习题来加深理解。
16 0
Golang语言流程控制之for循环结构篇
|
2月前
|
NoSQL Java 测试技术
Golang内存分析工具gctrace和pprof实战
文章详细介绍了Golang的两个内存分析工具gctrace和pprof的使用方法,通过实例分析展示了如何通过gctrace跟踪GC的不同阶段耗时与内存量对比,以及如何使用pprof进行内存分析和调优。
55 0
Golang内存分析工具gctrace和pprof实战
|
2月前
|
JSON Go API
一文搞懂 Golang 高性能日志库 - Zap
一文搞懂 Golang 高性能日志库 - Zap
62 2
|
2月前
|
监控 测试技术 API
|
2月前
|
NoSQL Unix 编译器
Golang协程goroutine的调度与状态变迁分析
文章深入分析了Golang中goroutine的调度和状态变迁,包括Grunnable、Gwaiting、Grunning和Gsyscall等状态,以及它们之间的转换条件和原理,帮助理解Go调度器的内部机制。
33 0
|
2月前
|
存储 JSON Go
一文搞懂 Golang 高性能日志库 Zerolog
一文搞懂 Golang 高性能日志库 Zerolog
89 0
|
2月前
|
JSON Go 数据格式
[golang]标准库-json
[golang]标准库-json
|
4月前
|
SQL NoSQL Go
技术经验分享:Golang标准库:errors包应用
技术经验分享:Golang标准库:errors包应用
36 0
下一篇
无影云桌面