前言
go相对其他语言,对并发的支持更友好。这使得他的设计和其他程序迥然不同。让我们来看看它都是如何初始化程序的,从程序加载到运行到底经历的什么。
我们继续之前的版本1.18.4
汇编入口
首先我们编译一个hello world 程序。
package mainimport "fmt"func main(){fmt.Println("hello world")}package main import "fmt" func main(){ fmt.Println("hello world") }package main import "fmt" func main(){ fmt.Println("hello world") }
编译时带上调试信息
go build -gcflags=all="-N -l" -ldflags=-compressdwarf=falsego build -gcflags=all="-N -l" -ldflags=-compressdwarf=falsego build -gcflags=all="-N -l" -ldflags=-compressdwarf=false
用gdb启动,找到入口
可以看到入口点在rt0_darwin_amd64.s
,这里解释一下文件名:
- rt0 : runtime0 表示起始运行时
- darwin : 操作系统 我这里是mac系统 对应(GOOS)
- amd64 : 操作系统架构 对应(GOHOSTARCH)
启动文件位于GOROOT/src/runtime
目录下,那同理可以看到其他系统的启动文件
看一下这个启动文件干了嘛
#include "textflag.h" //定义了一些特殊的宏,用于标记全局符号(函数和全局变量)//在Go汇编语言中,内存是通过SB伪寄存器定位。SB是Static base pointer 的缩写,意为静态内存的开始地址。//所有的静态全局符号可以通过SB加一个偏移量定位,而我们定义的符号其实就是相对于SB内存开始地址偏移量。//对于SB伪寄存器,全局变量和全局函数的符号并没有任何区别。TEXT _rt0_amd64_darwin(SB),NOSPLIT,$-8JMP _rt0_amd64(SB) //跳转执行程序的入口函数TEXT _rt0_amd64_darwin_lib(SB),NOSPLIT,$0JMP _rt0_amd64_lib(SB) //跳转lib库的入口函数#include "textflag.h" //定义了一些特殊的宏,用于标记全局符号(函数和全局变量) //在Go汇编语言中,内存是通过SB伪寄存器定位。SB是Static base pointer 的缩写,意为静态内存的开始地址。 //所有的静态全局符号可以通过SB加一个偏移量定位,而我们定义的符号其实就是相对于SB内存开始地址偏移量。 //对于SB伪寄存器,全局变量和全局函数的符号并没有任何区别。 TEXT _rt0_amd64_darwin(SB),NOSPLIT,$-8 JMP _rt0_amd64(SB) //跳转执行程序的入口函数 TEXT _rt0_amd64_darwin_lib(SB),NOSPLIT,$0 JMP _rt0_amd64_lib(SB) //跳转lib库的入口函数#include "textflag.h" //定义了一些特殊的宏,用于标记全局符号(函数和全局变量) //在Go汇编语言中,内存是通过SB伪寄存器定位。SB是Static base pointer 的缩写,意为静态内存的开始地址。 //所有的静态全局符号可以通过SB加一个偏移量定位,而我们定义的符号其实就是相对于SB内存开始地址偏移量。 //对于SB伪寄存器,全局变量和全局函数的符号并没有任何区别。 TEXT _rt0_amd64_darwin(SB),NOSPLIT,$-8 JMP _rt0_amd64(SB) //跳转执行程序的入口函数 TEXT _rt0_amd64_darwin_lib(SB),NOSPLIT,$0 JMP _rt0_amd64_lib(SB) //跳转lib库的入口函数
再看下_rt0_amd64
函数,位于runtime目录下的asm_amd64.s
文件中
TEXT _rt0_amd64(SB),NOSPLIT,$-8MOVQ 0(SP), DI // argcLEAQ 8(SP), SI // argvJMP runtime·rt0_go(SB)TEXT _rt0_amd64(SB),NOSPLIT,$-8 MOVQ 0(SP), DI // argc LEAQ 8(SP), SI // argv JMP runtime·rt0_go(SB)TEXT _rt0_amd64(SB),NOSPLIT,$-8 MOVQ 0(SP), DI // argc LEAQ 8(SP), SI // argv JMP runtime·rt0_go(SB)
最终跳转到runtime·rt0_go(SB)
这里是整个go代码的起点,看一下这个函数的重点部分
rt0_go函数
TEXT runtime·rt0_go(SB),NOSPLIT|TOPFRAME,$0...// 略过前置设置和检查// 这段有大量的 m0,g0 tls的分配,设置内容,懒得写略过了get_tls(BX)// 将g0保存到tls中LEAQ runtime·g0(SB), CX...//运行时类型检查,主要是校验编译器的翻译工作是否正确。//基本代码均为检查 int8 在 unsafe.Sizeof 方法下是否等于 1 这类动作。CALL runtime·check(SB)...//系统参数传递,主要是将系统参数转换传递给程序使用。CALL runtime·args(SB)//系统基本参数设置,主要是获取 CPU 核心数和内存物理页大小CALL runtime·osinit(SB)//进行各种运行时组件的初始化,包含调度器、内存分配器、堆、栈、GC 等一大堆初始化工作。//会进行 p 的初始化,并将 m0 和某一个 p 进行绑定。CALL runtime·schedinit(SB)//创建一个新的 goroutine 来启动程序//虽然在runtime·rt0_go 中指向的是$runtime·mainPC,但实质指向的是 runtime.mainMOVQ $runtime·mainPC(SB), AX // entry//创建一个新的 goroutine,且绑定 runtime.main 方法(也就是应用程序中的入口 main 方法)。//并将其放入 m0 绑定的p的本地队列中去,以便后续调度。CALL runtime·newproc(SB)//启动 M.mstart,调度器开始进行循环调度。CALL runtime·mstart(SB)CALL runtime·abort(SB) // M.mstart 应该永不返回RET...TEXT runtime·rt0_go(SB),NOSPLIT|TOPFRAME,$0 ... // 略过前置设置和检查 // 这段有大量的 m0,g0 tls的分配,设置内容,懒得写略过了 get_tls(BX) // 将g0保存到tls中 LEAQ runtime·g0(SB), CX ... //运行时类型检查,主要是校验编译器的翻译工作是否正确。 //基本代码均为检查 int8 在 unsafe.Sizeof 方法下是否等于 1 这类动作。 CALL runtime·check(SB) ... //系统参数传递,主要是将系统参数转换传递给程序使用。 CALL runtime·args(SB) //系统基本参数设置,主要是获取 CPU 核心数和内存物理页大小 CALL runtime·osinit(SB) //进行各种运行时组件的初始化,包含调度器、内存分配器、堆、栈、GC 等一大堆初始化工作。 //会进行 p 的初始化,并将 m0 和某一个 p 进行绑定。 CALL runtime·schedinit(SB) //创建一个新的 goroutine 来启动程序 //虽然在runtime·rt0_go 中指向的是$runtime·mainPC,但实质指向的是 runtime.main MOVQ $runtime·mainPC(SB), AX // entry //创建一个新的 goroutine,且绑定 runtime.main 方法(也就是应用程序中的入口 main 方法)。 //并将其放入 m0 绑定的p的本地队列中去,以便后续调度。 CALL runtime·newproc(SB) //启动 M.mstart,调度器开始进行循环调度。 CALL runtime·mstart(SB) CALL runtime·abort(SB) // M.mstart 应该永不返回 RET ...TEXT runtime·rt0_go(SB),NOSPLIT|TOPFRAME,$0 ... // 略过前置设置和检查 // 这段有大量的 m0,g0 tls的分配,设置内容,懒得写略过了 get_tls(BX) // 将g0保存到tls中 LEAQ runtime·g0(SB), CX ... //运行时类型检查,主要是校验编译器的翻译工作是否正确。 //基本代码均为检查 int8 在 unsafe.Sizeof 方法下是否等于 1 这类动作。 CALL runtime·check(SB) ... //系统参数传递,主要是将系统参数转换传递给程序使用。 CALL runtime·args(SB) //系统基本参数设置,主要是获取 CPU 核心数和内存物理页大小 CALL runtime·osinit(SB) //进行各种运行时组件的初始化,包含调度器、内存分配器、堆、栈、GC 等一大堆初始化工作。 //会进行 p 的初始化,并将 m0 和某一个 p 进行绑定。 CALL runtime·schedinit(SB) //创建一个新的 goroutine 来启动程序 //虽然在runtime·rt0_go 中指向的是$runtime·mainPC,但实质指向的是 runtime.main MOVQ $runtime·mainPC(SB), AX // entry //创建一个新的 goroutine,且绑定 runtime.main 方法(也就是应用程序中的入口 main 方法)。 //并将其放入 m0 绑定的p的本地队列中去,以便后续调度。 CALL runtime·newproc(SB) //启动 M.mstart,调度器开始进行循环调度。 CALL runtime·mstart(SB) CALL runtime·abort(SB) // M.mstart 应该永不返回 RET ...
从这个汇编文件中,开始调用runtime方法。
go在1.5的版本里就实现了自举,所有大部分的功能都是用go实现的,并多处于runtime文件夹中。
runtime
check
check
函数位于runtime的runtime1.go
中,主要是检查一些标识,有兴趣的可以自己去看一下。
args
args
函数同样runtime的runtime1.go
中。
var (argc int32 //参数个数argv **byte //入参)func args(c int32, v **byte) { //初始全局变量 argc,argv 并调用sysargsargc = cargv = vsysargs(c, v)}var executablePath string//获取执行程序路径 复制到全局变量executablePathfunc sysargs(argc int32, argv **byte) {。。。}var ( argc int32 //参数个数 argv **byte //入参 ) func args(c int32, v **byte) { //初始全局变量 argc,argv 并调用sysargs argc = c argv = v sysargs(c, v) } var executablePath string //获取执行程序路径 复制到全局变量executablePath func sysargs(argc int32, argv **byte) { 。。。 }var ( argc int32 //参数个数 argv **byte //入参 ) func args(c int32, v **byte) { //初始全局变量 argc,argv 并调用sysargs argc = c argv = v sysargs(c, v) } var executablePath string //获取执行程序路径 复制到全局变量executablePath func sysargs(argc int32, argv **byte) { 。。。 }
osinit
osinit
位于runtime的os_darwin.go
中,我这里是mac系统所以是darwin
func osinit() {ncpu = getncpu() //获取cpu核数physPageSize = getPageSize() //获取页大小}func osinit() { ncpu = getncpu() //获取cpu核数 physPageSize = getPageSize() //获取页大小 }func osinit() { ncpu = getncpu() //获取cpu核数 physPageSize = getPageSize() //获取页大小 }
在mac下,这里实际就是调用sysctl
获取系统信息。你也可以理解函数对应命令行是这样的:
- getncpu() ->
sysctl -h -a | grep hw.ncpu
- getPageSize() ->
sysctl -h -a | grep hw.pagesize
schedinit
schedinit
位于runtime的proc.go
文件中。
func schedinit() {//lockInit 锁相关的初始化 暂时忽略//获取当前的g 之前已经保存在tls中了,getg就是从tls中获取//大致的关系是fs -> tls[1] -> g() -> tls[0] -> g0 -> g0.m0 = &m0 -> m0.g0 = &g0//从fs段寄存器出发 找到 m0.tls[1] ,地址-8后得到 tls[0] 而 tls[0]正好指向g0获取到_g_ := getg()if raceenabled { //如果启用了race 则进行raceinit的初始化,默认false_g_.racectx, raceprocctx0 = raceinit()}//默认m(线程)的最大值是10000个,面试经常问sched.maxmcount = 10000// The world starts stopped.worldStopped() //用于lock rank,可忽略moduledataverify() //验证链接器符号,可忽略//初始栈,就是初始 stackLarge,stackpool 两个全局变量。对这哥俩感兴趣的可以看上篇博文 内存管理//注意这里还没有给栈分配内存stackinit()//内存分配初始化。就是计算内存大小,初始化mheap,mcache0 等操作mallocinit()//初始化CPU相关的参数//读取环境变量GODEBUG,并调用 internal/cpu.Initializecpuinit() // must run before alginit//map使用必须调用,算法相关alginit() // maps, hash, fastrand must not be used before this call//随机数相关fastrandinit() // must run before mcommoninit//初始化m,调用atomicstorep将m0放入全局变量allm//并且将allm挂到m的alllink上mcommoninit(_g_.m, -1)//模块初始化,将所有模块的moduledata的gc标志初始化,并将moduledata放入全局变量modulesSlice中modulesinit() // provides activeModules//type这种别名相关的,消除重复映射typelinksinit() // uses maps, activeModules//接口相关,将每个模块的itab 放入全局变量itabTable.entries中,方便动态派发//itab粗糙的理解 = 接口类型+具体实现类型,方便动态类型的查找。itabsinit() // uses activeModules//初始化methodValueCallFrameObjs栈对象stkobjinit() // must run before GC starts//将当前线程信号保存到m.sigmask中,一并设置到全局变量initSigmasksigsave(&_g_.m.sigmask)initSigmask = _g_.m.sigmask...goargs() //入参全局变量argslice初始化goenvs() //环境全局变量envs初始化parsedebugvars() //初始化debug包变量,并根据环境变量GODEBUG解析dbgvars的一系列配置gcinit() //gc相关lock(&sched.lock)//sched.lastpoll 设置调度器初始化轮训时间sched.lastpoll = uint64(nanotime())//设置当前cpu个数,在 osinit() 函数里已经获取到。如果环境变量GOMAXPROCS设置了CPU个数,直使用设置个数。procs := ncpuif n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 {procs = n}//调整cpu 数量if procresize(procs) != nil {throw("unknown runnable goroutine during bootstrap")}unlock(&sched.lock)...// World is effectively started now, as P's can run.worldStarted()}func schedinit() { //lockInit 锁相关的初始化 暂时忽略 //获取当前的g 之前已经保存在tls中了,getg就是从tls中获取 //大致的关系是fs -> tls[1] -> g() -> tls[0] -> g0 -> g0.m0 = &m0 -> m0.g0 = &g0 //从fs段寄存器出发 找到 m0.tls[1] ,地址-8后得到 tls[0] 而 tls[0]正好指向g0获取到 _g_ := getg() if raceenabled { //如果启用了race 则进行raceinit的初始化,默认false _g_.racectx, raceprocctx0 = raceinit() } //默认m(线程)的最大值是10000个,面试经常问 sched.maxmcount = 10000 // The world starts stopped. worldStopped() //用于lock rank,可忽略 moduledataverify() //验证链接器符号,可忽略 //初始栈,就是初始 stackLarge,stackpool 两个全局变量。对这哥俩感兴趣的可以看上篇博文 内存管理 //注意这里还没有给栈分配内存 stackinit() //内存分配初始化。就是计算内存大小,初始化mheap,mcache0 等操作 mallocinit() //初始化CPU相关的参数 //读取环境变量GODEBUG,并调用 internal/cpu.Initialize cpuinit() // must run before alginit //map使用必须调用,算法相关 alginit() // maps, hash, fastrand must not be used before this call //随机数相关 fastrandinit() // must run before mcommoninit //初始化m,调用atomicstorep将m0放入全局变量allm //并且将allm挂到m的alllink上 mcommoninit(_g_.m, -1) //模块初始化,将所有模块的moduledata的gc标志初始化,并将moduledata放入全局变量modulesSlice中 modulesinit() // provides activeModules //type这种别名相关的,消除重复映射 typelinksinit() // uses maps, activeModules //接口相关,将每个模块的itab 放入全局变量itabTable.entries中,方便动态派发 //itab粗糙的理解 = 接口类型+具体实现类型,方便动态类型的查找。 itabsinit() // uses activeModules //初始化methodValueCallFrameObjs栈对象 stkobjinit() // must run before GC starts //将当前线程信号保存到m.sigmask中,一并设置到全局变量initSigmask sigsave(&_g_.m.sigmask) initSigmask = _g_.m.sigmask ... goargs() //入参全局变量argslice初始化 goenvs() //环境全局变量envs初始化 parsedebugvars() //初始化debug包变量,并根据环境变量GODEBUG解析dbgvars的一系列配置 gcinit() //gc相关 lock(&sched.lock) //sched.lastpoll 设置调度器初始化轮训时间 sched.lastpoll = uint64(nanotime()) //设置当前cpu个数,在 osinit() 函数里已经获取到。如果环境变量GOMAXPROCS设置了CPU个数,直使用设置个数。 procs := ncpu if n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 { procs = n } //调整cpu 数量 if procresize(procs) != nil { throw("unknown runnable goroutine during bootstrap") } unlock(&sched.lock) ... // World is effectively started now, as P's can run. worldStarted() }func schedinit() { //lockInit 锁相关的初始化 暂时忽略 //获取当前的g 之前已经保存在tls中了,getg就是从tls中获取 //大致的关系是fs -> tls[1] -> g() -> tls[0] -> g0 -> g0.m0 = &m0 -> m0.g0 = &g0 //从fs段寄存器出发 找到 m0.tls[1] ,地址-8后得到 tls[0] 而 tls[0]正好指向g0获取到 _g_ := getg() if raceenabled { //如果启用了race 则进行raceinit的初始化,默认false _g_.racectx, raceprocctx0 = raceinit() } //默认m(线程)的最大值是10000个,面试经常问 sched.maxmcount = 10000 // The world starts stopped. worldStopped() //用于lock rank,可忽略 moduledataverify() //验证链接器符号,可忽略 //初始栈,就是初始 stackLarge,stackpool 两个全局变量。对这哥俩感兴趣的可以看上篇博文 内存管理 //注意这里还没有给栈分配内存 stackinit() //内存分配初始化。就是计算内存大小,初始化mheap,mcache0 等操作 mallocinit() //初始化CPU相关的参数 //读取环境变量GODEBUG,并调用 internal/cpu.Initialize cpuinit() // must run before alginit //map使用必须调用,算法相关 alginit() // maps, hash, fastrand must not be used before this call //随机数相关 fastrandinit() // must run before mcommoninit //初始化m,调用atomicstorep将m0放入全局变量allm //并且将allm挂到m的alllink上 mcommoninit(_g_.m, -1) //模块初始化,将所有模块的moduledata的gc标志初始化,并将moduledata放入全局变量modulesSlice中 modulesinit() // provides activeModules //type这种别名相关的,消除重复映射 typelinksinit() // uses maps, activeModules //接口相关,将每个模块的itab 放入全局变量itabTable.entries中,方便动态派发 //itab粗糙的理解 = 接口类型+具体实现类型,方便动态类型的查找。 itabsinit() // uses activeModules //初始化methodValueCallFrameObjs栈对象 stkobjinit() // must run before GC starts //将当前线程信号保存到m.sigmask中,一并设置到全局变量initSigmask sigsave(&_g_.m.sigmask) initSigmask = _g_.m.sigmask ... goargs() //入参全局变量argslice初始化 goenvs() //环境全局变量envs初始化 parsedebugvars() //初始化debug包变量,并根据环境变量GODEBUG解析dbgvars的一系列配置 gcinit() //gc相关 lock(&sched.lock) //sched.lastpoll 设置调度器初始化轮训时间 sched.lastpoll = uint64(nanotime()) //设置当前cpu个数,在 osinit() 函数里已经获取到。如果环境变量GOMAXPROCS设置了CPU个数,直使用设置个数。 procs := ncpu if n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 { procs = n } //调整cpu 数量 if procresize(procs) != nil { throw("unknown runnable goroutine during bootstrap") } unlock(&sched.lock) ... // World is effectively started now, as P's can run. worldStarted() }
mainPC main newproc
上面汇编中将runtime·mainPC作为runtime.newproc的参数,即回调用函数参入。
而上面说的rt0_go
函数中还有一段,这里将mainPC指向了main
DATA runtime·mainPC+0(SB)/8,$runtime·main<ABIInternal>(SB)GLOBL runtime·mainPC(SB),RODATA,$8DATA runtime·mainPC+0(SB)/8,$runtime·main<ABIInternal>(SB) GLOBL runtime·mainPC(SB),RODATA,$8DATA runtime·mainPC+0(SB)/8,$runtime·main<ABIInternal>(SB) GLOBL runtime·mainPC(SB),RODATA,$8
先看newproc
函数,在runtime的proc.go
文件中
//创建一个新的g,绑定main函数,并且加入到队列中等待执行func newproc(fn *funcval) {gp := getg() //获取当前gpc := getcallerpc() //获取程序计数器systemstack(func() {//创建新的g并绑定fn,也就是mainnewg := newproc1(fn, gp, pc)_p_ := getg().m.p.ptr()//推入p的队列中runqput(_p_, newg, true)//是否启动M开始执行//默认为false,在下面的main函数中设置mainStarted=true,所以第一次到这里是不会执行的。if mainStarted {wakep()}})}//创建一个新的g,绑定main函数,并且加入到队列中等待执行 func newproc(fn *funcval) { gp := getg() //获取当前g pc := getcallerpc() //获取程序计数器 systemstack(func() { //创建新的g并绑定fn,也就是main newg := newproc1(fn, gp, pc) _p_ := getg().m.p.ptr() //推入p的队列中 runqput(_p_, newg, true) //是否启动M开始执行 //默认为false,在下面的main函数中设置mainStarted=true,所以第一次到这里是不会执行的。 if mainStarted { wakep() } }) }//创建一个新的g,绑定main函数,并且加入到队列中等待执行 func newproc(fn *funcval) { gp := getg() //获取当前g pc := getcallerpc() //获取程序计数器 systemstack(func() { //创建新的g并绑定fn,也就是main newg := newproc1(fn, gp, pc) _p_ := getg().m.p.ptr() //推入p的队列中 runqput(_p_, newg, true) //是否启动M开始执行 //默认为false,在下面的main函数中设置mainStarted=true,所以第一次到这里是不会执行的。 if mainStarted { wakep() } }) }
- 这里相当于将runtime·main推到p的队列中
- golang中go statement入口就是newproc,即go func(){}实际上newproc(func(){})的调用
main
函数,同样在这个文件里
func main() {g := getg()g.m.g0.racectx = 0//设置栈的最大值,按处理器位数,64位对应1G,32位对应250MBif goarch.PtrSize == 8 {maxstacksize = 1000000000} else {maxstacksize = 250000000}maxstackceiling = 2 * maxstacksizemainStarted = true //允许上面的newproc函数创建Ms...//执行每runtime的initdoInit(&runtime_inittask) // Must be before defer....gcenable() //开启gc//下面一大坨都是cgo相关main_init_done = make(chan bool)if iscgo {...}doInit(&main_inittask) //执行package main的init...fn := main_main // 执行package main中主函数fn()...exit(0) //退出进程}func main() { g := getg() g.m.g0.racectx = 0 //设置栈的最大值,按处理器位数,64位对应1G,32位对应250MB if goarch.PtrSize == 8 { maxstacksize = 1000000000 } else { maxstacksize = 250000000 } maxstackceiling = 2 * maxstacksize mainStarted = true //允许上面的newproc函数创建Ms ... //执行每runtime的init doInit(&runtime_inittask) // Must be before defer. ... gcenable() //开启gc //下面一大坨都是cgo相关 main_init_done = make(chan bool) if iscgo { ... } doInit(&main_inittask) //执行package main的init ... fn := main_main // 执行package main中主函数 fn() ... exit(0) //退出进程 }func main() { g := getg() g.m.g0.racectx = 0 //设置栈的最大值,按处理器位数,64位对应1G,32位对应250MB if goarch.PtrSize == 8 { maxstacksize = 1000000000 } else { maxstacksize = 250000000 } maxstackceiling = 2 * maxstacksize mainStarted = true //允许上面的newproc函数创建Ms ... //执行每runtime的init doInit(&runtime_inittask) // Must be before defer. ... gcenable() //开启gc //下面一大坨都是cgo相关 main_init_done = make(chan bool) if iscgo { ... } doInit(&main_inittask) //执行package main的init ... fn := main_main // 执行package main中主函数 fn() ... exit(0) //退出进程 }
这里着重说一下doInit
函数,它会执行每个模块中的init函数,init函数对应结构体如下:
type initTask struct {state uintptr //状态标识 0:未执行, 1:执行中, 2:已完成ndeps uintptr //当前模块的其他依赖nfns uintptr //模块里面的几个init函数}type initTask struct { state uintptr //状态标识 0:未执行, 1:执行中, 2:已完成 ndeps uintptr //当前模块的其他依赖 nfns uintptr //模块里面的几个init函数 }type initTask struct { state uintptr //状态标识 0:未执行, 1:执行中, 2:已完成 ndeps uintptr //当前模块的其他依赖 nfns uintptr //模块里面的几个init函数 }
看这个结构就能猜到,所有的init函数会根据模块的依赖关系形成一个有向无环图,执行的过程就是对这个图进行深度优先遍历,遍历函数doInit
如下
func doInit(t *initTask) {switch t.state {case 2: // 完成退出returncase 1: // 异常panicthrow("recursive call during initialization - linker skew")default: // 遍历执行t.state = 1 // 先设置状态到执行中//向下递归for i := uintptr(0); i < t.ndeps; i++ {p := add(unsafe.Pointer(t), (3+i)*goarch.PtrSize)t2 := *(**initTask)(p)doInit(t2)}//当前模块没init则设置状态到完成,返回if t.nfns == 0 {t.state = 2 // initialization donereturn}... //执行当前模块的init,完成后设置状态2 返回t.state = 2}}func doInit(t *initTask) { switch t.state { case 2: // 完成退出 return case 1: // 异常panic throw("recursive call during initialization - linker skew") default: // 遍历执行 t.state = 1 // 先设置状态到执行中 //向下递归 for i := uintptr(0); i < t.ndeps; i++ { p := add(unsafe.Pointer(t), (3+i)*goarch.PtrSize) t2 := *(**initTask)(p) doInit(t2) } //当前模块没init则设置状态到完成,返回 if t.nfns == 0 { t.state = 2 // initialization done return } ... //执行当前模块的init,完成后设置状态2 返回 t.state = 2 } }func doInit(t *initTask) { switch t.state { case 2: // 完成退出 return case 1: // 异常panic throw("recursive call during initialization - linker skew") default: // 遍历执行 t.state = 1 // 先设置状态到执行中 //向下递归 for i := uintptr(0); i < t.ndeps; i++ { p := add(unsafe.Pointer(t), (3+i)*goarch.PtrSize) t2 := *(**initTask)(p) doInit(t2) } //当前模块没init则设置状态到完成,返回 if t.nfns == 0 { t.state = 2 // initialization done return } ... //执行当前模块的init,完成后设置状态2 返回 t.state = 2 } }
mstart
mstart
函数汇编中指向mstart0
TEXT runtime·mstart(SB),NOSPLIT|TOPFRAME,$0CALL runtime·mstart0(SB)RET // not reachedTEXT runtime·mstart(SB),NOSPLIT|TOPFRAME,$0 CALL runtime·mstart0(SB) RET // not reachedTEXT runtime·mstart(SB),NOSPLIT|TOPFRAME,$0 CALL runtime·mstart0(SB) RET // not reached
mstart0
这个函数同样在proc.go文件里
func mstart0() {...mstart1() // 启动m//退出当前线程if mStackIsSystemAllocated() {osStack = true}//执行完所有的 Goroutine 后,清理并退出m,不会执行到这里mexit(osStack)}func mstart1() {...asminit()minit() //初始化新的m,在新线程上调用...schedule() //开始调度,找到一个`runnable`状态的goroutine并执行}func mstart0() { ... mstart1() // 启动m //退出当前线程 if mStackIsSystemAllocated() { osStack = true } //执行完所有的 Goroutine 后,清理并退出m,不会执行到这里 mexit(osStack) } func mstart1() { ... asminit() minit() //初始化新的m,在新线程上调用 ... schedule() //开始调度,找到一个`runnable`状态的goroutine并执行 }func mstart0() { ... mstart1() // 启动m //退出当前线程 if mStackIsSystemAllocated() { osStack = true } //执行完所有的 Goroutine 后,清理并退出m,不会执行到这里 mexit(osStack) } func mstart1() { ... asminit() minit() //初始化新的m,在新线程上调用 ... schedule() //开始调度,找到一个`runnable`状态的goroutine并执行 }
尾语
至此,一整个go的启动流程就串起来了。
再声明一下,博主用的mac环境1.18.4
go版本,不同的版本会有稍有差异,尤其是go1.12到1.18内容变动还是比较多的。