如何在Linux内核中实现clone(2)syscall的另一种形式?
作者:互联网
我正在尝试创建另一个版本的clone(2)syscall(在内核空间中)以创建带有一些其他参数的用户进程的克隆.此系统调用将执行与clone(2)完全相同的工作,但我想要从user_space向内核传递一个附加参数.但是,当我看到glibc的code时
似乎每个参数的传递顺序与用户调用clone()的顺序不同
int clone(int (*fn)(void *), void *child_stack,
int flags, void *arg, ...
/* pid_t *ptid, void *newtls, pid_t *ctid */ );
相反,其中一些是由glibc的代码本身处理的.我在网上搜索了glib的clone()的工作原理,但找不到更好的文档.
谁能解释一下
> glibc如何处理clone()?
>而且内核中syscall的所有参数与glibc中的clone并不完全相同,那么如何处理这些变化?
解决方法:
How glibc handles the clone()?
通过特定于拱的装配包装.对于i386,请参阅glibc源代码中的sysdeps/unix/sysv/linux/i386/clone.S;有关详细信息,请参阅.对于x86-64,请参见sysdeps/unix/sysv/linux/x86-64/clone.S,依此类推.
普通的syscall包装器是不够的,因为要由用户空间来切换堆栈.上面的程序集文件对除syscall外在用户空间中实际需要执行的操作提供了非常有用的注释.
All the parameters of syscall in kernel are not exactly the same as clone in glibc, so how is these variation handled?
映射到syscall的C库函数是包装器函数.
例如,考虑POSIX.1 write()
C库低级I / O函数和Linux write()syscall.参数与错误条件基本相同,但是错误返回值不同.如果发生错误,C库函数将返回-1,并设置errno,而Linux syscall将返回负错误代码(基本上与errno值匹配).
如果你看例如sysdeps/unix/sysv/linux/x86_64/sysdep.h,您可以看到x86-64上用于Linux的基本syscall包装器可以归结为
# 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,
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()函数将始终使用errno == ENOTSUP返回-1.
但是请注意,Linux系统调用仅限于6个参数.如果您的内核功能需要更多功能,则需要将参数打包到一个结构中,将该结构的地址传递给内核,然后使用copy_from_user()将值复制到内核中的同一结构.
在所有Linux体系结构中,指针和long的大小相同(int可能小于指针),因此我建议您在此类结构中使用long或固定大小的类型将数据传递到内核或从内核传递数据.
标签:system-calls,glibc,c-3,linux,fork 来源: https://codeday.me/bug/20191026/1936671.html