420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
|
# 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
# 1) 从AX中获取参数,注意这里还是再普通goroutine中不是g0
# 在go1.17后版本中采用寄存器传参,因此AX作为第一个参数存储的是macll的参数
# 传参顺序 AX、BX、CX、DI、SI、R8、R9、R10、R11
MOVQ AX, DX # DX = &funcval; &funcval -> goexit0
# 2) 保存状态到 g->sched。这里的g是当前正在运行的goroutine。
# save state in g->sched
# 以下保存当前状态到g->sched中,在go 1.17版本后R14寄存器存储的是当前工作线程运行的goroutine
# 0(SP):存储的是goexit1函数调用mall函数的下一条指令地址。也就是goexit1()函数的返回地址
MOVQ 0(SP), BX # caller's PC mcall返回地址放入BX
# g.sched.pc = BX
MOVQ BX, (g_sched+gobuf_pc)(R14) # g.sched.pc = BX,保存g的rip
# fn+0(FP)表当前参数所在栈位置,也就是goexit1函数的SP位置处。因为参数在调用者栈上。
LEAQ fn+0(FP), BX # caller's SP
MOVQ BX, (g_sched+gobuf_sp)(R14) # g.sched.sp = BX,保存g的rsp
# g.sched.bp = BP
MOVQ BP, (g_sched+gobuf_bp)(R14) # g.sched.bp = BP,保存g的rbp
# 3) 切换到 m->g0 及其堆栈,调用fn函数。
# switch to m->g0 & its stack, call fn
#
# BX = m
MOVQ g_m(R14), BX # BX = g.m,拿到当前工作线程M
# SI = g0
MOVQ m_g0(BX), SI # SI = g.m.g0,那当当前工作线程M的g0栈
# 此刻,SI = g0, R14 = g,所以这里在判断g是否是g0,如果g == g0则一定是哪里代码写错了
CMPQ SI, R14 # if g == m->g0 call badmcall
JNE goodm # SI和R14不相等则跳转
JMP runtime·badmcall(SB)
goodm: # 正常流程跳转到这里
# AX = g
MOVQ R14, AX # AX (and arg 0) = g,AX = g2,当前g不是g0,AX也是goexit0函数需要的参数
# R14 = g0
MOVQ SI, R14 # g = g.m.g0,R14 = g0,设置当前正在运行的是g0
# CX = &m.tls[1]
get_tls(CX) # Set G in TLS
# TLS = g0
MOVQ R14, g(CX)
# sp = g0.sched.sp
MOVQ (g_sched+gobuf_sp)(R14), SP
# AX = g 入栈,此时已经在g0的栈上了
# AX存储的时普通的goroutine,这里入栈也是goexit0()函数的参数
PUSHQ AX # open up space for fn's arg spill slot
# R12 = funcval.fn; DX = &funcval
MOVQ 0(DX), R12 # fn的第一个成员是goexit0函数代码地址处,R12 = fn.fn
# 调用goexit0()函数,参数再AX寄存器中,上下文在DX寄存器中。
CALL R12 # 调用 goexit0(g),这里【永不会返回】
POPQ AX
JMP runtime·badmcall2(SB)
RET
|