返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

1.2 初始化

发布于 2024-10-12 19:15:55 字数 7469 浏览 0 评论 0 收藏 0

在 rt0_go 内,先完成环境初始化,然后再启动运行时。

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

系统

系统环境相关的主要是逻辑 CPU 核数量,以及 HugePage 页大小。

Logical processors are simply a measure of how many Cores the operating system sees and can address. It is, therefore, the product (multiplication) of the number of physical cores with the number of threads each core can handle.

HugePages is a feature integrated into the Linux kernel 2.6. Enabling HugePages makes it possible for the operating system to support memory pages greater than the default (usually 4 KB). Using very large page sizes can improve system performance by reducing the amount of system resources required to access page table entries. HugePages is useful for both 32-bit and 64-bit configurations. HugePage sizes vary from 2 MB to 256 MB, depending on the kernel version and the hardware architecture.

// os_linux.go

func osinit() {
	ncpu = getproccount()
	physHugePageSize = getHugePageSize()

    osArchInit()
}
// runtime2.go

var ncpu int32

标准库 debug 返回的核数量就是该变量。

// debug.go

// NumCPU returns the number of logical CPUs usable by the current process.

func NumCPU() int {
	return int(ncpu)
}

调度器

调度器(scheduler)执行前,需要对内存分配器、垃圾回收器等一系列部件进行初始化。

相关部件初始化留待后续章节再论,此处仅观察初步流程。

// 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

	// The world starts stopped.
	worldStopped()

    // 检查,避免引发 ABI mismatch 错误。
	moduledataverify()

    // 内存。
	stackinit()
	mallocinit()
    
    // 当前 M。
	mcommoninit(_g_.m, -1)
    
	// 参数。
	goargs()
	goenvs()
	parsedebugvars()
    
    // 垃圾回收器。
	gcinit()

	// 初始化 poll 时间。
	sched.lastpoll = uint64(nanotime())
    
    // GOMAXPROC 设置。
	procs := ncpu
	if n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 {
		procs = n
	}
	if procresize(procs) != nil {
		throw("unknown runnable goroutine during bootstrap")
	}

	// World is effectively started now, as P's can run.
	worldStarted()
}

运行时

运行时入口 runtime.main 函数。由此,程序算是正式 “执行”。

// proc.go

// The main goroutine.
func main() {

	// 栈(G.stack)最大值 1 GB。
	if goarch.PtrSize == 8 {
		maxstacksize = 1000000000
	} else {
		maxstacksize = 250000000
	}

	// G.main 启动标志。
	mainStarted = true

    // 启动系统监控。
	newm(sysmon, nil, -1)
    
	runtimeInitTime = nanotime()

    // 执行 runtime.init 函数。
	doInit(&runtime_inittask)

	// 启动垃圾回收器。
	gcenable()

    // 执行标准库、第三方库和用户代码 init 函数。
	main_init_done = make(chan bool)
	doInit(&main_inittask)
	close(main_init_done)

    
    // 链接库模式。
	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
	fn()

    // 退出。
	exit(0)
}
//go:linkname main_main main.main
func main_main()

初始化函数

初始化函数分成两部分:

  • 运行时。
  • 用户代码(含标准库、第三方库)。
// proc.go

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

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

所有初始化函数被收集存储到 initTask 结构内。

使用弹性结构字段,依次保存依赖项的 initTask 和当前包初始化函数指针列表。

其具体数量由 ndeps、nfns 存储,以便读取。

// An initTask represents the set of initializations that need to be done 
// for a package.

type initTask struct {
	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
}

用一个简单示例探索该结构内存储的数据。

// 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!")
}

编译,输出汇编结果到文本文件,以便于阅读。

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

# test
"".init.0 STEXT size=66 args=0x0 locals=0x10 funcid=0x0 align=0x0
"".init.1 STEXT size=66 args=0x0 locals=0x10 funcid=0x0 align=0x0

""..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

基于这个结果,我们可以绘制一个示意图。

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

这些数据在编译期就整理存储,然后在初始化阶段由 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,递归执行。
		for i := uintptr(0); i < t.ndeps; i++ {
			p := add(unsafe.Pointer(t), (3+i)*goarch.PtrSize)  // 3 是头部固定字段数量。
			t2 := *(**initTask)(p)
			doInit(t2)
		}

		if t.nfns == 0 {
			t.state = 2 // initialization done
			return
		}


		// 提取当前包初始化函数并执行。
		firstFunc := add(unsafe.Pointer(t), (3+t.ndeps)*goarch.PtrSize)
		for i := uintptr(0); i < t.nfns; i++ {
			p := add(firstFunc, i*goarch.PtrSize)
			f := *(*func())(unsafe.Pointer(&p))
			f()
		}

		t.state = 2 // initialization done
	}
}

退出进程

当用户入口函数(main.main)执行结束后,调用 exit 结束进程,返回状态码。

// sys_linux_amd64.s

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

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);

不会等待其他 goroutine 结束。

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

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

发布评论

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