bootstrap顺序
// The bootstrap sequence is:
//
// call osinit
// call schedinit
// make & queue new G
// call runtime·mstart
//
// The new G calls runtime·main.
src\runtime\asm_amd64.s
go进程的启动过程在文件src\runtime\asm_amd64.s
中可以看到。
1. main入口
汇编main入口比较简单,直接跳转到runtime·rt0_go。
对于amd64系统,不同的链接方式入口不同。有两种方式。
// _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 is common startup code for most amd64 systems when using
// external linking. The C startup code will call the symbol "main"
// passing argc and argv in the usual C ABI registers DI and SI.
TEXT main(SB),NOSPLIT,$-8
JMP runtime·rt0_go(SB)
2. runtime·rt0_go
rt0_go 调用一系列runtime函数,进行初始化。包括
- runtime·check
- runtime·args
- runtime·osinit
- runtime·schedinit
- runtime·newproc
创建一个goroutine,执行对象函数为runtime.main
。 - runtime·mstart
TEXT runtime·rt0_go(SB),NOSPLIT,$0
// 1. 将参数入栈。
// 2. 查找cpu信息。
// 1.如果没有cpu信息,如果有_cgo_init就调用
// 3. 设置m->g0 = g0
// 4. 设置m0 to g0->m
// 5. CALL runtime·check(SB)
// 6. CALL runtime·args(SB)
// 7. CALL runtime·osinit(SB)
// 8. CALL runtime·schedinit(SB)
// 9. CALL runtime·newproc创建一个goroutine启动进程。
// create a new goroutine to start program
// 0.MOVQ $runtime·mainPC(SB), AX // entry == runtime·main
// 将函数入口runtime·main放入AX,作为newproc执行函数
// DATA runtime·mainPC+0(SB)/8,$runtime·main(SB)
// 1.通过调用runtime·newproc启动一个goroutine
// 2.CALL runtime·newproc(SB)
// 10.CALL runtime·mstart 启动一个M,执行 runtime·main
// 1.CALL runtime·mstart(SB)
//
//
3. osinit()
获取CPU个数。
func osinit() {
ncpu = getproccount() //通过封装系统调用,获取CPU个数
}
4. schedinit()
进行模块初始化,符号初始化,内存初始化,gc初始化,p初始化等。
func schedinit() {
//1. 获取当前的goroutine
_g_ := getg()
//2. tracebackinit()
tracebackinit()
//3.模块数据校验
moduledataverify()
//type moduledata struct { 定义了二进制映像文件信息,由链接器填写。包括:代码段、只读数据,数据段等信息
//4. 栈初始化
stackinit()
//5. 内存分配器初始化
mallocinit()
//6.
mcommoninit(_g_.m)
//7. cpu初始化,
// 1.cpu个数;2.从参数获取cpu特性
cpuinit()
//8. 初始化AES hash算法
alginit()
//9. 模块初始化
// src\runtime\symtab.go
// 当一个module被动态链接器首次加载时,.init_array被调用。
// 在一个Go进程声明周期内,有两种场景会发生module的加载。
// 1.以-buildmode=shared 模式编译,进程初始化会加载。
// 2.-buildmode=plugin模式编译,进程运行时加载。
// 约束:1.modulesinit一次只能有一个goroutine调用。
modulesinit()
//10. 建立符号表
typelinksinit()
//11. itab初始化
itabsinit()
//12. 空函数
msigsave(_g_.m)
//13. 将参数都放入切片argslice
// windows下什么也不做
goargs()
//14. 将所有参数放入切边envs
goenvs()
//15. 获取所有GODEBUG参数。
// 1.更新memprofilerate参数
// 2.设置GOTRACEBACK级别
// 3.默认进行cgocheck=1,invalidptr=1
// 4.根据参数设置debug参数dbgvars
parsedebugvars()
//16. gc初始化
// 1.memstats.triggerRatio = 7 / 8.0
// 2.默认触发GC的堆大小为4M。可以调整。
/// 3.根据GOGC参数设置GC比例,
// 比如100->4M*1
// 比如200->4M*2
// 4.以通过设置 GOGC=off 来彻底关闭 GC
gcinit()
//17. 设置proc个数
默认为cpu个数(procs)。
如果设置了环境变量,"GOMAXPROCS",则设置为环境变量设定值。
//18. 改变p个数
procresize(procs)
//整个过程所有处理都是暂停了stop the world,并且sched被加锁
//allp是一个slice保存所有的p。根据需要增长allp大小
//初始化新创建的p
//将所有可以运行的p放入,sched.runq队列头部
5. runtime.newproc
实际调用newproc1(具体见[13-goroutine创建过程](https://blog.csdn.net/svasticah/article/details/94345696)
)。
- 优先从本地(当前线程p对象),或者全局(sched)的gfree队列获取一个空闲g对象。
- 没有空闲g对象,则新创建一个g对象。 设置状态为
_Grunnable
- 以50%概率作为下个执行对象,否则放到待执行队列p.runq。如果队列已满,从本地队列抽取一半g对象放入全局队列。
- 唤醒一个闲置线程,如果没有闲置的,则新建一个线程。
6. runtime·mstart
通过schedule()
实际执行之前准备好的goroutine,即runtime.main
。
func mstart1() {
// 1.asminit()空函数
// 2.minit() m初始化,主要是信号初始化
// 3. mstartm0()//对于windows单独启动一个额外的M用于callback
// 4. fn() //如果有mstartfn,则执行。
// 5. schedule() //找到一个可以运行的的goroutine并执行。
1.如果处于gc等待状态sched.gcwaiting,则stop the world.
1.gcstopm()
1.releasep() 将p与m解除关联
2.stopm() 停止执行当前的m,直到新的work可用
2.goto top //循环等待gcwaiting是否结束
2.trace
3.获取后台一个goroutine
findRunnableGCWorker()
4.如果获取失败,以一定几率从全局队列中获取一个goroutine
1.几率schedtick%61 == 0
2.从全局队列获取,globrunqget()
3.获取过程会加锁, lock(&sched.lock)
5.如果仍然没有可以运行goroutine,则阻塞等待。
findrunnable()
1. 处于sched.gcwaiting状态,循环等待goto top
2. 如果处于唤醒等待状态,则准备一个goroutine
3. 如果有cgo处于yeild,则调用cgo
4. 再次尝试获取一个本地goroutine,成功则返回。
5. 再次尝试从全局队列中获取goroutine,成功则返回。
6. Poll network.
通过poll等待网络。
6. 找到一个goroutine,则执行runtime·gogo
}
7.执行一个goroutine
// func gogo(buf *gobuf)
// restore state from Gobuf; longjmp
TEXT runtime·gogo(SB), NOSPLIT, $16-8
MOVQ buf+0(FP), BX // gobuf
MOVQ gobuf_g(BX), DX
MOVQ 0(DX), CX // make sure g != nil
get_tls(CX)
MOVQ DX, g(CX)
MOVQ gobuf_sp(BX), SP // restore SP
MOVQ gobuf_ret(BX), AX
MOVQ gobuf_ctxt(BX), DX
MOVQ gobuf_bp(BX), BP
MOVQ $0, gobuf_sp(BX) // clear to help garbage collector
MOVQ $0, gobuf_ret(BX)
MOVQ $0, gobuf_ctxt(BX)
MOVQ $0, gobuf_bp(BX)
MOVQ gobuf_pc(BX), BX
JMP BX
8. runtime.main
src\runtime\proc.go
// The main goroutine.
- 创建一个线程,执行sysmon服务。
- 执行main.init
- 执行main.main
func main() {
1. newm(sysmon, nil)
对于非wasm(WebAssembly )环境,创建一个m,执行sysmon。
1. allocm() 创建一个m对象
2. 对于cgo,通过asmcgocall(_cgo_thread_start, unsafe.Pointer(&ts))系统调用创建一个线程
3. 非cgo,通过pthread_create方式创建线程。其他通过clone方式创建(linux)
4. sysmon
1. 常驻后台
2. trigger libc interceptors if needed
3. poll network if not polled for more than 10ms
4. 夺回被系统调用阻塞的p,并且抢占长期运行的g
5. 判断是否需要做gc
6. 偶尔清理一下heap,
1.在gc后5分钟没有使用,就归还给操作系统,scavengelimit := int64(5 * 60 * 1e9)
2. runtime_init()
执行默认.init函数
3. gcenable()
启动gc goroutine
4. main_init
执行main.init
5. main_main
执行main.main
}
9. 总结
- 创建
runtime.main()
goroutine
通过runtime.newproc
创建一个goroutine
,执行对象为runtime.main()
- 执行
runtime.main()
通过runtime.mstart()
调用m.mstartfn
即runtime.main()
。 - 启动sysmon线程
runtime.main()
中调用newm()
创建一个线程,执行后台服务sysmon
- runtime.main() goroutine中执行.init()。
- runtime.main() goroutine中创建一个gc goroutine。
- runtime.main() goroutine中执行main.init()。
- runtime.main() goroutine中执行main.main(),真正进入业务进程。
10. 主要过程
- newm()
创建一个系统线程sysmon。 - newproc()
创建一个g对象,用来执行runtime.main
。 - mstart()
用来通过runtime.main
来进入main
。