繁体   English   中英

为什么Golang克隆syscall abi与x86-64上的Linux内核克隆不同

[英]Why golang clone syscall abi is diffent from linux kernel clone on x86-64

在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 promise? 另一方面,R9和R12似乎是不必要的。

请帮我。

DX和R10为零的原因

根据clone联机帮助页,仅在设置CLONE_PARENT_SETTIDCLONE_CHILD_SETTID时才使用CLONE_PARENT_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与此手册页( 参考 )中的ptidctid相对应。

实际上,从os_linux.go: Source调用runtime.clone()时未设置此标志。

他们不需要tid的原因可能是因为它不是pthread之类的库,用户可以使用tid完成一些复杂的事情。

R8,R9和R12用于什么

简而言之,R8,R9和R12不会被系统调用使用,而是用于在其后构造堆栈。

请注意,R8和R9作为参数传递给系统调用,但未被克隆使用(请参见下面的原因),并且R12在系统调用之后被保留,在系统调用之后可以安全地使用这些寄存器。 参考

让我们看看细节。

内部调用runtime.clone如下:

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)))
    ....
}

阅读Go的汇编程序快速指南 ,并发布OP代码,您可以看到R8是mp指针,R9是mp.g0指针,R12是要在clone线程中调用的某些函数的指针。 mg结构如下: Source和this: Source )。

R8是clone的参数,表示tls(线程本地存储),但除非设置了CLONE_SETTLS否则不使用它: Source

R9通常用作系统调用的第六个参数,但是clone不使用它,因为它仅使用5个参数( Source )。

R12是在系统调用后保留的寄存器。

最后,让我们看看runtime.clone的源代码 重要的是在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)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM