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

MOVQ    $runtime·g0(SB), DI
LEAQ    (-64*1024+104)(SP), BX
MOVQ    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), CX
MOVQ    CX, g(BX)
LEAQ    runtime·m0(SB), AX

    // save m->g0 = g0
MOVQ    CX, m_g0(AX)
    // save m0 to g0->m
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 等等

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 + nret
siz = (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 = callerpc
newg.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

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值