一页天书不分说

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理
// func mcall(fn func(*g))
// Switch to m->g0's stack, call fn(g).
// Fn must never return. It should gogo(&g->sched)
// to keep running g.
// 上面的翻译:
// 切换到 m->g0 的栈上,然后调用 fn (g)函数。
// fn 必须不能返回。fn 应该 调用 gogo 函数 来保持运行 g。
TEXT runtime·mcall(SB), NOSPLIT, $0-8
    MOVQ    fn+0(FP), DI       // 很容易理解的,将fn这个函数指针存到DI寄存器。

    get_tls(CX)                     // 宏,顾名思义,tls 是线程局部存储,将这个存储的开始位置存到 CX
    MOVQ    g(CX), AX   // save state in g->sched     // g(CX)还是宏,在tls上可以拿到g,也就是当前正在跑的g,把这个g的地址存到AX寄存器
    MOVQ    0(SP), BX   // caller's PC                       // 这一句有点难懂,得明白整个golang的函数调用规则才能明白。简介:golang的函数栈的大小,不包含传入的参数和返回值,这两个部分由调用者管理,mcall例子中,fn是传入参数,这个fn是放在【调用mcall的函数】的函数栈里的。mcall除了一个传入参数fn之外,没有其他变量了,所以mcall的栈大小就是0。一个栈大小为0的函数,栈顶,也就是SP指向的位置,刚好就是存的调用mcall的函数的下一条指令,也就是这里所谓的 caller 的 PC。这个PC值为什么会出现在这里,那就是因为,这是CALL指令自动做的事情,也是golang编译器做的事情,调用某函数时,先把本函数的下一掉指令push进栈。话说回来了,既然mcall不需要额外的栈大小了,前面说了,栈大小是0,所以,自然而然的,SP,存的东西就是caller的PC。好,拿到了caller‘s PC,把他存到BX里。
    MOVQ    BX, (g_sched+gobuf_pc)(AX) // 这句简单,AX存了g的地址,BX存了caller's PC,那这句就是把caller's PC 存到 g 结构体的 sched 的 gobuf 的 pc 字段。
    LEAQ    fn+0(FP), BX    // caller's SP // 这句又难了。为什么栈上,存fn的地方,是caller的栈顶呢。根据上面的解释能猜到一点,因为栈上存fn这个事,就是由caller来处理的,caller的栈在往下生长的时候,最后一个数据项就是fn。再下面就是caller pc,等等等等。
    MOVQ    BX, (g_sched+gobuf_sp)(AX) // BX存的是caller的SP,将BX存到g的相应位置 
    MOVQ    AX, (g_sched+gobuf_g)(AX)   // AX存的就是g本身,也存起来
    MOVQ    BP, (g_sched+gobuf_bp)(AX) //  这一句不太重要,好像BP寄存器已经没啥用了。可能在debug的时候,有点用??不清楚,这个,但是肯定是跟切换上下文的主逻辑和函数调用的主逻辑没什么关系了。

    // switch to m->g0 & its stack, call fn // 切换到m->g0的栈,然后调用fn
    MOVQ    g(CX), BX // 把当前g的地址存到BX里
    MOVQ    g_m(BX), BX // 根据g,可以拿到m
    MOVQ    m_g0(BX), SI // 根据m,又可以拿到 g0,目的就是拿到 g0,下面要切换了。
    CMPQ    SI, AX  // if g == m->g0 call badmcall // 如果g0就是g,那不行,这种情况,我们不考虑先。
    JNE 3(PC) // 假如g!= g0,就跳到红色标记,继续执行。 
    MOVQ    $runtime·badmcall(SB), AX
    JMP AX
    红色标记:MOVQ    SI, g(CX)   // g = m->g0 // SI存的是g0,将g0变成当前g。
    MOVQ    (g_sched+gobuf_sp)(SI), SP  // sp = m->g0->sched.sp // 将g0的SP值,拿出来,交给寄存器SP,看见没,开始切换了,或者说,已经切完了,下面就是调用 fn (g)
    PUSHQ   AX // AX 是什么,存的就是刚才的g,不是现在的g0,将g放到栈上。这一步就是普通的,我要调用fn函数了,我要把参数g,先放到栈上。
    MOVQ    DI, DX // 这一步把fn存到DX不知道要干嘛,可能后续调用fn的时候,会用到??不知道,等再接着看。
    MOVQ    0(DI), DI // 这一步和下一步,就是调用 fn
    CALL    DI           // 调用fn
    POPQ    AX
    MOVQ    $runtime·badmcall2(SB), AX
    JMP AX
    RET
posted on 2019-04-30 16:55  一页天书不分说  阅读(1556)  评论(0编辑  收藏  举报