上卷 程序设计
中卷 标准库
- bufio 1.18
- bytes 1.18
- io 1.18
- container 1.18
- encoding 1.18
- crypto 1.18
- hash 1.18
- index 1.18
- sort 1.18
- context 1.18
- database 1.18
- connection
- query
- queryrow
- exec
- prepare
- transaction
- scan & null
- context
- tcp
- udp
- http
- server
- handler
- client
- h2、tls
- url
- rpc
- exec
- signal
- embed 1.18
- plugin 1.18
- reflect 1.18
- runtime 1.18
- KeepAlived
- ReadMemStats
- SetFinalizer
- Stack
- sync 1.18
- atomic
- mutex
- rwmutex
- waitgroup
- cond
- once
- map
- pool
- copycheck
- nocopy
- unsafe 1.18
- fmt 1.18
- log 1.18
- math 1.18
- time 1.18
- timer
下卷 运行时
源码剖析
附录
1.2 初始化
在引导过程中,先调用了:
参考上一节 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论