上卷 程序设计
中卷 标准库
- 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 初始化
在 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论