461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
|
# func systemstack(fn func())
TEXT runtime·systemstack(SB), NOSPLIT, $0-8
# 闭包参数 fn func(),把fn存入DI寄存器
MOVQ fn+0(FP), DI # DI = fn
get_tls(CX) # CX = &m.tls[1]; TLS
# 这里获取的g是当前正在运行的g,可能是g0也可能不是
# 从TLS获取当前g,存入AX寄存器
MOVQ g(CX), AX # AX = g
# 当前正在运行的工作线程m,将g.m存入BX寄存器中
MOVQ g_m(AX), BX # BX = m
# 1) 验证数据
# 如果当前 g 是 m.gsignal
# 跳转 noswitch 没有什么可做直接调用 fn 即可
# 可知m.gsignal的栈是g0栈
CMPQ AX, m_gsignal(BX)
JEQ noswitch # gsignal 也是系统栈,不用切换
# 将m.g0存入DX寄存器
MOVQ m_g0(BX), DX # DX = g0
# 比较 g 和 g0,是否是一个,如果是直接跳转 noswitch
# 比较当前g是不是g0
CMPQ AX, DX
JEQ noswitch # 已经在g0上,不需要切换
# 比较当前g是否和m.curg不一致
# 比较 g 和 m.curg,如果不相等跳转 bad
# 程序刚启动初始化时m.curg为nil,会在前面的g0判断处直接跳转了,不会走到这里
# 为什么要比较crug是否是当前g?是因为从g0切换回当前g需要m.curg这个参数。
# 这种情况在普通 goroutine 切换 g0 栈时用到
CMPQ AX, m_curg(BX)
JNE bad
# 2) 存储g信息
# switch stacks
# save our state in g->sched. Pretend to
# be systemstack_switch if the G stack is scanned.
#
# 将当前g的信息保存到 g->sched 中。如果G栈已被扫描,则假装是 systemstack_switch 调用的。
# 保存 goroutine 的调度信息。
CALL gosave_systemstack_switch<>(SB)
# 3) 切换到g0栈
# g0写入TLS、g0写入R14寄存器中、g0的栈顶值写入SP寄存器中
# switch to g0
MOVQ DX, g(CX) # g0写入TLS中
# R14 = g0
MOVQ DX, R14 # set the g register
# BX = g0.sched.gobuf.sp
MOVQ (g_sched+gobuf_sp)(DX), BX
# SP = g0.sched.gobuf.sp
MOVQ BX, SP # 恢复g0的SP
# 4) 调用 fn 函数,此时已经切换到g0栈
# 上下文信息在DX寄存器中,包含闭包捕获的变量列表
# call target function
MOVQ DI, DX # DX = fn = &funcval
MOVQ 0(DI), DI # DI = funcval.fn
CALL DI # fn()
# 5) 切换回g栈
# 注意:当从g0切换回g的时候,并没有将g0的状态保存到g0.sched中
# 也就是说每次从g0切换至其他的goroutine后,g0栈上的内容就被抛弃了
# 下次切换至g0还是从头开始。
# 从m.curg中取出g,然后写入TLS中,恢复SP寄存器的值
# 这里没有恢复PC寄存器和BP寄存器的值,因为PC寄存器的值这里不需要恢复顺序执行代码即可,
# BP寄存器的值在调用systemstack()函数的整个过程中都没有修改,因此也不需要恢复。
# switch back to g
get_tls(CX) # CX = &m.tls[1]
MOVQ g(CX), AX # AX = g0
MOVQ g_m(AX), BX # BX = m
MOVQ m_curg(BX), AX # AX = m.curg; 当前g
MOVQ AX, g(CX) # g存入TLS
# 调用 systemstack 函数前的 SP;
# R14寄存器在这个函数中没有被设置回来,应该是编译器负责设置回来吧。
MOVQ (g_sched+gobuf_sp)(AX), SP # 恢复SP; SP = g.sched.gobuf.sp
MOVQ $0, (g_sched+gobuf_sp)(AX) # 清除 g.sched.gobuf.sp = 0
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.
#
# 已经在m栈上;由于我们不会在中间系统栈上停止,因此在这里直接调用fn
MOVQ DI, DX # DX = &funcval
MOVQ 0(DI), DI # DI = funcval.fn
JMP DI # 调用fn函数
bad:
# Bad: g is not gsignal, not g0, not curg. What is it?
# Bad:g 不是 gsignal,也不是 g0,不是 curg。它是什么?
MOVQ $runtime·badsystemstack(SB), AX
CALL AX
INT $3 # 调试错误
|