后台-插件-广告管理-内容页广告位一(手机)

您现在的位置是:首页 > 新兴行业 > 大数据大数据

Go的初始化流程分析

2021-06-05 09:12:38大数据人已围观

简介借助gdb、go tool objdump等工具进行go程序的调试,结合编译文件对go程序的初始化流程进行探究和学习,了解init函数、impot等在初始化过程中产生的依赖效果,以具体实例进行概述,最后总结了go初始化过程中的注意事项

前言

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

gdb调试

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

流程调试

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

阶段次序语言环境执行文件执行函数核心逻辑11.1汇编(.s)$GOROOT/src/runtime/rt0_darwin_amd64.s_rt0_amd64_darwin汇编引导11.2汇编(.s)$GOROOT/src/runtime/asm_amd64.s_rt0_amd64汇编引导11.3汇编(.s)$GOROOT/src/runtime/asm_amd64.srt0_go汇编引导22.1golang(.go)$GOROOT/src/runtime/runtime1.goargs整理命令行参数22.2golang(.go)$GOROOT/src/runtime/os_darwin.goosinit确定CPU数量22.3golang(.go)$GOROOT/src/runtime/proc.goschedinit初始化、参数、环境处理22.4golang(.go)$GOROOT/src/runtime/proc.gonewproc创建主goroutine即runtime.main对应的g22.5golang(.go)$GOROOT/src/runtime/proc.gomstart启动调度循环22.6golang(.go)$GOROOT/src/runtime/proc.gomain调用main goroutine运行,但不是用户函数入口的main.main32.3 => 2.3.1golang(.go)$GOROOT/src/runtime/proc.golockInit(xxx)各类Lock的初始化32.3 => 2.3.2golang(.go)$GOROOT/src/runtime/proc.gosched.maxmcount = 10000最大线程数32.3 => 2.3.3golang(.go)$GOROOT/src/runtime/proc.gostackinit()栈初始化32.3 => 2.3.4golang(.go)$GOROOT/src/runtime/proc.gomallocinit()内存管理器初始化32.3 => 2.3.5golang(.go)$GOROOT/src/runtime/proc.gomcommoninit()调度器初始化32.3 => 2.3.6golang(.go)$GOROOT/src/runtime/proc.gogoargs()命令行参数处理32.3 => 2.3.7golang(.go)$GOROOT/src/runtime/proc.gogoenvs()环境变量处理32.3 => 2.3.8golang(.go)$GOROOT/src/runtime/proc.goparsedebugvars()调试相关参数处理32.3 => 2.3.9golang(.go)$GOROOT/src/runtime/proc.gogcinit()垃圾回收器初始化32.6 => 2.6.1golang(.go)$GOROOT/src/runtime/proc.go(64bit-1G 32bit-250M)Stack栈的最大限制32.6 => 2.6.2golang(.go)$GOROOT/src/runtime/proc.gosystemstack()启动系统后台监控(垃圾回收,并发调度相关)32.6 => 2.6.3golang(.go)$GOROOT/src/runtime/proc.goruntime_initruntime包内所有init函数初始化32.6 => 2.6.4golang(.go)$GOROOT/src/runtime/proc.gogcenable()启动垃圾回收32.6 => 2.6.5golang(.go)$GOROOT/src/runtime/proc.gomain_init用户包内所有init函数初始化32.6 => 2.6.6golang(.go)$GOROOT/src/runtime/proc.gomain_main调用用户程序入口执行,由编译器动态生成32.6 => 2.6.7golang(.go)$GOROOT/src/runtime/proc.goexit(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.go中doInit的实现
  • 所有init函数结束后才会执⾏main.main函数,也就是说先完成初始化再进入程序入口,这点可以帮助我们更好地理解初始化与程序执行入口两者的次序关系,在编码时避免出现问题
  • import会产生对包的依赖,如果依赖包有init函数则会先执行,而init函数中的内容以及依赖也会有此效果,因此不控制使用init会产生隐藏地、不易察觉的依赖链并产生初始化一系列操作,所以慎用该功能,推荐的是只做当前局部作用域的初始化工作,以免带来不必要的问题隐患

参考

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

文章来源:https://blog.csdn.net/u013161278/article/details/117473683

Tags:Go gdb 初始化 

很赞哦! ()

后台-插件-广告管理-内容页广告位二(手机)

相关文章

后台-插件-广告管理-内容页广告位三(手机)

随机图文

后台-插件-广告管理-内容页广告位四(手机)

文章评论

留言与评论(共有 0 条评论)
   
验证码:

本栏推荐

站点信息

  • 文章统计60123篇文章
  • 浏览统计4401次浏览
  • 评论统计1个评论
  • 标签管理标签云
  • 统计数据:统计代码
  • 微信公众号:扫描二维码,关注我们