深入解析Golang程序启动流程 - 基于cch123/golang-notes项目

深入解析Golang程序启动流程 - 基于cch123/golang-notes项目

golang-notes Go source code analysis(zh-cn) golang-notes 项目地址: https://gitcode.com/gh_mirrors/go/golang-notes

Go语言程序的启动过程是一个精心设计的流程,涉及从操作系统内核到用户代码的完整初始化过程。本文将通过cch123/golang-notes项目中的分析,详细讲解Go程序的启动机制。

一、程序入口定位

Go程序的启动始于操作系统的入口点(entry point)。不同操作系统下,Go使用不同的汇编文件作为程序入口:

  • Linux系统:rt0_linux_amd64.s
  • Darwin(Mac)系统:rt0_darwin_amd64.s

我们可以使用调试工具来定位入口点:

1. 使用GDB定位入口点

(gdb) info files
Symbols from "/home/ubuntu/exec_file".
Local exec file:
	`/home/ubuntu/exec_file', file type elf64-x86-64.
	Entry point: 0x448fc0
	...
(gdb) b *0x448fc0
Breakpoint 1 at 0x448fc0: file /usr/local/go/src/runtime/rt0_linux_amd64.s, line 8.

2. 使用readelf和lldb定位

$ readelf -h ./exec_file
ELF Header:
  ...
  Entry point address:               0x448fc0
  ...
  
$ lldb ./exec_file
(lldb) image lookup --address 0x448fc0
      Address: exec_file[0x0000000000448fc0] (exec_file..text + 294848)
      Summary: exec_file`_rt0_amd64_linux at rt0_linux_amd64.s:8

二、启动流程总览

Go程序的完整启动流程可以用以下流程图表示:

graph TD
A[_rt0_amd64_darwin/linux] --> B[_rt0_amd64]
B --> C[runtime·rt0_go]
C --> D[runtime·args]
D --> E[runtime·osinit]
E --> F[runtime·schedinit]
F --> G[runtime·newproc]
G --> H[runtime·mstart]
H --> I[runtime·main]

三、启动流程详细解析

1. 汇编阶段初始化

_rt0_amd64_darwin/linux

这是操作系统识别的第一个入口点,仅做简单跳转:

TEXT _rt0_amd64_darwin(SB),NOSPLIT,$-8
	JMP	_rt0_amd64(SB)
_rt0_amd64

负责获取并传递命令行参数:

TEXT _rt0_amd64(SB),NOSPLIT,$-8
	MOVQ	0(SP), DI	// argc
	LEAQ	8(SP), SI	// argv
	JMP	runtime·rt0_go(SB)
runtime·rt0_go

这是主要的汇编初始化阶段,完成多项关键工作:

  1. 设置TLS(线程本地存储)
  2. 初始化g0和m0(调度器的初始Goroutine和Machine)
  3. 调用runtime.check进行运行时检查
  4. 准备参数并调用后续初始化函数
TEXT runtime·rt0_go(SB),NOSPLIT,$0
	// 设置TLS
	LEAQ	runtime·m0+m_tls(SB), DI
	CALL	runtime·settls(SB)
	
	// 初始化g0和m0
	get_tls(BX)
	LEAQ	runtime·g0(SB), CX
	MOVQ	CX, g(BX)
	LEAQ	runtime·m0(SB), AX
	MOVQ	CX, m_g0(AX)
	MOVQ	AX, g_m(CX)
	
	// 调用初始化函数
	CALL	runtime·check(SB)
	CALL	runtime·args(SB)
	CALL	runtime·osinit(SB)
	CALL	runtime·schedinit(SB)
	
	// 创建main goroutine
	MOVQ	$runtime·mainPC(SB), AX
	PUSHQ	AX
	PUSHQ	$0
	CALL	runtime·newproc(SB)
	POPQ	AX
	POPQ	AX
	
	// 启动M
	CALL	runtime·mstart(SB)

2. 运行时初始化阶段

runtime·args

处理命令行参数和环境变量:

func args(c int32, v **byte) {
	argc = c
	argv = v
	sysargs(c, v)
}
runtime·osinit

操作系统相关的初始化,获取CPU核心数和页大小:

func osinit() {
	ncpu = getncpu()
	physPageSize = getPageSize()
	darwinVersion = getDarwinVersion()
}
runtime·schedinit

调度器初始化,是启动过程中最复杂的部分:

func schedinit() {
	// 初始化栈池和内存分配器
	stackinit()
	mallocinit()
	
	// 初始化M
	mcommoninit(_g_.m)
	
	// 算法初始化
	alginit()
	
	// 模块和类型系统初始化
	modulesinit()
	typelinksinit()
	itabsinit()
	
	// 处理参数和环境变量
	goargs()
	goenvs()
	
	// 调试和GC初始化
	parsedebugvars()
	gcinit()
	
	// 设置P的数量
	procs := ncpu
	if n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 {
		procs = n
	}
	procresize(procs)
}

3. Goroutine创建与调度

runtime·newproc

创建主Goroutine来运行runtime.main:

func newproc(siz int32, fn *funcval) {
	argp := add(unsafe.Pointer(&fn), sys.PtrSize)
	pc := getcallerpc()
	systemstack(func() {
		newproc1(fn, (*uint8)(argp), siz, pc)
	})
}
runtime·mstart

启动M并开始调度:

func mstart() {
	_g_ := getg()
	
	// 初始化栈保护
	_g_.stackguard0 = _g_.stack.lo + _StackGuard
	_g_.stackguard1 = _g_.stackguard0
	
	mstart1(0)
	
	// 退出线程
	mexit(osStack)
}

4. 主程序执行阶段

runtime·main

这是主Goroutine执行的函数,完成最后的初始化并调用用户main函数:

func main() {
	// 设置最大栈大小
	if sys.PtrSize == 8 {
		maxstacksize = 1000000000
	} else {
		maxstacksize = 250000000
	}
	
	// 启动系统监控
	systemstack(func() {
		newm(sysmon, nil)
	})
	
	// 执行runtime包的init函数
	runtime_init()
	
	// 启用GC
	gcenable()
	
	// 执行用户包的init函数
	fn := main_init
	fn()
	
	// 执行用户main函数
	fn = main_main
	fn()
	
	// 退出处理
	exit(0)
}

四、关键数据结构初始化

在启动过程中,Go运行时初始化了几个关键数据结构:

  1. g0:第一个Goroutine,用于调度器自身使用
  2. m0:第一个Machine,代表主线程
  3. P列表:处理器列表,数量由GOMAXPROCS决定
  4. 调度器状态:全局调度器sched的初始化

五、总结

Go程序的启动流程是一个从底层汇编到高级运行时逐步初始化的过程:

  1. 通过汇编代码建立最基本的执行环境
  2. 初始化运行时关键数据结构
  3. 准备调度系统(GPM模型)
  4. 创建主Goroutine并开始调度
  5. 执行初始化函数和用户代码

理解这个启动流程对于深入掌握Go语言运行时机制和调试复杂问题都有很大帮助。通过cch123/golang-notes项目的分析,我们可以清晰地看到Go语言如何在各种初始化步骤中构建起完整的运行时环境。

golang-notes Go source code analysis(zh-cn) golang-notes 项目地址: https://gitcode.com/gh_mirrors/go/golang-notes

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卓桢琳Blackbird

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值