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

时间:2019-02-26 18:12:00

标签: linux go

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似乎是不必要的。

请帮助我。

1 个答案:

答案 0 :(得分:5)

为什么DX和R10都为零

根据manpage of clone,这些仅在设置了CLONE_PARENT_SETTIDCLONE_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)中的ptidctid

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

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

R8,R9和R12用于什么

简而言之,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线程。 (mg的结构看起来像这样:Source并且这样:Source )。

R8是clone的参数,表示tls(线程本地存储),但除非设置CLONE_SETTLSSource

,否则不使用它。

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)