1. 启动过程函数调用流程
rt0_linux_amd64.s -->_rt0_amd64 --> rt0_go-->runtime·settls -->runtime·check-->runtime·args-->runtime·osinit-->runtime·schedinit-->runtime·newproc-->runtime·mstart
2. asm_amd64.s. runtime·rt0_go 关键部分源码分析
(版本:go 1.17.3 ,第191 行)
ok:
// set the per-goroutine and per-mach "registers"
get_tls(BX)
LEAQ runtime·g0(SB), CX
MOVQ CX, g(BX)
LEAQ runtime·m0(SB), AX
// 实现m0与g0 相关绑定
// save m->g0 = g0
MOVQ CX, m_g0(AX)
// save m0 to g0->m
MOVQ AX, g_m(CX)
CLD // convention is D is always left cleared
CALL runtime·check(SB)
MOVL 16(SP), AX // copy argc
MOVL AX, 0(SP)
MOVQ 24(SP), AX // copy argv
MOVQ AX, 8(SP)
// check 函数检查了各种类型以及类型转换是否有问题,位于 runtime/runtime1.go#137 中
CALL runtime·args(SB)
// osinit 函数用来初始化 cpu 数量(CPU 核心数与内存物理页大小),函数位于 runtime/os_linux.go#301
CALL runtime·osinit(SB)
// schedinit 函数用来初始化调度器(进行各种运行时组件初始化工作,这包括我们的调度器与内存分配器、回收器的初始化),函数位于 runtime/proc.go#654
CALL runtime·schedinit(SB)
// create a new goroutine to start program
//runtime.mainPC在数据段中被定义为runtime.main的入口地址
MOVQ $runtime·mainPC(SB), AX // entry
PUSHQ AX // runtime.main 作为 newproc 的第二个参数入栈
PUSHQ $0 // arg size // newproc 的第一个参数入栈,该参数表示runtime.main函数需要的参数大小,runtime.main没有参数,所以这里是0
// newproc 创建一个新的 goroutine 并放置到等待队列里,该 goroutine 会执行runtime.main 函数, 函数位于 runtime/proc.go#4250
//负责根据主 goroutine (即main)入口地址创建可被运行时调度的执行单元
CALL runtime·newproc(SB)
POPQ AX
POPQ AX
// start this M
// mstart 函数会启动主线程进入调度循环,然后运行刚刚创建的 goroutine,mstart 会阻塞住,除非函数退出,mstart 函数位于 runtime/proc.go#1328
CALL runtime·mstart(SB)
CALL runtime·abort(SB) // mstart should never return
RET
// Prevent dead-code elimination of debugCallV2, which is
// intended to be called by debuggers.
MOVQ $runtime·debugCallV2<ABIInternal>(SB), AX
RET
runtime.mainPC
DATA runtime·mainPC+0(SB)/8,$runtime·main(SB)
GLOBL runtime·mainPC(SB),RODATA,$8
3. schedinit
// src/runtime/proc.go
func schedinit() {
_g_ := getg()
(...)
// 栈、内存分配器、调度器相关初始化
sched.maxmcount = 10000 // 限制最大系统线程数量
stackinit() // 初始化执行栈
mallocinit() // 初始化内存分配器
mcommoninit(_g_.m) // 初始化当前系统线程
(...)
gcinit() // 垃圾回收器初始化
(...)
// 创建 P
// 通过 CPU 核心数和 GOMAXPROCS 环境变量确定 P 的数量
procs := ncpu
if n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 {
procs = n
}
procresize(procs)
(...)
}
stackinit()goroutine 执行栈初始化
mallocinit()内存分配器初始化
mcommoninit()系统线程的部分初始化工作
gcinit()垃圾回收器初始化
procresize()根据 CPU 核心数,初始化系统线程的本地缓存
4. mstart
有mstart 执行 runtime .main()
// The go:noinline is to guarantee the getcallerpc/getcallersp below are safe,
// so that we can set up g0.sched to return to the call of mstart1 above.
//go:noinline
func mstart1() {
_g_ := getg()
if _g_ != _g_.m.g0 {
throw("bad runtime·mstart")
}
// Set up m.g0.sched as a label returning to just
// after the mstart1 call in mstart0 above, for use by goexit0 and mcall.
// We're never coming back to mstart1 after we call schedule,
// so other calls can reuse the current frame.
// And goexit0 does a gogo that needs to return from mstart1
// and let mstart0 exit the thread.
_g_.sched.g = guintptr(unsafe.Pointer(_g_))
_g_.sched.pc = getcallerpc()
_g_.sched.sp = getcallersp()
asminit()
minit()
// Install signal handlers; after minit so that minit can
// prepare the thread to be able to handle the signals.
if _g_.m == &m0 {
mstartm0()
}
if fn := _g_.m.mstartfn; fn != nil {
fn()
}
if _g_.m != &m0 {
acquirep(_g_.m.nextp.ptr())
_g_.m.nextp = 0
}
schedule()
}
5. runtime.main
(proc.go 145行)
// The main goroutine.
func main() {
g := getg()
....
doInit(&main_inittask)
// Disable init tracing after main init done to avoid overhead
// of collecting statistics in malloc and newproc
inittrace.active = false
close(main_init_done)
needUnlock = false
unlockOSThread()
if isarchive || islibrary {
// A program compiled with -buildmode=c-archive or c-shared
// has a main, but it is not executed.
return
}
//main.mian函数调用 (应用程序的main函数)
fn := main_main // make an indirect call, as the linker doesn't know the address of the main package when laying down the runtime
fn()
if raceenabled {
racefini()
}
// Make racy client program work: if panicking on
// another goroutine at the same time as main returns,
// let the other goroutine finish printing the panic trace.
// Once it does, it will exit. See issues 3934 and 20018.
if atomic.Load(&runningPanicDefers) != 0 {
// Running deferred functions should not take long.
for c := 0; c < 1000; c++ {
if atomic.Load(&runningPanicDefers) == 0 {
break
}
Gosched()
}
}
if atomic.Load(&panicking) != 0 {
gopark(nil, nil, waitReasonPanicWait, traceEvGoStop, 1)
}
exit(0)
for {
var x *int32
*x = 0
}
}
参考:
https://www.jianshu.com/p/59b2e46c8708
https://www.kancloud.cn/cfun_good/golang/2033482
网友评论