书接上回。现在我们知道_rt0_go这个汇编过程是GO语言的真正入口并做了一些初始化工作,本文来大略的过一下这个过程和它调用的几个runtime里的函数。
Windwos Live Writer写的文章,编码有问题,对不住各位:我不想改。
 
参数
MOVL    argc+0(FP), AX
MOVL    argv+4(FP), BX
SUBL    $128, SP        // plenty of scratch
ANDL    $~15, SP
MOVL    AX, 120(SP)        // save argc, argv away
MOVL    BX, 124(SP)
这一段主要是处理操作系统传来的参数,并把他们保存到栈上
 
初始化栈
MOVL    $runtime路g0(SB), BP
LEAL    (-64*1024+104)(SP), BX
MOVL    BX, g_stackguard(BP)
MOVL    BX, g_stackguard0(BP)
MOVL    SP, g_stackbase(BP)
 
测试CPUID(386、AMD64)
// find out information about the processor we're on
MOVL    $0, AX
CPUID
CMPL    AX, $0
JE    nocpuinfo
MOVL    $1, AX
CPUID
MOVL    CX, runtime·cpuid_ecx(SB)
MOVL    DX, runtime·cpuid_edx(SB)
测试CPUID的目的在于充分利用硬件的能力,在GO源码中的哈希部分用到了它,在哈希的时候用aes&sse指令提高速度。
 
初始化CGO
    // if there is an _cgo_init, call it to let it
    // initialize and to set up GS.  if not,
    // we set up GS ourselves.
    MOVL    _cgo_init(SB), AX
    TESTL    AX, AX
    JZ    needtls
    MOVL    $setmg_gcc<>(SB), BX
    MOVL    BX, 4(SP)
    MOVL    BP, 0(SP)
    CALL    AX
    // update stackguard after _cgo_init
    MOVL    $runtime·g0(SB), CX
    MOVL    g_stackguard0(CX), AX
    MOVL    AX, g_stackguard(CX)
    // skip runtime·ldt0setup(SB) and tls test after _cgo_init for non-windows
    CMPL runtime·iswindows(SB), $0
    JEQ ok
needtls:
    // skip runtime·ldt0setup(SB) and tls test on Plan 9 in all cases
    CMPL    runtime·isplan9(SB), $1
    JEQ    ok
    // set up %gs
    CALL    runtime·ldt0setup(SB)
    // store through it, to make sure it works
    get_tls(BX)
    MOVL    $0x123, g(BX)
    MOVL    runtime·tls0(SB), AX
    CMPL    AX, $0x123
    JEQ    ok
 
运行时检测
// set up m and g "registers"
get_tls(BX)
LEAL    runtime·g0(SB), CX
MOVL    CX, g(BX)
LEAL    runtime·m0(SB), AX
MOVL    AX, m(BX)
// save m->g0 = g0
MOVL    CX, m_g0(AX)
CALL    runtime·emptyfunc(SB)    // fault if stack check is wrong
// convention is D is always cleared
CLD
CALL    runtime·check(SB)
这段检查一下运行时函数调用,emptyfunc只有一条ret指令,会立即返回。
 
重头戏:调用初始化C函数并最终进入C环境
// saved argc, argv
MOVL    120(SP), AX
MOVL    AX, 0(SP)
MOVL    124(SP), AX
MOVL    AX, 4(SP)
CALL    runtime·args(SB)
CALL    runtime·osinit(SB)
CALL    runtime·hashinit(SB)
CALL    runtime·schedinit(SB)
// create a new goroutine to start program
PUSHL    $runtime·main·f(SB)    // entry
PUSHL    $0    // arg size
ARGSIZE(8)
CALL    runtime·newproc(SB)
ARGSIZE(-1)
POPL    AX
POPL    AX
// start this M
CALL    runtime·mstart(SB)
INT $3
RET
DATA    runtime·main·f+0(SB)/4,$runtime·main(SB)
GLOBL    runtime·main·f(SB),RODATA,$4
1,args在windows下不执行,目前只有在linux下执行
2,osinit会加载一些dll,并设置控制台和获取系统信息
3,hashinit在检测到CPU支持ase和sse指令集时,会优化执行速度。
 
proc.c
_rt0_go最后调用的几个C函数都在proc.c里面,到这里就容易理解了。追踪newproc/newproc1可以发现它先推入goexit后推入main,这说明goexit是在main退出后执行的。在栈里的表现就好像是goexit调用了main,其实这样做只是为了让main退出后能弹出goexit的地址。所以上文所指的runtime.caller的错误信息也就不难理解了。