1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
| // TEXT runtime·rt0_go(SB),NOSPLIT|NOFRAME|TOPFRAME,$0
// Go 运行时最早入口。NOSPLIT 禁止栈分裂,NOFRAME 不建立常规栈帧,TOPFRAME 标记为栈顶帧(便于回溯)。
TEXT runtime·rt0_go(SB),NOSPLIT|NOFRAME|TOPFRAME,$0
// ==================== 第一阶段:栈初始化和参数保存 ====================
// copy arguments forward on an even stack
// 保证栈对齐后把 argc/argv 挪到新的栈槽上(SysV ABI 需要 16 字节对齐)
MOVQ DI, AX // argc - 将命令行参数个数从 DI 寄存器保存到 AX
// 入参寄存器 DI 保存 argc(根据 System V ABI,第一个整数参数通过 RDI 传递)
MOVQ SI, BX // argv - 将命令行参数数组指针从 SI 寄存器保存到 BX
// 入参寄存器 SI 保存 argv(第二个整数参数通过 RSI 传递)
SUBQ $(5*8), SP // 3args 2auto - 在栈上预留 40 字节空间
// 预留 5*8=40 字节的栈空间:3个参数位(24字节)+ 2个自动变量槽(16字节)
// 早期调用约定使用栈传参,需要为后续函数调用准备参数空间
ANDQ $~15, SP // 栈指针对齐到 16 字节边界
// 使用位运算清除 SP 的低 4 位,确保栈指针按 16 字节对齐
// System V ABI 要求栈在函数调用时必须 16 字节对齐
MOVQ AX, 24(SP) // 将 argc 存储到栈偏移 24 的位置
// 把 argc 写入对齐后的栈槽,为后续 runtime·args 调用准备参数
MOVQ BX, 32(SP) // 将 argv 存储到栈偏移 32 的位置
// 把 argv 写入对齐后的栈槽,为后续 runtime·args 调用准备参数
// ==================== 第二阶段:g0 栈空间初始化 ====================
// 为 g0(调度器专用 goroutine)设定一个临时的 64KB 栈窗口和栈哨兵,供引导期使用
MOVQ $runtime·g0(SB), DI // 加载全局 g0 结构体的地址到 DI
// runtime·g0 是全局的调度器 goroutine,用于执行调度器代码和系统调用
LEAQ (-64*1024)(SP), BX // 计算栈底地址:当前 SP - 64KB
// 为 g0 分配 64KB 的栈空间,LEA 指令计算有效地址而不访问内存
MOVQ BX, g_stackguard0(DI) // 设置栈保护边界 0(用于栈增长检查)
// stackguard0 用于检测栈溢出,当 SP 接近此值时触发栈扩容
MOVQ BX, g_stackguard1(DI) // 设置栈保护边界 1(用于 C 代码栈检查)
// stackguard1 主要用于 C 代码的栈检查,通常与 stackguard0 相同
MOVQ BX, (g_stack+stack_lo)(DI) // 设置栈的低地址边界
// 记录栈的起始地址(低地址),用于垃圾回收器扫描栈空间
MOVQ SP, (g_stack+stack_hi)(DI) // 设置栈的高地址边界
// 记录栈的结束地址(高地址),栈从高地址向低地址增长
// ==================== 第三阶段:TLS(线程本地存储)初始化 ====================
// 建立 TLS,令该 OS 线程的 TLS 槽指向 m0.tls;后续可用 get_tls 快速取 g/m
LEAQ runtime·m0+m_tls(SB), DI // 计算 m0.tls 字段的地址
// m0 是主线程对应的 M 结构,m_tls 是其 TLS 存储区域
CALL runtime·settls(SB) // 调用平台相关的 TLS 设置函数
// settls 将 m0.tls 地址设置到当前线程的 TLS 槽中,使得后续可以快速访问
// ==================== 第四阶段:G-M 关联建立 ====================
// set the per-goroutine and per-mach "registers"
// 把 g/m 写入 TLS 中的约定槽位(编译器内置伪寄存器访问)
get_tls(BX) // 获取当前线程的 TLS 基址到 BX
// get_tls 是编译器内置宏,展开为平台相关的 TLS 访问代码
LEAQ runtime·g0(SB), CX // 加载 g0 地址到 CX
MOVQ CX, g(BX) // 将 g0 设置为当前 goroutine
// g(BX) 是编译器约定的 TLS 槽位,存储当前执行的 goroutine
LEAQ runtime·m0(SB), AX // 加载 m0 地址到 AX
// save m->g0 = g0 - 建立 M 到 G 的引用
// m0.g0 = &g0 - 让 m0 知道它的调度 goroutine 是 g0
MOVQ CX, m_g0(AX) // 设置 m0.g0 = g0
// save m0 to g0->m - 建立 G 到 M 的反向引用
// g0.m = &m0 - 让 g0 知道它运行在 m0 上
MOVQ AX, g_m(CX) // 设置 g0.m = m0
// ==================== 第五阶段:运行时初始化调用序列 ====================
// 恢复 argc/argv,按 ABI0 的栈传参方式调用早期 C/Go 桥接函数
MOVL 24(SP), AX // 从栈中恢复 argc(32位加载)
MOVL AX, 0(SP) // 将 argc 作为第一个参数放到栈顶
MOVQ 32(SP), AX // 从栈中恢复 argv(64位加载)
MOVQ AX, 8(SP) // 将 argv 作为第二个参数放到栈偏移8处
CALL runtime·args(SB)
// 调用 runtime·args 解析命令行参数和环境变量
// 把 argc/argv 解析并复制到受管内存(避免后续 C 层改动)
CALL runtime·osinit(SB)
// 调用操作系统相关初始化函数
// 探测 CPU 数、页大小、时间源、初始化信号子系统基本状态等(平台相关早期信息)
CALL runtime·schedinit(SB)
// 调用调度器初始化函数 - 这是最关键的初始化步骤
// 初始化调度器/内存/GC/类型链接等核心子系统;保证 newproc/mstart 之前环境有效
// 初始化顺序:ticks→stack→rand→malloc→cpu→alg→mcommon→modules/typelinks/itabs/stkobj→gc→GOMAXPROCS/procresize
// ==================== 第六阶段:主 Goroutine 创建 ====================
// create a new goroutine to start program
// 构造第一个用户 goroutine,入口是 runtime.main(经间接全局 mainPC 取地址)
MOVQ $runtime·mainPC(SB), AX // 加载 runtime.main 函数地址
// mainPC 是一个全局变量,存储 runtime.main 函数的地址
PUSHQ AX // 将函数地址压栈作为 newproc 的参数
CALL runtime·newproc(SB) // 调用 newproc 创建新的 goroutine
// 在 g0 的系统栈上分配新的 G 结构,初始化其用户栈与入口 PC,入队到当前 P 的 runq
POPQ AX // 清理栈上的参数
// ==================== 第七阶段:启动调度器 ====================
// start this M - 启动当前 M 的调度循环
// 进入调度循环,从当前 P 的 runq 取出刚刚创建的 runtime.main 开始执行
CALL runtime·mstart(SB) // 启动 M 的调度循环,永不返回
// ==================== 异常处理 ====================
CALL runtime·abort(SB) // mstart should never return
// 正常情况下 mstart 永不返回;如果返回则说明出现严重错误,直接 abort
RET // 函数返回(实际上永远不会执行到这里)
|