golang 启动流程

来源:互联网 发布:网络高清摄像头报价 编辑:程序博客网 时间:2024/06/05 20:20

找到启动函数

在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

MOVQ    $runtime·g0(SB), DILEAQ    (-64*1024+104)(SP), BXMOVQ    BX, g_stackguard0(DI)MOVQ    BX, g_stackguard1(DI)MOVQ    BX, (g_stack+stack_lo)(DI)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

get_tls(BX)LEAQ    runtime·g0(SB), CXMOVQ    CX, g(BX)LEAQ    runtime·m0(SB), AX    // save m->g0 = g0MOVQ    CX, m_g0(AX)    // save m0 to g0->mMOVQ    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 等等

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

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

runtime.newproc(0, runtime.mainPC )

func newproc(siz int32, fn *funcval)

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

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

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

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

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

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

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

newg.gopc = callerpcnewg.startpc = fn.fn

最后将newg放入就绪队列。

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

这个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

DATA    runtime·mainPC+0(SB)/8,$runtime·main(SB)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

0 0
原创粉丝点击