glibc / sysdeps / unix / sysv / linux / x86_64 / clone.S中的linux内核克隆abi定义:
The kernel expects:
rax: system call number
rdi: flags
rsi: child_stack
rdx: TID field in parent
r10: TID field in child
r8: thread pointer
然后转到go1.11.5 / src / runtime / sys_linux_amd64.s的golang克隆syscall:
// int32 clone(int32 flags, void *stk, M *mp, G *gp, void (*fn)(void));
TEXT runtime·clone(SB),NOSPLIT,$0
MOVL flags+0(FP), DI
MOVQ stk+8(FP), SI
MOVQ $0, DX
MOVQ $0, R10
// Copy mp, gp, fn off parent stack for use by child.
// Careful: Linux system call clobbers CX and R11.
MOVQ mp+16(FP), R8
MOVQ gp+24(FP), R9
MOVQ fn+32(FP), R12
MOVL $SYS_clone, AX
SYSCALL
那么,为什么DX,R10,R8不保持对clone-syscall的承诺? 另一方面,R9和R12似乎是不必要的。
请帮助我。
答案 0 :(得分:5)
根据manpage of clone,这些仅在设置了CLONE_PARENT_SETTID
,CLONE_CHILD_SETTID
时使用。
CLONE_PARENT_SETTID(从Linux 2.5.49开始) 将子线程ID存储在父线程的ptid位置 记忆。 (在Linux 2.5.32-2.5.48中,有一个标志CLONE_SETTID 这样做。)存储操作在clone()之前完成。 将控制权返回给用户空间。
CLONE_CHILD_SETTID(从Linux 2.5.49开始) 将子线程ID存储在子线程的位置ctid中 记忆。在clone()返回之前,存储操作完成 控制用户空间。
DX和R10对应于此联机帮助页(Reference)中的ptid
和ctid
。
实际上,从os_linux.go:Source调用runtime.clone()时,不会设置此标志。
他们不需要tid的原因可能是因为它不是pthread之类的库,用户可以使用tid做一些复杂的事情。
简而言之,R8,R9和R12不会被系统调用使用,而是用于在其后构造堆栈。
请注意,R8和R9作为参数传递给系统调用,但未被克隆使用(请参见下面的原因),并且R12在系统调用之后被保留,因此在系统调用之后可以安全地使用这些寄存器。 (Reference)
让我们看看细节。
内部runtime.clone的调用如下:Source
func newosproc(mp *m) {
stk := unsafe.Pointer(mp.g0.stack.hi)
....
ret := clone(cloneFlags, stk, unsafe.Pointer(mp), unsafe.Pointer(mp.g0), unsafe.Pointer(funcPC(mstart)))
....
}
阅读Quick Guide to Go's Assembler,并发布代码OP,您可以看到R8指向mp
的指针,R9指向mp.g0
的指针,R12指向您想要的某个函数的指针调用clone
ed线程。 (m
和g
的结构看起来像这样:Source并且这样:Source
)。
R8是clone的参数,表示tls(线程本地存储),但除非设置CLONE_SETTLS
:Source
R9通常用作系统调用的第6个参数,但clone不使用它,因为它仅使用5个参数(Source)。
R12是在系统调用后保留的寄存器。
因此,最后让我们来看一下runtime.clone的source。重要的是在SYSCALL
之后。他们正在使用已创建的子线程中的R8和R9进行一些堆栈设置,并最终调用R12。
// int32 clone(int32 flags, void *stk, M *mp, G *gp, void (*fn)(void));
TEXT runtime·clone(SB),NOSPLIT,$0
MOVL flags+0(FP), DI
MOVQ stk+8(FP), SI
MOVQ $0, DX
MOVQ $0, R10
// Copy mp, gp, fn off parent stack for use by child.
// Careful: Linux system call clobbers CX and R11.
MOVQ mp+16(FP), R8
MOVQ gp+24(FP), R9
MOVQ fn+32(FP), R12
MOVL $SYS_clone, AX
SYSCALL
// In parent, return.
CMPQ AX, $0
JEQ 3(PC)
MOVL AX, ret+40(FP)
RET
// In child, on new stack.
MOVQ SI, SP
// If g or m are nil, skip Go-related setup.
CMPQ R8, $0 // m
JEQ nog
CMPQ R9, $0 // g
JEQ nog
// Initialize m->procid to Linux tid
MOVL $SYS_gettid, AX
SYSCALL
MOVQ AX, m_procid(R8)
// Set FS to point at m->tls.
LEAQ m_tls(R8), DI
CALL runtime·settls(SB)
// In child, set up new stack
get_tls(CX)
MOVQ R8, g_m(R9)
MOVQ R9, g(CX)
CALL runtime·stackcheck(SB)
nog:
// Call fn
CALL R12
//(omitted)