返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

1.2 初始化

发布于 2024-10-12 19:16:02 字数 8811 浏览 0 评论 0 收藏 0

在引导过程中,先调用了:

参考上一节 runtime·rt0_go 代码。

  • runtime·args
  • runtime·osinit
  • runtime·schedinit

然后才是 main goroutine (runtime.main) 执行。

osinit

系统环境初始化。

// os_linux.go

func osinit() {
    ncpu = getproccount()    // 逻辑处理器数量。
}
// runtime2.go

var ncpu int32

标准库 runtime.NumCPU 就是返回该变量值。

// debug.go

// NumCPU returns the number of logical CPUs usable by the current process.
func NumCPU() int {
    return int(ncpu)
}

schedinit

调度器初始化。

// proc.go

// The bootstrap sequence is:
//
//   call osinit
//   call schedinit
//   make & queue new G
//   call runtime·mstart
//
// The new G calls runtime·main.

func schedinit() {
    // M 默认最大值。
    sched.maxmcount = 10000       
   
    // 检查 shared library、plugin 哈希值。如果不符,会引发 ABI mismatch 错误。
    moduledataverify()            
    
    // 内存相关初始化。
    stackinit()                   
    mallocinit()

    // 初始化当前 M。
    mcommoninit(_g_.m, -1)            

    // 处理参数、环境变量(包括 GODEBUG、GOTRACEBACK)。
    goargs()
    goenvs()
    parsedebugvars()
    
    // 垃圾回收器初始化。
    gcinit()
    
    // 初始化 poll 时间。
    sched.lastpoll = uint64(nanotime())
    
    // GOMAXPROCS 设置。
    procs := ncpu
    if n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 {
        procs = n
    }
    if procresize(procs) != nil {
        throw("unknown runnable goroutine during bootstrap")
    }
}

从 1.10 起,取消了 GOMAXPROCS 256 限制。

runtime.main

主 “线程” 执行,包初始化。

// proc.go

// The main goroutine.
func main() {
    
    // G.stack 最大值(1 GB)。
    if sys.PtrSize == 8 {
        maxstacksize = 1000000000
    } else {
        maxstacksize = 250000000
    }
    
    // main.G 启动标记,允许启动新的 M。
    mainStarted = true
    
    // 启动系统监控。
    newm(sysmon, nil, -1)
    
    // 执行 runtime 包初始化函数。
    doInit(&runtime_inittask)
    
    // 初始化时间。
    runtimeInitTime = nanotime()
    
    // 启动垃圾回收器。
    gcenable()
    
    // 执行用户、标准库、第三方包初始化函数。
    doInit(&main_inittask)
    
    // 动态库模式,不执行入口函数。
    if isarchive || islibrary {
        // A program compiled with -buildmode=c-archive or c-shared
        // has a main, but it is not executed.
        return
    }
    
    // 执行 main.main 用户入口函数。
    fn := main_main
    fn()
    
    // 退出。
    exit(0)
}

init

每个包持有一个独立 initTask 结构,存储初始化函数(init)信息。

相比 1.12 自动生成(autogenerated) runtime.init、main.init 函数,从 1.13 起改为 doInit 执行模式。

// proc.go

// An initTask represents the set of initializations that need to be done for a package.
type initTask struct {
    // TODO: pack the first 3 fields more tightly?
    state uintptr // 0 = uninitialized, 1 = in progress, 2 = done
    ndeps uintptr
    nfns  uintptr
    // followed by ndeps instances of an *initTask, one per package depended on
    // followed by nfns pcs, one per init function to run
}

按依赖关系,为每个包收集所有初始化函数。并以两个全局变量构成链式结构。

//go:linkname runtime_inittask runtime..inittask
var runtime_inittask initTask

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

例如:

// test/main.go

package main

import (
	_ "net/http"
	"test/mylib"
)

func init() {
	println(1)
}

func init() {
	println(2)
}

func main() {
	mylib.Hello()
}
// test/mylib/lib.go

package mylib

func init() {
	println("mylib.init")
}

func Hello() {
	println("hello, world!")
}

依赖关系示意图:

 +----------------------+
 | main..inittask       |                +-------------------+
 +----------------------+         +----> | net/http.inittask |
 |   state              |         |      +-------------------+
 +----------------------+         |      |  state            |
 |   ndeps = 2          |         |      +-------------------+
 +----------------------+         |      |  ndeps            |        +----------------+
 |   nfns = 2           |         |      +-------------------+        | mylib.inittask |
 +----------------------+         |      |  nfns             |        +----------------+
 |   *net/http.inittask | --------+      +-------------------+        |  state         |
 +----------------------+                |  ...              |        +----------------+
 |   *mylib.inittask    | --------+      +-------------------+        |  ndeps = 0     |
 +----------------------+         |                                   +----------------+
 |   *main.init.0       |         |                                   |  nfns = 1      |
 +----------------------+         |                                   +----------------+
 |   *main.init.1       |         +---------------------------------> |  *mylib.init.0 |
 +----------------------+                                             +----------------+

多个 init 会添加数字后缀,比如 runtime.init.1 ,如此才允许有多个同名函数存在。

通过编译输出,查看元数据信息。

$ go build -gcflags "-N -l -S" 2>a.txt

""..inittask SNOPTRDATA size=56
 0x0000 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00  ................
 0x0010 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
 0x0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
 0x0030 00 00 00 00 00 00 00 00                          ........
 rel 24+8 t=1 net/http..inittask+0
 rel 32+8 t=1 test/mylib..inittask+0
 rel 40+8 t=1 "".init.0+0
 rel 48+8 t=1 "".init.1+0
$ go build -gcflags "-N -l -S" test/mylib 2>b.txt

""..inittask SNOPTRDATA size=32
 0x0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
 0x0010 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
 rel 24+8 t=1 "".init.0+0

也可用调试器查看。

(gdb) b runtime.main
(gdb) r

(gdb) info address main..inittask
Symbol "main..inittask" is static storage at address 0x891ac0.

(gdb) b runtime.doInit if t=0x891ac0

(gdb) c
Thread 1 "test" hit Breakpoint 2, runtime.doInit (t=0x891ac0 <main..inittask>) 

(gdb) p *t
$1 = {
  state = 0, 
  ndeps = 2, 
  nfns = 2
}

(gdb) x/8xg t
0x891ac0 <main..inittask>:       0x0000000000000000  0x0000000000000002
0x891ad0 <main..inittask+16>:    0x0000000000000002  0x0000000000896e20
0x891ae0 <main..inittask+32>:    0x00000000008902a0  0x0000000000632070
0x891af0 <main..inittask+48>:    0x00000000006320c0  0x0000000000000000

(gdb) info symbol 0x0000000000896e20
net/http..inittask in section .noptrdata

(gdb) info symbol 0x00000000008902a0
test/mylib..inittask in section .noptrdata

(gdb) info symbol 0x0000000000632070
main.init in section .text

(gdb) info symbol 0x00000000006320c0
main.init in section .text

(gdb) x/4xg 0x00000000008902a0
0x8902a0 <test/mylib..inittask>:     0x0000000000000000  0x0000000000000000
0x8902b0 <test/mylib..inittask+16>:  0x0000000000000001  0x0000000000631fb0

(gdb) info symbol 0x0000000000631fb0
test/mylib.init in section .text

通过这些预存信息,doInit 就可递归遍历,执行所有初始化函数。且确保被依赖包优先执行。

// proc.go

func doInit(t *initTask) {
    switch t.state {
    case 2: // fully initialized
        return
    case 1: // initialization in progress
        throw("recursive call during initialization - linker skew")
    default: // not initialized yet
        
        // 修改状态。
        t.state = 1 // initialization in progress
        
        // 通过 initTask.ndeps 获取依赖包数量。
        for i := uintptr(0); i < t.ndeps; i++ {
            // 通过偏移量,获取地址。
            p := add(unsafe.Pointer(t), (3+i)*sys.PtrSize)
            t2 := *(**initTask)(p)
            
            // 递归依赖包初始化。
            doInit(t2)
        }
        
        // 通过 initTask.nfns 获取当前包初始化函数数量。
        for i := uintptr(0); i < t.nfns; i++ {
            // 计算偏移量,获取地址。
            p := add(unsafe.Pointer(t), (3+t.ndeps+i)*sys.PtrSize)
            
            // 转换为函数类型,执行初始化函数。
            f := *(*func())(unsafe.Pointer(&p))
            f()
        }
        t.state = 2 // initialization done
    }
}

exit

main.main 调用结束,直接通过系统调用 sys_exit_group 终止进程。

等初始化函数执行结束后,才执行 main.main。

并不会等待其他 goroutine 结束。

由内核实现的 sys_exit_group 会终止所有线程。

exit_group − Same as _exit(2) , but kills all threads in the current thread group, not just the current thread.

void sys_exit_group (int error_code);

// sys_linux_amd64.s

TEXT runtime·exit(SB),NOSPLIT,$0-4
    MOVL    code+0(FP), DI
    MOVL    $SYS_exit_group, AX
    SYSCALL
    RET

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文