Ami*_*t G 3 c linux fork glibc system-calls
我正在尝试创建克隆(2)系统调用的另一个版本(在内核空间中)来创建带有一些附加参数的用户进程的克隆。此系统调用将执行与克隆(2)完全相同的工作,但我想要从 user_space 向内核传递一个附加参数。但是,当我看到 glibc 的代码时 ,似乎每个参数都没有按照与用户调用 clone() 相同的顺序传递
int clone(int (*fn)(void *), void *child_stack,
int flags, void *arg, ...
/* pid_t *ptid, void *newtls, pid_t *ctid */ );
Run Code Online (Sandbox Code Playgroud)
相反,其中一些是由 glibc 的代码本身处理的。我在互联网上搜索以了解 glib 的 clone() 是如何工作的,但找不到任何更好的文档。谁能解释一下
glibc 如何处理clone()?
通过特定于拱门的装配包装器。对于 i386,请参阅glibc 源代码中的sysdeps/unix/sysv/linux/i386/clone.S ;对于 x86-64,请参阅sysdeps/unix/sysv/linux/x86-64/clone.S等。
普通的系统调用包装器是不够的,因为切换堆栈取决于用户空间。上面的汇编文件对于除了系统调用之外实际上需要在用户空间中完成的操作提供了非常丰富的注释。
内核中syscall的所有参数与glibc中的clone并不完全相同,那么如何处理这些变化呢?
映射到系统调用的 C 库函数是包装函数。
例如,考虑 POSIX.1 write()C 库低级 I/O 函数和 Linuxwrite()系统调用。参数和错误条件基本相同,但错误返回值不同。如果发生错误, C 库函数会返回-1set errno,而 Linux 系统调用会返回负错误代码(基本上与errno值匹配)。
如果您查看sysdeps/unix/sysv/linux/x86_64/sysdep.h,您可以看到 x86-64 上 Linux 的基本系统调用包装器归结为
# 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; })
Run Code Online (Sandbox Code Playgroud)
它只调用实际的系统调用,然后检查系统调用返回值是否指示错误;如果是,则将结果更改为-1并进行errno相应设置。它看起来很有趣,因为它依赖于 GCC 扩展来使其表现为单个语句。
假设您向 Linux 添加了一个新的系统调用
SYSCALL_DEFINE2(splork, unsigned long, arg1, void *, arg2);
Run Code Online (Sandbox Code Playgroud)
并且,无论出于何种原因,您希望将其公开给用户空间
int splork(void *arg2, unsigned long arg1);
Run Code Online (Sandbox Code Playgroud)
没问题!您所需要的只是提供一个最小的头文件,
#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 */
Run Code Online (Sandbox Code Playgroud)
和是定义新系统调用的系统调用号的预处理器宏SYS_splork。__NR_splork由于系统调用号可能(还没有?)包含在官方内核源代码和头文件中,因此上面的头文件为每个支持的体系结构显式声明了它。对于不支持的架构,该splork()函数将始终-1以errno == ENOTSUP.
但请注意,Linux 系统调用仅限于 6 个参数。如果您的内核函数需要更多参数,则需要将参数打包到一个结构中,将该结构的地址传递给内核,然后使用copy_from_user()将这些值复制到内核中的同一结构中。
在所有 Linux 体系结构中,指针 和long的大小相同(int可能比指针小),因此我建议您long在此类结构中使用任一类型或固定大小类型来将数据传递到内核或从内核传递数据。