563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
|
# int32 clone(int32 flags, void *stk, M *mp, G *gp, void (*fn)(void));
TEXT runtime·clone(SB),NOSPLIT,$0
# 1) 接受参数分蘖放入 DI、SI、R13、R9、R12寄存器中
# 清除 DX、R10、D8寄存器的值
# 第一个参数 flags,clone需要的参数
MOVL flags+0(FP), DI # DI = flags
# 第二个参数 stk,g0栈空间
MOVQ stk+8(FP), SI # SI = stk
MOVQ $0, DX # 清除DX寄存器
MOVQ $0, R10 # 清除R10寄存器
MOVQ $0, R8 # 清除R8寄存器
# Copy mp, gp, fn off parent stack for use by child.
# Careful: Linux system call clobbers CX and R11.
#
# 从父堆栈中复制 mp、gp、fn 以供子级使用
# 小心:Linux系统调用clobbers CX和R11
# 第三个参数 mp
# 在clone后子线程开始运行时,R13、R9、R12的值会被拷贝给子线程
MOVQ mp+16(FP), R13 # R13 = mp
# 第四个参数 gp,这里是 g0
MOVQ gp+24(FP), R9 # R9 = gp
# 第五个参数 fn,mstart()函数
MOVQ fn+32(FP), R12 # R12 = fn
# 2) 判断 mp 和 gp 的值是为 nil
# 判断mp==nil和g0=nil
# m 如果R13为0则跳转
CMPQ R13, $0
JEQ nog1
# g 如果R9为0则跳转
CMPQ R9, $0
JEQ nog1
# 3) 找到需要设置TLS的地址值,也就是&m.tls[1]
# 调用系统SYS_clone函数克隆线程
# 把m.tls地址存入R8寄存器
LEAQ m_tls(R13), R8 # R8 = TLS
#ifdef GOOS_android
# Android stores the TLS offset in runtime·tls_g.
SUBQ runtime·tls_g(SB), R8
#else
# R8 = -8(FS); R8=&m.tls[1]处地址
ADDQ $8, R8 # ELF wants to use -8(FS)
#endif
# 添加CLONE_SETTLS标志
ORQ $0x00080000, DI #add flag CLONE_SETTLS(0x00080000) to call clone
nog1:
MOVL $SYS_clone, AX # 写入clone函数标志,然后调用系统函数
# 系统调用约定寄存器 DI SI DX R10 R8 R9 参数传参
# DI = flags
# SI = stk
# DX = 0
# R10 = 0
# R8 = R8=&m.tls[1]
# R9 = gp
SYSCALL
# 4) 系统调用后,新创建的子线程和当前线程都会从系统调用中返回然后执行后面的代码
#
# 那么从系统调用返回之后我们怎么知道哪个是父线程哪个是子线程,从而来决定它们的执行流程?
# 使用过fork系统调用的读者应该知道,我们需要通过返回值来判断父子线程:
# 1. 系统调用的返回值如果是0则表示这是子线程
# 2. 不为0则表示这个是父线程
# 4.1) 父线程的处理逻辑
# In parent, return.
#
# 在父线程中,直接返回。
# 判断系统调用SYS_clone的返回值AX与0比较
CMPQ AX, $0
# JEQ 表示AX是0则执行 3(PC)跳过3条指令
JEQ 3(PC) #跳转到子线程部分
# 这里是父线程直接把返回值写入栈,然后退出函数
MOVL AX, ret+40(FP)
RET
# 4.2) 子线程的处理逻辑,设置SP,判断mp和gp,设置mp.procid
# In child, on new stack.
#
# 在子线程中,在new栈上。
# 新创建的子线程从这里开始,注意一下代码是在子线程中,寄存器也是子线程的
# 设置CPU栈顶寄存器指向子线程的栈顶,这条指令看起来是多余的?内核应该已经把SP设置好了
MOVQ SI, SP
# If g or m are nil, skip Go-related setup.
#
# 如果 g 或 m 为 nil,跳过 Go-related 设置。
# m 新创建的m结构体对象的地址,由父线程保存在R13寄存器中的值被复制到了子线程
CMPQ R13, $0 # R13 = mp
JEQ nog2 # R13 为 0 时跳转
# g m.g0的地址,由父线程保存在R9寄存器中的值被复制到了子线程
CMPQ R9, $0 # R9 = gp
JEQ nog2 # R9 为 0 时跳转
# Initialize m->procid to Linux tid
#
# 将m->procid初始化为Linux tid。
MOVL $SYS_gettid, AX # 通过gettid()系统调用获取线程ID(tid)
SYSCALL
MOVQ AX, m_procid(R13) # m.procid = tid
# Set FS to point at m->tls.
#
# 新线程刚刚创建出来,还未设置线程本地存储,即m结构体对象还未与工作线程关联起来,
# 下面的指令负责设置新线程的TLS,把m对象和工作线程关联起来
# 这两行代码在go1.18中消失了,原因在于CLONE_SETTLS配合参数和R8寄存器在clone中被设置了
# LEAQ m_tls(R13), DI # 取m.tls字段的地址
# CALL runtime·settls(SB)
# In child, set up new stack
get_tls(CX) # CX=&m.tls[1]; CX=TLS
MOVQ R13, g_m(R9) # g0.m = m
MOVQ R9, g(CX) # m.tls[0]=&g0
# R14=&g0 R14寄存器主要存储当前正在运行的goroutine
MOVQ R9, R14 # set g register
CALL runtime·stackcheck(SB) # 检查 SP 是否在 [g->stack.lo, g->stack.hi) 范围内
nog2:
# Call fn. This is the PC of an ABI0 function.
#
# 调用mstart()函数开始调度循环
CALL R12 # 永不返回
# It shouldn't return. If it does, exit that thread.
MOVL $111, DI
MOVL $SYS_exit, AX
SYSCALL
JMP -3(PC) // keep exiting
|