如何在Linux内核中实现另一种clone(2)系统调用的变体?

3

我试图创建另一个版本的clone(2)系统调用(在内核空间中),以使用一些额外参数来创建用户进程的克隆。这个系统调用将完全像clone(2)一样工作,但是我想从用户空间向内核传递一个附加参数。然而当我看到glibc的代码时,似乎每个参数的顺序都和用户调用clone()时不同。

int clone(int (*fn)(void *), void *child_stack,
             int flags, void *arg, ...
             /* pid_t *ptid, void *newtls, pid_t *ctid */ );

有些参数是由glibc的代码处理的。我在互联网上搜索了解glib的clone()如何工作,但找不到更好的文档。 请问有人可以解释一下吗?

  1. glibc如何处理clone()?
  2. 内核系统调用中的所有参数都与glibc中的clone()并不完全相同,那么这些变化是如何处理的呢?
2个回答

6

glibc如何处理clone()?

通过特定于体系结构的汇编包装器。对于i386,请参见glibc源代码中的sysdeps/unix/sysv/linux/i386/clone.S;对于x86-64,请参见sysdeps/unix/sysv/linux/x86-64/clone.S,以此类推。

普通的系统调用包装器是不足够的,因为用户空间需要切换堆栈。上述汇编文件有非常详细的注释,说明除了系统调用外,在用户空间实际上需要做什么。


所有内核中系统调用的参数与glibc中的clone并不完全相同,那么这些变化是如何处理的?
映射到系统调用的C库函数是包装函数。
例如,考虑POSIX.1 C库低级I/O函数write()和Linux write()系统调用。参数基本相同,错误条件也相同,但错误返回值不同。如果发生错误,C库函数返回-1并设置errno,而Linux系统调用返回负错误代码(基本匹配errno值)。
如果您查看例如sysdeps/unix/sysv/linux/x86_64/sysdep.h,您会发现Linux x86-64的基本系统调用包装器简化为:
# define INLINE_SYSCALL(name, nr, args...) \
  ({                                       \
    unsigned long int resultvar = INTERNAL_SYSCALL (name, , nr, args);        \
    if (__glibc_unlikely (INTERNAL_SYSCALL_ERROR_P (resultvar, )))            \
      {                                                                       \
        __set_errno (INTERNAL_SYSCALL_ERRNO (resultvar, ));                   \
        resultvar = (unsigned long int) -1;                                   \
      }                                                                       \
    (long int) resultvar; })

这只是调用实际系统调用的函数,然后检查系统调用返回值是否指示出错。如果出错,则将结果更改为-1并相应地设置errno。看起来有些奇怪,因为它依赖于GCC扩展来使其表现为单个语句。


假设您向Linux添加了一个新的系统调用,比如说

SYSCALL_DEFINE2(splork, unsigned long, arg1, void *, arg2);

无论出于何种原因,您希望将其作为用户空间的公开。

int splork(void *arg2, unsigned long arg1);

没问题!您只需要提供一个最小的头文件即可。
#ifndef _SPLORK_H
#define _SPLORK_H
#define _GNU_SOURCE
#include <sys/syscall.h>
#include <errno.h>

#ifndef __NR_splork
#if defined(__x86_64__)
#define __NR_splork /* syscall number on x86-64 */
#else
#if defined(__i386)
#define __NR_splork /* syscall number on i386 */
#endif
#endif

#ifdef __NR_splork
#ifndef SYS_splork
#define SYS_splork __NR_splork
#endif

int splork(void *arg2, unsigned long arg1)
{
    long retval;

    retval = syscall(__NR_splork, (long)arg1, (void *)arg2);
    if (retval < 0) {
        /* Note: For backward compatibility, we might wish to use
                     *(__errno_location()) = -retval;
                 here. */
        errno = -retval;
        return -1;
    } else
        return (int)retval;
}

#else
#undef SYS_splork

int splork(void *arg2, unsigned long arg1)
{
    /* Note: For backward compatibility, we might wish to use
                 *(__errno_location()) = ENOTSUP;
             here. */
    errno = ENOTSUP;
    return -1;
}

#endif

#endif /* _SPLORK_H */

SYS_splork__NR_splork是预处理宏,定义了新系统调用的系统调用号。由于该系统调用号可能尚未(?)包含在官方内核源代码和头文件中,因此上述头文件为每个支持的架构显式声明了它。对于不支持它的体系结构,splork()函数将始终返回-1,并带有errno == ENOTSUP

但请注意,Linux系统调用仅限于6个参数。如果您的内核函数需要更多参数,则需要将参数打包到一个结构中,将该结构的地址传递给内核,并使用copy_from_user()将值复制到内核中的同一结构中。

在所有Linux体系结构中,指针和long大小相同(int可能小于指针),因此我建议您在这些结构中使用long或固定大小类型来传递数据以及从内核中获取数据。


0

几乎可以没有汇编语言使用克隆系统调用。

问题不在于内核作为系统调用的一部分完成的堆栈切换,而很可能是glibc的syscall()包装器。

使用这些基本的包装器:

long _x64_syscall0(long n) {
  long ret;
  __asm__ __volatile__("syscall" : "=a"(ret) : "a"(n) : "rcx", "r11", "memory");
  return ret;
}


long _x64_syscall5(long n, long a1, long a2, long a3, long a4, long a5) {
  long ret;
  register long r10 __asm__("r10") = a4;
  register long r8 __asm__("r8") = a5;
  __asm__ __volatile__("syscall"
                       : "=a"(ret)
                       : "a"(n), "D"(a1), "S"(a2), "d"(a3), "r"(r10), "r"(r8)
                       : "rcx", "r11", "memory");
  return ret;
}

克隆使用的草图如下:

    int ret = _x64_syscall5(56 /* clone */ , CLONE_VM | SIGCHLD, 
                              (long long)new_stack, 0, 0, 0);
    if (ret == 0) {
      // we are the child
      ChildFunc();
      _x64_syscall0(60 /* exit */ );
    } else {
      // we are the parent
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接