runtime·main

[TOC]

@TODO

runtime·main 汇编真入口

以amd64架构为例,入口函数是在runtime/asm_amd64.s中定义的main函数,main函数会调用 runtime·rt0_go 来执行程序的初始化和启动调度系统。

runtime·rt0_go 中,通过 runtime·args(SB) 处理参数,runtime·osinit(SB) 进行 os 初始化,调用 runtime·schedinit(SB) 进行调度系统初始化,进行栈分配,将全局变量 g0 和 m0 绑定,调用 runtime·mstart(SB) 启动线程。

TEXT main(SB),NOSPLIT,$-8
	JMP	runtime·rt0_go(SB)
    
TEXT runtime·rt0_go(SB),NOSPLIT,$0
	...
	// set the per-goroutine and per-mach "registers"
	get_tls(BX)  // tls thread-local storage 
	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)
	CALL	runtime·args(SB)		// 将argc, argv设置到static全局变量中了
	CALL	runtime·osinit(SB)		// os初始化, 设置runtime.ncpu,不同平台实现方式不一样
	CALL	runtime·schedinit(SB) 	// 调度系统初始化, 根据GOMAXPROCS设置使用的procs等

	// 创建一个goroutine,然后开启执行程序
	MOVQ	$runtime·mainPC(SB), AX		// entry
	PUSHQ	AX
	PUSHQ	$0			// arg size
	CALL	runtime·newproc(SB) 
	POPQ	AX
	POPQ	AX

	// 启动 OS 线程 M
	CALL	runtime·mstart(SB)

	CALL	runtime·abort(SB)	// mstart should never return
	RET

	// Prevent dead-code elimination of debugCallV1, which is
	// intended to be called by debuggers.
	MOVQ	$runtime·debugCallV1(SB), AX
	RET

// m0和g0互相绑定
CALL	runtime·args(SB)		// 处理args
CALL	runtime·osinit(SB)		// os初始化
CALL	runtime·schedinit(SB) 	// 调度系统初始化

runtime.main() main goroutine

在系统栈上调用 newm(sysmon, nil, -1) ,分配一个OS 线程运行后台监控 sysmon,通过 sysmon 可以在程序启动阶段进行死锁检查,定期垃圾回收,调度抢张等工作。

调用 runtime.doInit 执行内部 init 函数,这个必须要在defer 之前执行,为何?

调用 runtime.gcenable 启动一个goroutine进行gc清扫。

调用 runtime.doInit 执行各种package的 init 函数

执行用户定义的包级别的 init 函数

执行 main_main 函数,也就是用户代码中的 main 函数

runtime内存init函数的执行,runtime_init 是由编译器动态生成的,里面包含了 runtime 包中所有的 init 函数 。

执行 main_init 函数,编译器动态生成的,包括用户定义的所有的init函数。

若是 exit(0) 后,进程没退出,由接下来的代码确保进程一会退出,就是for循环一直访问非法地址,正常情况下,一但出现非法地址访问,系统就会把该进程杀死,用这样的方法来确保进程退出。

// The main goroutine.
func main() {
	g := getg()

	// Racectx of m0->g0 is used only as the parent of the main goroutine.
	// It must not be used for anything else.
	g.m.g0.racectx = 0

	// Max stack size is 1 GB on 64-bit, 250 MB on 32-bit.
	// Using decimal instead of binary GB and MB because
	// they look nicer in the stack overflow failure message.
	if sys.PtrSize == 8 {
		maxstacksize = 1000000000
	} else {
		maxstacksize = 250000000
	}

	// Allow newproc to start new Ms.
	mainStarted = true

	if GOARCH != "wasm" { // no threads on wasm yet, so no sysmon
		systemstack(func() {
			newm(sysmon, nil, -1)
		})
	}

	// Lock the main goroutine onto this, the main OS thread,
	// during initialization. Most programs won't care, but a few
	// do require certain calls to be made by the main thread.
	// Those can arrange for main.main to run in the main thread
	// by calling runtime.LockOSThread during initialization
	// to preserve the lock.
	lockOSThread()

	if g.m != &m0 {
		throw("runtime.main not on m0")
	}

	doInit(&runtime_inittask) // must be before defer
	if nanotime() == 0 {
		throw("nanotime returning zero")
	}

	// Defer unlock so that runtime.Goexit during init does the unlock too.
	needUnlock := true
	defer func() {
		if needUnlock {
			unlockOSThread()
		}
	}()

	// Record when the world started.
	runtimeInitTime = nanotime()

	gcenable()

	main_init_done = make(chan bool)
	if iscgo {
		if _cgo_thread_start == nil {
			throw("_cgo_thread_start missing")
		}
		if GOOS != "windows" {
			if _cgo_setenv == nil {
				throw("_cgo_setenv missing")
			}
			if _cgo_unsetenv == nil {
				throw("_cgo_unsetenv missing")
			}
		}
		if _cgo_notify_runtime_init_done == nil {
			throw("_cgo_notify_runtime_init_done missing")
		}
		// Start the template thread in case we enter Go from
		// a C-created thread and need to create a new thread.
		startTemplateThread()
		cgocall(_cgo_notify_runtime_init_done, nil)
	}

	doInit(&main_inittask)

	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
	}
	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
	}
}

更新时间: