找到启动函数
在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
runtime.rt0_go
(asm_amd64.s)
保存AX,BX到栈里面
设置栈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)
如果有cgo,初始化cgo; 调用setg_gcc(g0),然后更新stackguard。
设置tls windows是设置到GS寄存器里面去了(sys_windows_and64.s runtime·settls(SB))
保存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)
- 调用rumtime.check()检查进行必要的运行时间检查,针对变量长度等。。。(runtime1.go check())
- 重新设置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)
park_m
把gp的状态转为waiting(gp是调用gopark的goroutinue)
同时将m.curg和m分离
然后schedule,执行别的goroutinue