Go 代码初始化过程

Go init

Posted by cslqm on December 14, 2020

从上一篇引导已经知道,最后,我们来到了初始化函数。

1
2
3
4
5
6
(gdb) b runtime.args                                                                           
Breakpoint 4 at 0x437a40: file /usr/lib/golang/src/runtime/runtime1.go, line 60.
(gdb) b runtime.osinit
Breakpoint 5 at 0x427a20: file /usr/lib/golang/src/runtime/os_linux.go, line 289.
(gdb) b runtime.schedinit
Breakpoint 6 at 0x42c0c0: file /usr/lib/golang/src/runtime/proc.go, line 529.

runtime.args 整理命令行参数,sysagrs 不同平台有不同实现。

1
2
3
4
5
func args(c int32, v **byte) {
        argc = c
        argv = v
        sysargs(c, v)
}

runtime.osinit 获得 cpu core 信息和大页内存。

1
2
3
4
func osinit() {
        ncpu = getproccount()
        physHugePageSize = getHugePageSize()
}

在系统启动期间,你能用“大内存页”为应用程序预留一部分内存。

在虚拟内存管理中,内核维护一个将虚拟内存地址映射到物理地址的表,对于每个页面操作,内核都需要加载相关的映射。如果你的内存页很小,那么你需要加载的页就会很多,导致内核会加载更多的映射表。而这会降低性能。

使用“大内存页”,意味着所需要的页变少了。从而大大减少由内核加载的映射表的数量。这提高了内核级别的性能最终有利于应用程序的性能。

简而言之,通过启用“大内存页”,系统具只需要处理较少的页面映射表,从而减少访问/维护它们的开销!

runtime.schedinit 运行时环境初始化。

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
// The bootstrap sequence is:
//
//      call osinit
//      call schedinit
//      make & queue new G
//      call runtime·mstart
//
// The new G calls runtime·main.
func schedinit() {
        // raceinit must be the first call to race detector.
        // In particular, it must be done before mallocinit below calls racemapshadow.
        _g_ := getg()
        if raceenabled {
                _g_.racectx, raceprocctx0 = raceinit()
        }
        // 最大系统线程数量限制
        sched.maxmcount = 10000
        // 代码异常时使用
        tracebackinit()
        moduledataverify()
        // 栈
        stackinit()    
        // 内存分配器 
        mallocinit()
        // 调度器
        mcommoninit(_g_.m)
        cpuinit()       // must run before alginit
        alginit()       // maps must not be used before this call
        modulesinit()   // provides activeModules
        typelinksinit() // uses maps, activeModules
        itabsinit()     // uses activeModules
                                
        msigsave(_g_.m)         
        initSigmask = _g_.m.sigmask

        // 处理命令行参数和环境变量
        goargs()
        goenvs()

        // 处理 GODEBUG GOTRACEBACK 调试相关的环境变量设置
        parsedebugvars()

        // 垃圾回收器初始化
        gcinit()
        
        // 通过 CPU Core 和 GOMAXPROCS 环境变量确定 P 的数量
        sched.lastpoll = uint64(nanotime())
        procs := ncpu
        if n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 {
                procs = n
        }
        if procresize(procs) != nil {
                throw("unknown runnable goroutine during bootstrap")
        }

        // For cgocheck > 1, we turn on the write barrier at all times
        // and check all pointer writes. We can't do this until after
        // procresize because the write barrier needs a P.
        if debug.cgocheck > 1 {
                writeBarrier.cgo = true
                writeBarrier.enabled = true
                for _, p := range allp {
                        p.wbBuf.reset()
                }
        }

        if buildVersion == "" {
                // Condition should never trigger. This code just serves
                // to ensure runtime·buildVersion is kept in the resulting binary.
                buildVersion = "unknown"
        }
        if len(modinfo) == 1 {
                // Condition should never trigger. This code just serves
                // to ensure runtime·modinfo is kept in the resulting binary.
                modinfo = ""
        }
}

事实上,初始化操作到此并没有结束,因为接下来要执行的是 runtime.main。

1
2
(gdb) b runtime.main
Breakpoint 8 at 0x42aea0: file /usr/lib/golang/src/runtime/proc.go, line 113.

proc.go

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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
func main(){
        g := getg()

        // Racectx of m0->g0 is used only as the parent of the main goroutine.
        // It must not be used for anything else.
        g.m.g0.racectx = 0

        // Max stack size is 1 GB on 64-bit, 250 MB on 32-bit.
        // Using decimal instead of binary GB and MB because
        // they look nicer in the stack overflow failure message.
        // 执行栈的最大限制
        if sys.PtrSize == 8 {
                maxstacksize = 1000000000
        } else {
                maxstacksize = 250000000
        }

        // Allow newproc to start new Ms.
        mainStarted = true

        // 启动系统后台监控(定期垃圾回收,以及并发任务调度相关)
        if GOARCH != "wasm" { // no threads on wasm yet, so no sysmon
                systemstack(func() {
                        newm(sysmon, nil)
                })
        }

        // Lock the main goroutine onto this, the main OS thread,
        // during initialization. Most programs won't care, but a few
        // do require certain calls to be made by the main thread.
        // Those can arrange for main.main to run in the main thread
        // by calling runtime.LockOSThread during initialization
        // to preserve the lock.
        lockOSThread()

        if g.m != &m0 {
                throw("runtime.main not on m0")
        }

        // 具体没有找打初始化了什么。 其他地方看到的说法:执行 runtime 包内所有的初始化函数 init。另一个地方看到的说法是创建了非常多的系统线程。
        doInit(&runtime_inittask) // must be before defer
        if nanotime() == 0 {
                throw("nanotime returning zero")
        }

        // Defer unlock so that runtime.Goexit during init does the unlock too.
        needUnlock := true
        defer func() {
                if needUnlock {
                        unlockOSThread()
                }
        }()

        // Record when the world started.
        runtimeInitTime = nanotime()

        // 启动垃圾回收器后台操作
        gcenable()

        main_init_done = make(chan bool)
        if iscgo {
                if _cgo_thread_start == nil {
                        throw("_cgo_thread_start missing")
                }
                if GOOS != "windows" {
                        if _cgo_setenv == nil {
                                throw("_cgo_setenv missing")
                        }
                        if _cgo_unsetenv == nil {
                                throw("_cgo_unsetenv missing")
                        }
                }
                if _cgo_notify_runtime_init_done == nil {
                        throw("_cgo_notify_runtime_init_done missing")
                }
                // Start the template thread in case we enter Go from
                // a C-created thread and need to create a new thread.
                startTemplateThread()
                cgocall(_cgo_notify_runtime_init_done, nil)
        }

        // 执行所有的用户包(包括标准库)初始化函数 init
        doInit(&main_inittask)

        close(main_init_done)

        needUnlock = false
        unlockOSThread()

        // 执行用户逻辑入口 main.main 函数  main_main 是 main.main 的链接名
        if isarchive || islibrary {
                // A program compiled with -buildmode=c-archive or c-shared
                // has a main, but it is not executed.
                return
        }
        fn := main_main // make an indirect call, as the linker doesn't know the address of the main package when laying down the runtime
        fn()
        if raceenabled {
                racefini()
        }

        // Make racy client program work: if panicking on
        // another goroutine at the same time as main returns,
        // let the other goroutine finish printing the panic trace.
        // Once it does, it will exit. See issues 3934 and 20018.
        if atomic.Load(&runningPanicDefers) != 0 {
                // Running deferred functions should not take long.
                for c := 0; c < 1000; c++ {
                        if atomic.Load(&runningPanicDefers) == 0 {
                                break
                        }
                        Gosched()
                }
        }
        if atomic.Load(&panicking) != 0 {
                gopark(nil, nil, waitReasonPanicWait, traceEvGoStop, 1)
        }

        exit(0)
        for {
                var x *int32
                *x = 0
        }
}

runtime.main 函数的工作

main_main() 是编译器动态生成,更具体来说是链接器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//go:linkname runtime_inittask runtime..inittask
var runtime_inittask initTask

//go:linkname main_inittask main..inittask
var main_inittask initTask

// main_init_done is a signal used by cgocallbackg that initialization
// has been completed. It is made before _cgo_notify_runtime_init_done,
// so all cgo calls can rely on it existing. When main_init is complete,
// it is closed, meaning cgocallbackg can reliably receive from it.
var main_init_done chan bool

//go:linkname main_main main.main
func main_main()

注意:链接后,符号名的变化: runtime_init > runtime.init; main_main > main.main

  • 所有 init 函数都在同一个 goroutine 内执行
  • 所有 init 函数结束后才会执行 main.main 函数