runtime的作用
runtime被编译为程序的一部分,一起执行。code+runtime=>二进制。
runtime
- 内存管理
- 垃圾回收 (gc)
- 协程调度. (超强并发能力)
- 有一定的屏蔽系统调用的能力
- 一些go 关键字其实是runtime 的函数
- go => newproc
- new => newobject
- make => makeslice,makechain,makemap …
- <- -> chansend1,chanrecv1
go程序是如何编译的
go build -n 查看编译过程,不实际编译
Go 编译过程: 词法分析->句法分析->语义分析-> 中间码生成->代码优化->机器码生成(.a文件)-> 链接
-
词法分析
将源代码翻译成 token,token 是代码中的最小语义结构
-
句法分析
token序列经过处理,变成语法树
-
语义分析:类型检查、类型推断、函数内敛优化、查看类型是否匹配、逃逸分析
-
中间码生成(ssa)
为了处理不同平台的差异,先生成中间代码(ssa) (汇编)
查看从代码到中间码的整个过程
1 GOSSAFUNC=main go build test.go
1 ➜ bdsp git:(mao_main) GOSSAFUNC=main go build test.go 2 # runtime 3 dumped SSA to /Users/maozhongyu/code/bdsp/ssa.html 4 # command-line-arguments 5 dumped SSA to ./ssa.html
-
机器码生成
先生成plan9汇编代码(平台相关的汇编) ,最后编译为机器码 , 输出机器码为.a文件
查看plan9汇编代码
1 go build -gcflags -S main.go
-
链接
将各个包进行链接,包括runtime,生成二进制可执行文件
go程序是怎么运行的?
本文源码是针对go1.18.5进行分析
使用go build -x 观察编译连接过程
1go build -x hello.go
可执行文件
Linux 的可执行文件 ELF(Executable and Linkable Format) 为例,
ELF 由几部分构成:
- ELF header
- Section header
- Sections
操作系统执行可执行文件的步骤
解析 ELF header,加载文件到内存,从entry point 开始执行代码
readelf 配合dlv 找到程序入口
通过readelf -H中的entry找到程序⼊⼝
1[ubuntu@us_inner ~ 14:31:07]$readelf -h ./hello
2ELF Header:
3 Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
4 Class: ELF64
5 Data: 2's complement, little endian
6 Version: 1 (current)
7 OS/ABI: UNIX - System V
8 ABI Version: 0
9 Type: EXEC (Executable file)
10 Machine: Advanced Micro Devices X86-64
11 Version: 0x1
12 Entry point address: 0x45c020 (入口地址)
13 Start of program headers: 64 (bytes into file)
14 Start of section headers: 456 (bytes into file)
15 Flags: 0x0
16 Size of this header: 64 (bytes)
17 Size of program headers: 56 (bytes)
18 Number of program headers: 7
19 Size of section headers: 64 (bytes)
20 Number of section headers: 23
21 Section header string table index: 3
在dlv调试器中b *entry_addr找到代码位置
1sudo /usr/local/go/bin/go get github.com/go-delve/delve/cmd/dlv
2sudo /usr/local/go/bin/go install github.com/go-delve/delve/cmd/dlv
3
4
5[ubuntu@us_inner ~ 14:39:41]$/home/ubuntu/go/bin/dlv exec ./hello
6Type 'help' for list of commands.
7(dlv) b *0x45c020
8Breakpoint 1 set at 0x45c020 for _rt0_amd64_linux() /usr/local/go/src/runtime/rt0_linux_amd64.s:8
代码分析运行流程详解
上面是linux的,这里是用mac进行分析。
mac,amd64 cpu系统,通过追代码分析,大致流程。
rt0_darwin_amd64.s
1TEXT _rt0_amd64_darwin(SB),NOSPLIT,$-8
2 JMP _rt0_amd64(SB) // 第一步 go程序的入口 : runtime/rt0_平台
asm_amd64.s
1TEXT _rt0_amd64(SB),NOSPLIT,$-8
2 MOVQ 0(SP), DI // argc //第二步 读取命令行参数,复制参数数量argc和参数值argv到栈上
3 LEAQ 8(SP), SI // argv
4 JMP runtime·rt0_go(SB)
5
6
7TEXT runtime·rt0_go(SB),NOSPLIT|TOPFRAME,$0
8 // copy arguments forward on an even stack
9 MOVQ DI, AX // argc
10 MOVQ SI, BX // argv
11 SUBQ $(5*8), SP // 3args 2auto
12 ANDQ $~15, SP
13 MOVQ AX, 24(SP)
14 MOVQ BX, 32(SP)
15
16 // create istack out of the given (operating system) stack.
17 // _cgo_init may update stackguard.
18 MOVQ $runtime·g0(SB), DI // 第三步 初始化g0的执行栈,g0是为了调度协程而产生的协程
19 LEAQ (-64*1024+104)(SP), BX
20 MOVQ BX, g_stackguard0(DI)
21 MOVQ BX, g_stackguard1(DI)
22 MOVQ BX, (g_stack+stack_lo)(DI)
23 MOVQ SP, (g_stack+stack_hi)(DI)
24
25 . //此处代码省略
26 .
27 .
28
29 // runtime1.go check() 方法
30 CALL runtime·check(SB) //314 行 左右。 第四步 运行时检测:检测各种类型的长度、检测指针操作、检测结构体字段的偏移量、检测atomic的原子操作、检测cas操作 等
31
32 MOVL 24(SP), AX // copy argc
33 MOVL AX, 0(SP)
34 MOVQ 32(SP), AX // copy argv
35 MOVQ AX, 8(SP)
36 CALL runtime·args(SB) // 第5步 处理命令行参数runtime.args()
37 CALL runtime·osinit(SB) // 系统字长,cpu是多少核第。一些int 是多少字节的处理等。
38 CALL runtime·schedinit(SB) //第6步 调度器初始化 runtime.schedinit
39
40 // create a new goroutine to start program
41 MOVQ $runtime·mainPC(SB), AX // entry // runtime中的main方法地址
42 PUSHQ AX
43 CALL runtime·newproc(SB) // 第7步 创建主协程 。 创建一个新的协程 ,用来执行runtime.main,等待调度
44 POPQ AX
45
46 // start this M
47 CALL runtime·mstart(SB) //第8步 初始化一个m ,用来调度主协程。
48
49 CALL runtime·abort(SB) // mstart should never return
50 RET
51
52 bad_cpu: // show that the program requires a certain microarchitecture level.
53 MOVQ $2, 0(SP)
54 MOVQ $bad_cpu_msg<>(SB), AX
55 MOVQ AX, 8(SP)
56 MOVQ $84, 16(SP)
57 CALL runtime·write(SB)
58 MOVQ $1, 0(SP)
59 CALL runtime·exit(SB)
60 CALL runtime·abort(SB)
61 RET
62
63 // Prevent dead-code elimination of debugCallV2, which is
64 // intended to be called by debuggers.
65 MOVQ $runtime·debugCallV2<ABIInternal>(SB), AX
66 RET
67
68// mainPC is a function value for runtime.main, to be passed to newproc.
69// The reference to runtime.main is made via ABIInternal, since the
70// actual function (not the ABI0 wrapper) is needed by newproc.
71DATA runtime·mainPC+0(SB)/8,$runtime·main<ABIInternal>(SB) // 主协程执行主函数 runtime 包的main 方法, runtime/proc.go 里的main方法。
72GLOBL runtime·mainPC(SB),RODATA,$8
73
runtime1.go
第四步运行时检测: 检测各种类型的长度、检测指针操作、检测结构体字段的偏移量、检测atomic的原子操作、检测cas操作 等
1func check() {
2 var (
3 a int8
4 b uint8
5 c int16
6 d uint16
7 e int32
8 f uint32
9 g int64
10 h uint64
11 i, i1 float32
12 j, j1 float64
13 k unsafe.Pointer
14 l *uint16
15 m [4]byte
16 )
17 type x1t struct {
18 x uint8
19 }
20 type y1t struct {
21 x1 x1t
22 y uint8
23 }
24 var x1 x1t
25 var y1 y1t
26
27 if unsafe.Sizeof(a) != 1 {
28 throw("bad a")
29 }
30 if unsafe.Sizeof(b) != 1 {
31 throw("bad b")
32 }
33 //此处省略
34 //....
35
36 if unsafe.Offsetof(y1.y) != 1 {
37 throw("bad offsetof y1.y")
38 }
39 if unsafe.Sizeof(y1) != 2 {
40 throw("bad unsafe.Sizeof y1")
41 }
42
43 if timediv(12345*1000000000+54321, 1000000000, &e) != 12345 || e != 54321 {
44 throw("bad timediv")
45 }
46
47 var z uint32
48 z = 1
49 if !atomic.Cas(&z, 1, 2) {
50 throw("cas1")
51 }
52 if z != 2 {
53 throw("cas2")
54 }
55
56//此处省略
57//....
58
59 if z != 0xfffffffe {
60 throw("cas6")
61 }
62
63 m = [4]byte{1, 1, 1, 1}
64 atomic.Or8(&m[1], 0xf0)
65 if m[0] != 1 || m[1] != 0xf1 || m[2] != 1 || m[3] != 1 {
66 throw("atomicor8")
67 }
68
69 m = [4]byte{0xff, 0xff, 0xff, 0xff}
70 atomic.And8(&m[1], 0x1)
71 if m[0] != 0xff || m[1] != 0x1 || m[2] != 0xff || m[3] != 0xff {
72 throw("atomicand8")
73 }
74
75 *(*uint64)(unsafe.Pointer(&j)) = ^uint64(0)
76 if j == j {
77 throw("float64nan")
78 }
79 if !(j != j) {
80 throw("float64nan1")
81 }
82
83 *(*uint64)(unsafe.Pointer(&j1)) = ^uint64(1)
84 if j == j1 {
85 throw("float64nan2")
86 }
87 // ....省略
88
89 testAtomic64()
90
91 if _FixedStack != round2(_FixedStack) {
92 throw("FixedStack is not power-of-2")
93 }
94
95 if !checkASM() {
96 throw("assembly checks failed")
97 }
98}
runtime/proc.go
1
2
3// start forcegc helper goroutine
4// 第9步 执行runtime包的init方法
5func init() {
6 go forcegchelper()
7}
8
9func forcegchelper() {
10 forcegc.g = getg()
11 // 第10步 启动gc垃圾回收器
12 lockInit(&forcegc.lock, lockRankForcegc)
13 for {
14 lock(&forcegc.lock)
15 if forcegc.idle != 0 {
16 throw("forcegc: phase error")
17 }
18 atomic.Store(&forcegc.idle, 1)
19 goparkunlock(&forcegc.lock, waitReasonForceGCIdle, traceEvGoBlock, 1)
20 // this goroutine is explicitly resumed by sysmon
21 if debug.gctrace > 0 {
22 println("GC forced")
23 }
24 // Time-triggered, fully concurrent.
25 gcStart(gcTrigger{kind: gcTriggerTime, now: nanotime()})
26 }
27}
28
29
30//第11步 执行runtime包的main方法
31// The main goroutine.
32func main() {
33 g := getg()
34
35 // Racectx of m0->g0 is used only as the parent of the main goroutine.
36 // It must not be used for anything else.
37 g.m.g0.racectx = 0
38
39 // Max stack size is 1 GB on 64-bit, 250 MB on 32-bit.
40 // Using decimal instead of binary GB and MB because
41 // they look nicer in the stack overflow failure message.
42 if goarch.PtrSize == 8 {
43 maxstacksize = 1000000000
44 } else {
45 maxstacksize = 250000000
46 }
47
48 // An upper limit for max stack size. Used to avoid random crashes
49 // after calling SetMaxStack and trying to allocate a stack that is too big,
50 // since stackalloc works with 32-bit sizes.
51 maxstackceiling = 2 * maxstacksize
52
53 // Allow newproc to start new Ms.
54 mainStarted = true
55
56 if GOARCH != "wasm" { // no threads on wasm yet, so no sysmon
57 systemstack(func() {
58 newm(sysmon, nil, -1)
59 })
60 }
61
62 // Lock the main goroutine onto this, the main OS thread,
63 // during initialization. Most programs won't care, but a few
64 // do require certain calls to be made by the main thread.
65 // Those can arrange for main.main to run in the main thread
66 // by calling runtime.LockOSThread during initialization
67 // to preserve the lock.
68 lockOSThread()
69
70 if g.m != &m0 {
71 throw("runtime.main not on m0")
72 }
73
74 // Record when the world started.
75 // Must be before doInit for tracing init.
76 runtimeInitTime = nanotime()
77 if runtimeInitTime == 0 {
78 throw("nanotime returning zero")
79 }
80
81 if debug.inittrace != 0 {
82 inittrace.id = getg().goid
83 inittrace.active = true
84 }
85
86 doInit(&runtime_inittask) // Must be before defer.
87
88 // Defer unlock so that runtime.Goexit during init does the unlock too.
89 needUnlock := true
90 defer func() {
91 if needUnlock {
92 unlockOSThread()
93 }
94 }()
95
96 gcenable()
97
98 main_init_done = make(chan bool)
99 if iscgo {
100 if _cgo_thread_start == nil {
101 throw("_cgo_thread_start missing")
102 }
103 if GOOS != "windows" {
104 if _cgo_setenv == nil {
105 throw("_cgo_setenv missing")
106 }
107 if _cgo_unsetenv == nil {
108 throw("_cgo_unsetenv missing")
109 }
110 }
111 if _cgo_notify_runtime_init_done == nil {
112 throw("_cgo_notify_runtime_init_done missing")
113 }
114 // Start the template thread in case we enter Go from
115 // a C-created thread and need to create a new thread.
116 startTemplateThread()
117 cgocall(_cgo_notify_runtime_init_done, nil)
118 }
119
120 // 第12步 执行用户包依赖的init方法
121 doInit(&main_inittask)
122
123 // Disable init tracing after main init done to avoid overhead
124 // of collecting statistics in malloc and newproc
125 inittrace.active = false
126
127 close(main_init_done)
128
129 needUnlock = false
130 unlockOSThread()
131
132 if isarchive || islibrary {
133 // A program compiled with -buildmode=c-archive or c-shared
134 // has a main, but it is not executed.
135 return
136 }
137
138 // 第13步 执行用户主函数 main.main(), 这个才是我们平常main包中的main方法
139 fn := main_main // make an indirect call, as the linker doesn't know the address of the main package when laying down the runtime
140 fn()
141 if raceenabled {
142 racefini()
143 }
144
145 // Make racy client program work: if panicking on
146 // another goroutine at the same time as main returns,
147 // let the other goroutine finish printing the panic trace.
148 // Once it does, it will exit. See issues 3934 and 20018.
149 if atomic.Load(&runningPanicDefers) != 0 {
150 // Running deferred functions should not take long.
151 for c := 0; c < 1000; c++ {
152 if atomic.Load(&runningPanicDefers) == 0 {
153 break
154 }
155 Gosched()
156 }
157 }
158 if atomic.Load(&panicking) != 0 {
159 gopark(nil, nil, waitReasonPanicWait, traceEvGoStop, 1)
160 }
161
162 exit(0)
163 for {
164 var x *int32
165 *x = 0
166 }
167}
流程总结
-
go程序的入口 : runtime/rt0_平台.s,如 rt0_darwin_amd64.s
-
读取命令行参数,复制参数数量argc和参数值argv到栈上
-
初始化g0的执行栈,g0是为了调度协程而产生的协程
上面代码就是初始化go
G0 是每个go程序的 第一个协程 (万物之母) (汇编代码?)
-
运行时检测:检测各种类型的长度、检测指针操作、检测结构体字段的偏移量、检测atomic的原子操作、检测cas操作 等
-
处理命令行参数runtime.args()
-
调度器初始化 runtime.schedinit
- 全局栈空间内存分配
- 加载命令参数到os.Args
- 堆内存空间的初始化
- 加载操作系统环境变量
- 初始化当前系统线程
- 垃圾回收器参数初始化
- 算法初始化(map、hash)
- 设置proess数量(gpm中的p)
-
创建主协程
创建一个新的协程,执行runtime.main
放入调度器等待调度
-
初始化一个m ,用来调度主协程
-
执行runtime包的init方法
主协程要去调runtime包的main方法,那么先要执行它的init方法 和用户包init 顺序是一致的
-
启动gc垃圾回收器
-
主协程执行主函数
runtime包的main方法
-
执行用户包依赖的init方法
-
执行用户主函数 main.main()
go 启动时经历了检查,各种初始化,初始化协成调度的过程 ,main.main() 也是在协程中运行的