golang 启动流程

前端之家收集整理的这篇文章主要介绍了golang 启动流程前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

找到启动函数

在linux使用objdump反汇编可以看到golang编译的exe的启动代码

首先使用objdump -f exe 可以看到

start address 0x0808c760

然后使用objdump -d exe > t.asm

打开t.asm文件查找上面的start address
可以看到入口函数是_rt0_386_linux

入口函数分析

接下来就是找到这个入口函数了,这里因为我现在使用的是windows,所以接下的代码就是看的
_rt0_amd64_windows函数了(rt0_windows_amd64.s),linux主要是objdump反汇编比较方便

_rt0_amd64_windows

  1. _rt0_amd64_windows这个函数中将argc argv保存到DI SI 寄存器,然后调用main

  2. main直接调用runtime·rt0_go(asm_amd64.s)

runtime.rt0_go(asm_amd64.s)

  1. 保存AX,BX到栈里面

  2. 设置栈pos顶的位置,用于栈的扩大,初始化g0的栈空间。这是整个golang的第一个g

  1. MOVQ $runtime·g0(SB),DI
  2. LEAQ (-64*1024+104)(SP),BX
  3. MOVQ BX,g_stackguard0(DI)
  4. MOVQ BX,g_stackguard1(DI)
  5. MOVQ BX,(g_stack+stack_lo)(DI)
  6. MOVQ SP,(g_stack+stack_hi)(DI)
  1. 查询cpu信息

  2. 如果有cgo,初始化cgo; 调用setg_gcc(g0),然后更新stackguard。

  3. 设置tls windows是设置到GS寄存器里面去了(sys_windows_and64.s runtime·settls(SB))

  4. 保存g0到TLS, g0->m = m0 m0->g0 = g0

  1. get_tls(BX)
  2. LEAQ runtime·g0(SB),CX
  3. MOVQ CX,g(BX)
  4. LEAQ runtime·m0(SB),AX
  5.  
  6. // save m->g0 = g0
  7. MOVQ CX,m_g0(AX)
  8. // save m0 to g0->m
  9. MOVQ AX,g_m(CX)
  1. 调用rumtime.check()检查进行必要的运行时间检查,针对变量长度等。。。(runtime1.go check())
  2. 重新设置argc和argv到栈顶和栈第二个位置,然后调用
    runtime.args(c int32,v **byte)
    runtime1.go 保存argc和argv到全局变量
    runtime.osinit()
    osinit() os_windows.go
    runtime.schedinit()
    schedinit() proc.go

runtime.newproc(0,runtime.mainPC )

runtime.mstart()

schedinit()

这里主要初始化stack 内存 args env gc 等等

  1. if procresize(procs) != nil {
  2. throw("unknown runnable goroutine during bootstrap")
  3. }

创建P数组,MAXPROC个,同时设置当前的M的p。并且将多余的p设置为pidle状态并增加sched.npidle(通过pidleput函数实现)

runtime.newproc(0,runtime.mainPC )

  1. func newproc(siz int32,fn *funcval)

创建一个新的运行函数为fn的g,且fn的参数长度为siz。并且获取caller’s pc和argp,然后调用systemstack在系统栈上执行newproc1函数

  1. func newproc1(fn *funcval,argp *uint8,narg int32,nret int32,callerpc uintptr) *g

创建一个fn为函数,argp为参数 narg为参数个数 nret为返回值,callerpc是发起这次创建的地址(如果是在go语言中创建,就是go语句的位置

  1. siz := narg + nret
  2. siz = (siz + 7) &^ 7
  3.  
  4. // We could allocate a larger initial stack if necessary.
  5. // Not worth it: this is almost always an error.
  6. // 4*sizeof(uintreg): extra space added below
  7. // sizeof(uintreg): caller's LR (arm) or return address (x86,in gostartcall).
  8. if siz >= _StackMin-4*sys.RegSize-sys.RegSize {
  9. throw("newproc: function arguments too large for new goroutine")
  10. }

创建goroutinue的时候只分配初始大小的栈,如果参数argp的大小大于这个初始大小,则会报错。

  1. totalSize := 4*sys.RegSize + uintptr(siz) + sys.MinFrameSize // extra space in case of reads slightly beyond frame //设置栈指针
  2. totalSize += -totalSize & (sys.SpAlign - 1) // align to spAlign
  3. sp := newg.stack.hi - totalSize
  4. spArg := sp
  5. if usesLR {
  6. // caller's LR
  7. *(*uintptr)(unsafe.Pointer(sp)) = 0
  8. prepGoExitFrame(sp)
  9. spArg += sys.MinFrameSize
  10. }
  11. if narg > 0 { //拷贝参数到goroutinue栈
  12. memmove(unsafe.Pointer(spArg),unsafe.Pointer(argp),uintptr(narg))
  13. // This is a stack-to-stack copy. If write barriers
  14. // are enabled and the source stack is grey (the
  15. // destination is always black),then perform a
  16. // barrier copy. We do this *after* the memmove
  17. // because the destination stack may have garbage on
  18. // it.
  19. if writeBarrier.needed && !_g_.m.curg.gcscandone {
  20. f := findfunc(fn.fn)
  21. stkmap := (*stackmap)(funcdata(f,_FUNCDATA_ArgsPointerMaps))
  22. // We're in the prologue,so it's always stack map index 0.
  23. bv := stackmapdata(stkmap, 0)
  24. bulkBarrierBitmap(spArg,spArg,uintptr(narg), 0,bv.bytedata)
  25. }
  26. }
  27. memclrNoHeapPointers(unsafe.Pointer(&newg.sched),unsafe.Sizeof(newg.sched))
  28. newg.sched.sp = sp
  29. newg.stktopsp = sp
  30. newg.sched.pc = funcPC(goexit) + sys.PCQuantum // +PCQuantum so that prevIoUs instruction is in same function
  31. newg.sched.g = guintptr(unsafe.Pointer(newg))
  32. gostartcallfn(&newg.sched,fn)

gostartcallfun函数设置caller's PC到LR或者是SP,然后设置gobuf.pc = fn; gobuf.sp = sp,这样就可以假装是从goexit调用过来的,以便结束的时候回到goexit进行最后的清理工作,同时当goroutinue被换入的时候,pc回复fn,SP也会恢复

  1. newg.gopc = callerpc
  2. newg.startpc = fn.fn

最后将newg放入就绪队列。

  1. if atomic.Load(&sched.npidle) != 0 && atomic.Load(&sched.nmspinning) == 0 && runtimeInitTime != 0 {
  2. wakep()
  3. }

这个wakep的功能是如果有pidle状态的P,则新建一个M来执行P

这里因为还没有调用runtime.main()函数出初始化runtimeInitTime,所以本次调用并不会触发wakep,所以这个goroutinue会继续在最初的线程执行。

其中runtimeInitTime的初始化在runtime.main的这一句完成。runtimeInitTime = nanotime()

如果是系统启动之后调用newproc,且设置的maxproc大于1,则会有调用wakep来创建新的M了

runtime.mstart()

初始化g0的栈大小 然后调用mstart1 保存g0的栈 然后schedule

这个时候因为前面设置了一个就绪的goroutinue,所以就会执行那个goroutinue,并执行mainPC函数

schedule里面会设置m的curg位即将要执行的g,并调用gogo切换pc和sp等

runtime.mainPC

由asm_amd64.s中可以看到mainPC其实就是runtime.main

  1. DATA runtime·mainPC+0(SB)/8,$runtime·main(SB)
  2. GLOBL runtime·mainPC(SB),RODATA,$8

在这里启动一个sysmon进程

进行初始化 runtime_init()

使能gc gcenable()

如果cgo,还要初始化cgo的运行时环境

调用main_init

调用main_main

如果有正在pancing的状态,还要调用然后调用gopark()

这个函数的作用是发起一次schedule,可以让panic的goroutinue有机会打印完panic信息。 (这种情况要从panic那里直接程序就exit了?)

gopark

gopark的作用是让当前让出m,别进行一次调度

设置m的wait状态,然后调用mcall(park_m)

mcall是在g0栈上调用函数

park_m

把gp的状态转为waiting(gp是调用gopark的goroutinue)

同时将m.curg和m分离

然后schedule,执行别的goroutinue

猜你在找的Go相关文章