diff --git "a/\346\272\220\347\240\201\345\211\226\346\236\220/1.18/1. \345\210\235\345\247\213\345\214\226/1. \345\274\225\345\257\274.md" "b/\346\272\220\347\240\201\345\211\226\346\236\220/1.18/1. \345\210\235\345\247\213\345\214\226/1. \345\274\225\345\257\274.md" new file mode 100644 index 0000000..785af9b --- /dev/null +++ "b/\346\272\220\347\240\201\345\211\226\346\236\220/1.18/1. \345\210\235\345\247\213\345\214\226/1. \345\274\225\345\257\274.md" @@ -0,0 +1,94 @@ +# 引导 + +随意编译一个可执行程序。 + +```go +$ gdb test + +GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2 +Copyright (C) 2020 Free Software Foundation, Inc. + +Reading symbols from test... +Loading Go Runtime support. + +(gdb) starti +Starting program: test + +Program stopped. +_rt0_amd64_linux () at /usr/local/go/src/runtime/rt0_linux_amd64.s:8 +8 JMP _rt0_amd64(SB) +``` + +  + +也可用 `readelf` 获取起始地址。 + +```go +$ readelf -h ./test + +ELF Header: + Entry point address: 0x453860 + + +$ addr2line -e ./test -a 0x453860 + +/usr/local/go/src/runtime/rt0_linux_amd64.s:8 +``` + +  + +查看 `go/src/runtime` 目录下以汇编实现的引导过程源码。 + +```go +// rt0_linux_amd64.s + +TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8 + JMP _rt0_amd64(SB) +``` + +```go +// asm_amd64.s + +// _rt0_amd64 is common startup code for most amd64 systems when using +// internal linking. This is the entry point for the program from the +// kernel for an ordinary -buildmode=exe program. The stack holds the +// number of arguments and the C-style argv. + +TEXT _rt0_amd64(SB),NOSPLIT,$-8 + MOVQ 0(SP), DI // argc + LEAQ 8(SP), SI // argv + JMP runtime·rt0_go(SB) +``` + +  + +核心流程是创建 `main goroutine(runtime.main)`,并执行 `mstart` 进入调度循环。 + +```go +TEXT runtime·rt0_go(SB),NOSPLIT|TOPFRAME,$0 + + CALL runtime·check(SB) + + CALL runtime·args(SB) + CALL runtime·osinit(SB) + CALL runtime·schedinit(SB) + + // create a new goroutine to start program + MOVQ $runtime·mainPC(SB), AX // entry + PUSHQ AX + CALL runtime·newproc(SB) + POPQ AX + + // start this M + CALL runtime·mstart(SB) + + CALL runtime·abort(SB) // mstart should never return + RET +``` + +```go +// mainPC is a function value for runtime.main, to be passed to newproc. + +DATA runtime·mainPC+0(SB)/8,$runtime·main(SB) +GLOBL runtime·mainPC(SB),RODATA,$8 +``` diff --git "a/\346\272\220\347\240\201\345\211\226\346\236\220/1.18/1. \345\210\235\345\247\213\345\214\226/2. \345\210\235\345\247\213\345\214\226.md" "b/\346\272\220\347\240\201\345\211\226\346\236\220/1.18/1. \345\210\235\345\247\213\345\214\226/2. \345\210\235\345\247\213\345\214\226.md" new file mode 100644 index 0000000..54fb29d --- /dev/null +++ "b/\346\272\220\347\240\201\345\211\226\346\236\220/1.18/1. \345\210\235\345\247\213\345\214\226/2. \345\210\235\345\247\213\345\214\226.md" @@ -0,0 +1,374 @@ +# 初始化 +## 退出进程 + +当用户 +在 `r0_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. + +  + +```go +// os_linux.go + +func osinit() { + ncpu = getproccount() + physHugePageSize = getHugePageSize() + + osArchInit() +} +``` + +```go +// runtime2.go + +var ncpu int32 +``` + +  + +标准库 `debug` 返回的核数量就是该变量。 + +```go +// debug.go + +// NumCPU returns the number of logical CPUs usable by the current process. + +func NumCPU() int { + return int(ncpu) +} +``` + +  + +## 调度器 + +调度器(scheduler)执行前,需要对内存分配器、垃圾回收器等一系列部件进行初始化。 + +```go +// 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` 函数。由此,程序算是正式“执行”。 + +```go +// 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 +//go:linkname main_main main.main +func main_main() +``` + +  + +## 初始化函数 + +初始化函数分成两部分: + +* 运行时。 +* 用户代码(含标准库、第三方库)。 + +  + +```go +// 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` 存储,以便读取。 + +```go +// 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 +} +``` + +  + +用一个简单示例探索该结构内存储的数据。 + +```go +// test/main.go + +package main + +import ( + _ "net/http" + "test/mylib" +) + +func init() { + println(1) +} + +func init() { + println(2) +} + +func main() { + mylib.Hello() +} +``` + +```go +// test/mylib/lib.go + +package mylib + +func init() { + println("mylib.init") +} + +func Hello() { + println("hello, world!") +} +``` + +  + +编译,输出汇编结果到文本文件,以便阅读。 + +```go +$> go build -gcflags "-N -l -S" 2>a.txt +``` + +```go +// 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 +``` + +  + +基于这个结果,我们可以绘制一个示意图。 + +```go + +------------------+ + | 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` 执行。 + +```go +// 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` 结束进程,返回状态码。 + +```go +// 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` 结束。