Go 语言程序启动后,需要对自身运行时进行初始化,其真正的程序入口由 runtime 包控制。
以 AMD64 架构上的 Linux 和 macOS 为例,分别位于:src/runtime/rt0_linux_amd64.s
和 src/runtime/rt0_darwin_amd64.s
。
1
2
3
4
| TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8
JMP _rt0_amd64(SB)
TEXT _rt0_amd64_darwin(SB),NOSPLIT,$-8
JMP _rt0_amd64(SB)
|
两者均跳转到了 _rt0_amd64
函数:
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
| TEXT _rt0_amd64(SB),NOSPLIT,$-8
MOVQ 0(SP), DI // argc
LEAQ 8(SP), SI // argv
JMP runtime·rt0_go(SB)
TEXT runtime·rt0_go(SB),NOSPLIT,$0
// 将参数向前复制到一个偶数栈上
MOVQ DI, AX // argc
MOVQ SI, BX // argv
SUBQ $(4*8+7), SP // 2args 2auto
ANDQ $~15, SP
MOVQ AX, 16(SP)
MOVQ BX, 24(SP)
// 初始化 g0 执行栈
MOVQ $runtime·g0(SB), DI // DI = g0
LEAQ (-64*1024+104)(SP), BX
MOVQ BX, g_stackguard0(DI) // g0.stackguard0 = SP + (-64*1024+104)
MOVQ BX, g_stackguard1(DI) // g0.stackguard1 = SP + (-64*1024+104)
MOVQ BX, (g_stack+stack_lo)(DI) // g0.stack.lo = SP + (-64*1024+104)
MOVQ SP, (g_stack+stack_hi)(DI) // g0.stack.hi = SP
// 确定 CPU 处理器的信息
MOVL $0, AX
CPUID // CPUID 会设置 AX 的值
MOVL AX, SI
(...)
|
sysmon是一个在main.main()
执行之前的runtime初始化中启动物理线程,,主要处理两个事件:
1
2
3
4
5
6
7
8
9
| newm(sysmon, nil); //sysmon 是一个m, 物理线程;
for(;;) {
runtime.usleep(delay);
if(lastpoll != 0 && lastpoll + 10*1000*1000 > now) {
runtime.netpoll();
}
retake(now); // 根据每个P的状态和运行时间决定是否要进行抢占
}
|
scavenger是一个goroutine,执行的是runtime.MHeap_Scavenger
函数。
它将一些不再使用的内存归还给操作系统,用于执行内存回收;
1
| runtime·newproc(&scavenger, nil, 0, 0, runtime·main); //scavenger 是一个goroutine
|
Go语言中,表达式go f(x, y, z)会启动一个新的goroutine运行函数f(x, y, z)
1
2
3
| go f(args)
//go 关键字是如下语句的一个包装
runtime.newproc(size, f, args)
|
defer
关键字的实现跟go
关键字很类似,不同的是它调用的是runtime.deferproc
而不是runtime.newproc
。
在defer出现的地方,插入了指令call runtime.deferproc
,然后在函数返回之前的地方,插入指令call runtime.deferreturn
。
goroutine的控制结构中,有一张表记录defer,
调用runtime.deferproc
时会将需要defer
的表达式记录在表中,而在调用runtime.deferreturn
的时候,则会依次从defer表中出栈并执行。
1
2
3
4
5
6
7
8
| //无defer函数返回
add xx SP
return
//defer 函数返回
call runtime.deferreturn,
add xx SP
return
|
每个go routine
需要能够运行,都有自己的栈。
初始时只给栈分配很小的空间,然后随着使用过程中的需要自动地增长
Go1.3版本之后则使用的是continuous stack;
每个Go函数调用的前几条指令,先比较栈指针寄存器跟g->stackguard,检测是否发生栈溢出。如果栈指针寄存器值超越了stackguard就需要扩展栈空间;
Go语言程序初始化过程 - 系统初始化 - 《深入解析Go》 - 书栈网 · BookStack
https://golang.design/under-the-hood/zh-cn/part1basic/ch05life/boot/
深入理解golang 的栈 - ma_fighting - 博客园
深入研究goroutine栈 | 花木兰
https://zhuanlan.zhihu.com/p/237870981
https://segmentfault.com/a/1190000019570427