go程序是如何启动的?
测试环境:
$ uname -a
Linux zxl 4.9.0-deepin13-amd64 #1 SMP PREEMPT Deepin 4.9.57-1 (2017-10-19) x86_64 GNU/Linux
$ go version
go version go1.10 linux/amd64
我们来写一个程序hello.go:
package main
func main(){
println("Hello,World!")
}
使用go build -gcflags "-N -l" -o hello hello.go
编译,-gcflags "-N -l"
关闭编译器代码优化和内联
运行$ gdb ./hello
(gdb) info files
Symbols from "/data/Study/go/hello".
Local exec file:
`/data/Study/go/hello', file type elf64-x86-64.
Entry point: 0x448ef0
(gdb) b *0x448ef0
Breakpoint 1 at 0x448ef0: file /usr/local/go/src/runtime/rt0_linux_amd64.s, line 8.
(gdb) b runtime.rt0_go
Breakpoint 2 at 0x445730: file /usr/local/go/src/runtime/asm_amd64.s, line 89.
(gdb) b runtime.args
Breakpoint 3 at 0x42f970: file /usr/local/go/src/runtime/runtime1.go, line 60.
(gdb) b runtime.osinit
Breakpoint 4 at 0x41ff80: file /usr/local/go/src/runtime/os_linux.go, line 272.
(gdb) b runtime.schedinit
Breakpoint 5 at 0x4245d0: file /usr/local/go/src/runtime/proc.go, line 472.
(gdb) b runtime.newproc
Breakpoint 6 at 0x42aec0: file /usr/local/go/src/runtime/proc.go, line 3235.
(gdb) b runtime.mstart
Breakpoint 7 at 0x426290: file /usr/local/go/src/runtime/proc.go, line 1170.
(gdb) b runtime.main
Breakpoint 8 at 0x423420: file /usr/local/go/src/runtime/proc.go, line 109
可以程序开始于/usr/local/go/src/runtime/rt0_linux_amd64.s:8
,即rt0_arm64_linux
函数, 源码包runtime下放可许多rt0_sys_arch.s文件
TEXT _rt0_arm64_linux(SB),NOSPLIT,$-8
MOVD 0(RSP), R0 // argc
ADD $8, RSP, R1 // argv
BL main(SB)
来看main
TEXT main(SB),NOSPLIT,$-8
MOVD $runtime·rt0_go(SB), R2
BL (R2)
exit:
MOVD $0, R0
MOVD $94, R8 // sys_exit
SVC
B exit
源码中的·
编译完之后会变成.
断点b runtime.rt0_go
,定位到/usr/local/go/src/runtime/asm_amd64.s, line 89
TEXT runtime·rt0_go(SB),NOSPLIT,$0
...
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
PUSHQ $0 // arg size
CALL runtime·newproc(SB)//创建一个goroutine
POPQ AX
POPQ AX
// start this M
CALL runtime·mstart(SB)
MOVL $0xf1, 0xf1 // crash
RET
DATA runtime·mainPC+0(SB)/8,$runtime·main(SB)
GLOBL runtime·mainPC(SB),RODATA,$8
由此,汇编引导过程完成
断点runtime.args
,在/usr/local/go/src/runtime/runtime1.go, line 60
,初始化参数列表
func args(c int32, v **byte) {
argc = c
argv = v
sysargs(c, v)
}
断点runtime.osinit
,在/usr/local/go/src/runtime/os_linux.go, line 272
,获取CPU核数
func osinit() {
ncpu = getproccount()
}
断点runtime.schedinit
,在/usr/local/go/src/runtime/proc.go, line 472
,调度器初始化
// The bootstrap sequence is:
// call osinit
// call schedinit
// make & queue new G
// call runtime·mstart
// The new G calls runtime·main.
func schedinit() {
// raceinit must be the first call to race detector.
// In particular, it must be done before mallocinit below calls racemapshadow.
_g_ := getg()
if raceenabled {
_g_.racectx, raceprocctx0 = raceinit()
}
sched.maxmcount = 10000 //最大系统线程数限制 runtime/debug.SetMaxThreads
//初始化栈、内存分配器,调度器相关
tracebackinit()
moduledataverify()
stackinit()
mallocinit()
mcommoninit(_g_.m)
alginit() // maps must not be used before this call
modulesinit() // provides activeModules
typelinksinit() // uses maps, activeModules
itabsinit() // uses activeModules
msigsave(_g_.m)
initSigmask = _g_.m.sigmask
//命令行参数与环境变量初始化
goargs()
goenvs()
//处理GODEBUG,GOTRACEBACK调试相关的环境变量
parsedebugvars()
//垃圾回收器的初始化
gcinit()
//通过CPU Core和GOMAXPROCS环境变量确定P的数量
sched.lastpoll = uint64(nanotime())
procs := ncpu
if n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 {
procs = n
}
// 调整P的数量
if procresize(procs) != nil {
throw("unknown runnable goroutine during bootstrap")
}
// For cgocheck > 1, we turn on the write barrier at all times
// and check all pointer writes. We can't do this until after
// procresize because the write barrier needs a P.
if debug.cgocheck > 1 {
writeBarrier.cgo = true
writeBarrier.enabled = true
for _, p := range allp {
p.wbBuf.reset()
}
}
if buildVersion == "" {
// Condition should never trigger. This code just serves
// to ensure runtime·buildVersion is kept in the resulting binary.
buildVersion = "unknown"
}
}
创建一个goroutine(G),断点runtime.newproc
,在/usr/local/go/src/runtime/proc.go, line 3235
//G就是goroutine实现的核心结构了,G维护了goroutine需要的栈、程序计数器以及它所在的M等信息。
启动一个内核级线程(M),一个开始执行程序,断点runtime.mstart
,在/usr/local/go/src/runtime/proc.go, line 1170
//M代表内核级线程,一个M就是一个线程,goroutine就是跑在M之上的;M是一个很大的结构,里面维护小对象内存cache(mcache)、当前执行的goroutine、随机数发生器等等非常多的信息。
关于调度器是如何调度的,请查看goroutine与调度器其形象的描述了go调度器的工作方式
而runtime.main
即 /usr/local/go/src/runtime/proc.go
中的mian
Breakpoint 8, runtime.main () at /usr/local/go/src/runtime/proc.go:109
109 func main() {
(gdb) bt
#0 runtime.main () at /usr/local/go/src/runtime/proc.go:109
#1 0x0000000000447fc1 in runtime.goexit () at /usr/local/go/src/runtime/asm_amd64.s:2361
#2 0x0000000000000000 in ?? ()
(gdb)
func main() {
...
// 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
//启动系统后台监控(定期垃圾回收,并发调度)
systemstack(func() {
newm(sysmon, nil)
})
//在初始化过程中,上主线程锁,大多数程序并不关心,但少数确实需要由主线程发出的某些调用,可以在初始化过程中的main_main调用runtime.lockosthread来保存锁。
lockOSThread()
...
//执行runtime包里的所有init函数 must be before defer
runtime_init()
...
// Defer unlock so that runtime.Goexit during init does the unlock too.
needUnlock := true
defer func() {
if needUnlock {
unlockOSThread()
}
}()
//启动垃圾回收器后台操作
gcenable()
...
//
fn := main_init //调用所有用户程序涉及包的init函数
fn()
//解锁
needUnlock = false
unlockOSThread()
...
fn = main_main // 执行用户逻辑入口
fn()
}
至此,一个go程序的启动逻辑完成
注:本文参考自go语言学习笔记
有疑问加站长微信联系(非本文作者)
