Go mcall源码分析

func mcall(fn func(*g)) 的主要作用是协程切换,它将当前正在执行的协程 g 的状态保存起来,然后在 m->g0 的堆栈上调用新的函数 fn(g),在函数 fn 内会将之前运行的协程 g 放弃,然后调用一次 schedule() 来挑选新的协程运行。

mcall 函数是一段汇编程序,位于 asm_amd64.s 文件中。

 

mcall 函数代码如下:

TEXT runtime·mcall(SB), NOSPLIT, $0-8

// 将 fn 函数指针存到 DI 寄存器
MOVQ fn+0(FP), DI

// get_tls 是一个宏,其中 tls 是线程局部存储,最终替换为 FS 寄存器,将 tls 内容保存到存到 CX 寄存器
get_tls(CX)

// g(CX) 是一个宏,在 tls 上可得到 g,也就是当前正在运行的 g,把这个 g 的地址存到 AX 寄存器
MOVQ g(CX), AX // save state in g->sched

// go 函数栈的大小,不包含传入的参数和返回值,这两个部分由调用者管理。mcall 的 fn 是传入参数,这个参数 fn 放在【调用 mcall 的函数】的函数栈里。
// mcall 除了一个传入参数 fn,没有其他变量,所以 mcall 的栈大小为 0。
// 一个栈大小为 0 的函数,它的栈顶,也就是 SP 指向的位置,存放了调用 mcall 的函数的下一条指令,也就是这里所谓的 caller 的 PC。 // PC 值的存放,是由 CALL 指令自动做的事情,也就是 go 编译器做的事情。调用某函数时,先把本函数的下一掉指令 push 进栈。
// 将 caller‘s PC 存放到 BX 里。 MOVQ 0(SP), BX // caller's PC // AX 存放了 g 的地址,BX 存放了caller's PC,把 caller's PC 存放到 g 结构体的 sched 的 gobuf 的 pc 字段。 MOVQ BX, (g_sched+gobuf_pc)(AX) LEAQ fn+0(FP), BX // caller's SP // BX 存放了 caller 的 SP,存放到 g 的相应位置  MOVQ BX, (g_sched+gobuf_sp)(AX) // AX 存放的就是 g 本身,存放到 g 的相应位置  MOVQ AX, (g_sched+gobuf_g)(AX) // 将 BP 寄存器的值存放到 g 的相应位置  MOVQ BP, (g_sched+gobuf_bp)(AX) // 切换到 m->g0 的栈,然后调用fn MOVQ g(CX), BX // 把当前g的地址存到BX里 MOVQ    g(CX), BX  // 根据 g,可以得到 m MOVQ    g_m(BX), BX  // 根据 m,可以得到 g0 MOVQ    m_g0(BX), SI  // 如果 g0 就是 g,调用 badmcall。  CMPQ    SI, AX  // if g == m->g0 call badmcall
// 如果 g != g0,就跳过 3 条指令,继续执行。 JNE 3(PC)   // g != g0 的 错误处理 badmcall
MOVQ $runtime·badmcall(SB), AX JMP AX // SI 存的是 g0,将 g0 变成当前 g。 MOVQ SI, g(CX) // g = m->g0
// 将 g0 的 SP 值,放到寄存器 SP,开始切换,调用 fn(g) MOVQ (g_sched+gobuf_sp)(SI), SP // sp = m->g0->sched.sp
// AX 存的就是刚才的 g,不是现在的 g0,将 g 放到栈上。这一步就是普通的,我要调用fn函数了,我要把参数g,先放到栈上。
PUSHQ AX

// 调用fn
MOVQ DI, DX MOVQ 0(DI), DI CALL DI
POPQ AX MOVQ $runtime·badmcall2(SB), AX JMP AX RET

 1. goroutinegoroutine 是 Go 语言实现的用户态线程,主要用来解决操作系统线程太重的问题,主要表现在以下两个方面:创建和切换太重操作系统线程的创建和切换都 ...