gh-liu / myNote

0 stars 0 forks source link

阅读 `go runtime`中的 `systemstack`, `mcall`函数 #5

Open gh-liu opened 5 months ago

gh-liu commented 5 months ago
// func systemstack(fn func())
TEXT runtime·systemstack(SB), NOSPLIT, $0-8 // NOSPLIT 不允许栈分割
    MOVQ    fn+0(FP), DI    // DI = fn  // 参数 fn 的指针储存到 DI 寄存器中
    get_tls(CX)
    MOVQ    g(CX), AX   // AX = g   // 获取当前 g
    MOVQ    g_m(AX), BX // BX = m   // 获取当前 m

    CMPQ    AX, m_gsignal(BX)           // 检查当前 g 是不是gsignal,是则跳转到 noswitch
    JEQ noswitch

    MOVQ    m_g0(BX), DX    // DX = g0  // 将当前 m 的 g0 储存到 DX 寄存器中
    CMPQ    AX, DX                      // 检查当前 g 是不是 g0,是则跳转到 noswitch
    JEQ noswitch

    CMPQ    AX, m_curg(BX)              // 检查当前 g 是不是 m.curg,不是则跳转到 bad
    JNE bad

    // Switch stacks.
    // The original frame pointer is stored in BP,
    // which is useful for stack unwinding.
    // Save our state in g->sched. Pretend to
    // be systemstack_switch if the G stack is scanned.
    CALL    gosave_systemstack_switch<>(SB) // 保存当前 goroutine 的状态,在切换栈之后恢复

    // switch to g0
    MOVQ    DX, g(CX)                     // DX 里是 g0, 将 g0 存储到 TLS 中
    MOVQ    DX, R14 // set the g register 
    MOVQ    (g_sched+gobuf_sp)(DX), SP    // 将 g0 的 栈指针 SP 设置到 SP 寄存器,也就是切换了栈

    // call target function
    MOVQ    DI, DX                        // 储存函数指针到 DX
    MOVQ    0(DI), DI                     // 将函数实际地址储存到 DI 
    CALL    DI                            // 调用函数

    // switch back to g
    get_tls(CX)
    MOVQ    g(CX), AX                  // g0
    MOVQ    g_m(AX), BX                // g0.m
    MOVQ    m_curg(BX), AX             // g
    MOVQ    AX, g(CX)                  // 将 g 存储到 TLS 中
    MOVQ    (g_sched+gobuf_sp)(AX), SP // 切换回 g 的栈
    MOVQ    (g_sched+gobuf_bp)(AX), BP 
    MOVQ    $0, (g_sched+gobuf_sp)(AX) // 清零,帮助垃圾回收器(GC)定位未使用的栈空间
    MOVQ    $0, (g_sched+gobuf_bp)(AX)
    RET

noswitch:
    // already on m stack; tail call the function
    // Using a tail call here cleans up tracebacks since we won't stop
    // at an intermediate systemstack.
    MOVQ    DI, DX    // 将函数指针移动到 DX 寄存器中
    MOVQ    0(DI), DI // 函数地址储存到 DI 寄存器中
    // The function epilogue is not called on a tail call.
    // Pop BP from the stack to simulate it.
    POPQ    BP        // 模拟函数调用的栈帧弹出操作
    JMP DI        // 直接跳转函数处,执行代码:将当前函数的控制器移动到新函数上,且未保留当前函数的调用帧

bad:
    // Bad: g is not gsignal, not g0, not curg. What is it?
    MOVQ    $runtime·badsystemstack(SB), AX
    CALL    AX
    INT $3
gh-liu commented 5 months ago
// 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.
TEXT runtime·mcall<ABIInternal>(SB), NOSPLIT, $0-8                  // 不需要栈分割
    MOVQ    AX, DX  // DX = fn                                  // 将函数参数 fn 存储到寄存器 DX 中

    // Save state in g->sched. The caller's SP and PC are restored by gogo to    
    // resume execution in the caller's frame (implicit return). The caller's BP 
    // is also restored to support frame pointer unwinding.                      
    MOVQ    SP, BX  // hide (SP) reads from vet                 // 将当前栈指针保存到寄存器 BX 中
    MOVQ    8(BX), BX   // caller's PC                      // 将调用者的程序计数器 PC 存储到寄存器 BX 中
    MOVQ    BX, (g_sched+gobuf_pc)(R14)                         // 将调用者的 PC 存储到当前 g 的调度器结构体中的 gobuf.pc 字段
    LEAQ    fn+0(FP), BX    // caller's SP                      // 计算函数 fn 的地址,并存储到寄存器 BX 中
    MOVQ    BX, (g_sched+gobuf_sp)(R14)                         // 将函数地址存储到当前 g 的调度器结构体中的 gobuf.sp 字段,将在调度器恢复时调用
    // Get the caller's frame pointer by dereferencing BP. Storing BP as it is
    // can cause a frame pointer cycle, see CL 476235.
    MOVQ    (BP), BX // caller's BP                             // 从当前帧指针(BP)指向的内存地址读取上一个帧指针的值,存储到寄存器 BX 中,支持帧指针的展开
    MOVQ    BX, (g_sched+gobuf_bp)(R14)                         // 将帧指针的值存储到当前 g 的调度器结构体中的 gobuf.bp 字段。

    // switch to m->g0 & its stack, call fn
    MOVQ    g_m(R14), BX                                        // 将当前 g 所属的调度器结构体中的 g 字段(即 M)的值读取到寄存器 BX 中
    MOVQ    m_g0(BX), SI    // SI = g.m.g0                      // 将 M 的 g0 字段(即全局 G0)的值读取到寄存器 SI 中
    CMPQ    SI, R14 // if g == m->g0 call badmcall              // 比较当前 g 是否为全局 G0。如果不是,继续执行,否则跳转到 badmcall 标签
    JNE goodm                                               // 如果当前 g 不是全局 G0,则跳转到 goodm 标签
    JMP runtime·badmcall(SB)                                // 如果当前 g 是全局 G0,则调用 badmcall 函数进行错误处理
goodm:
    MOVQ    R14, AX     // AX (and arg 0) = g               // 将当前 g 的地址存储到寄存器 AX 中,并作为参数传递给函数调用
    MOVQ    SI, R14     // g = g.m.g0                       // 将全局 G0 的地址存储到寄存器 R14 中
    get_tls(CX)     // Set G in TLS                     // 将当前 g 的地址存储到 TLS 中
    MOVQ    R14, g(CX)                                          // 将当前 g 的地址存储到 TLS 中
    MOVQ    (g_sched+gobuf_sp)(R14), SP // sp = g0.sched.sp // 将全局 G0 的调度器结构体中的栈指针(SP)加载到栈指针寄存器中
    PUSHQ   AX  // open up space for fn's arg spill slot    // 将当前 g 的地址压入栈中,为函数调用的参数腾出空间
    MOVQ    0(DX), R12                                          // 将函数 fn 的地址读取到寄存器 R12 中
    CALL    R12     // fn(g)                            // 调用函数 fn
    POPQ    AX                                                  // 弹出栈顶元素,即当前 g 的地址
    JMP runtime·badmcall2(SB)                               // 跳转到错误处理函数
    RET