Linux 上的系统调用参数类型是什么?
What is the type of system call arguments on Linux?
我想编写一个执行系统调用的通用函数。像
long my_syscall2(long number, long arg1, long arg2);
我希望它尽可能便携。所有架构的实现显然不同。函数的签名是否也需要不同?我可以使用 long
还是应该使用其他东西?
以下是我找到的可能的解决方案:
- 内核使用一些dark magic:(__SYSCALL_DEFINEx调用__SC_LONG获取类型,__SC_LONG包含魔法)。我在某处听说用户 space 中的类型与内核 space 中的类型并不总是相同,所以我不知道是否可以使用它。
- musl-libc uses long for all architectures that it supports except x32:(在 [arch]/syscall_arch.h 中定义)。
- 我可以找到我想要支持的所有处理器架构和编译器的文档,查找寄存器大小和整数类型大小,并选择与寄存器大小相同的任何整数类型。
所以我猜问题是"Is there some rule that says 'type of system call arguments are always long
with some exceptions like x32' or do I need to look up documentation for every architecture and compiler?"
编辑:我知道有些系统调用将指针和其他类型作为参数。我想编写可以使用通用参数类型调用任何系统调用的通用函数。这些通用参数类型应该足够大以容纳任何实际参数类型。我知道这是可能的,因为存在 syscall() 函数。
Edit2:这是这个问题的另一个部分解决方案。
这些函数的实现目前如下所示:
static __inline long my_syscall2(long number, long arg1, long arg2)
{
unsigned long ret;
__asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(number), "D"(arg1), "S"(arg2)
: "rcx", "r11", "memory");
return ret;
}
有趣的部分是 "=a"(ret)
,这意味着存储在寄存器 a
中的系统调用 return 值应该保存到变量 ret
中。我可以编写一个宏来创建系统调用并将结果存储到由呼叫者。它看起来像这样:
#define my_syscall2(RET, NUMBER, ARG1, ARG2) \
__asm__ __volatile__ ("syscall" : "=a"(RET) : "a"(NUMBER), "D"(ARG1), "S"(ARG2) \
: "rcx", "r11", "memory");
它会像这样使用:
long result;
void * arg1;
int arg2;
my_syscall2(result, <syscall number>, arg1, arg2);
这样我就不需要知道寄存器大小和足以容纳寄存器值的整数类型。
我建议您使用现有的 syscall 系统调用,而不是尝试自己编写一个。它似乎完全按照你的意愿行事。查看手册页的 "Architecture-specific requirements" 部分,讨论您提出的有效问题。
系统调用参数在寄存器中传递。因此,大小被限制为 CPU 寄存器的大小。也就是说,32 位在 32 位架构上,64 位在 64 位架构上。浮点数不能以这种方式传递给内核。传统上内核不使用浮点指令(并且可能无法使用,因为 FPU 状态通常不会在进入内核时保存)所以尽量避免在您自己的系统调用中使用浮点数。
使用较小类型零或符号参数的系统调用会扩展它们。使用较大参数类型的系统调用可能会将参数拆分到多个寄存器中。
具有许多参数的系统调用(如 mmap()
)可以通过将参数作为指向结构的指针传递来实现,但这具有可衡量的性能开销,因此请避免设计具有多个参数的系统调用五个参数。
归根结底,使用适合您要发送的值的类型。让 libc 处理将数据放在正确的位置。
没有通用的解决方案。如果你想让你的代码超多架构,你可以这样做:
#if ARCH_WITH_32BIT_REGS
typedef uint32_t reg_size_int_t;
#elif ARCH_WITH_64BIT_REGS
typedef uint64_t reg_size_int_t;
#elif ARCH_WITH_16BIT_REGS
typedef uint16_t reg_size_int_t;
....
#endif
reg_size_int_t syscall_1( reg_size_t nr, reg_size_t arg0);
...
但对于大多数常用架构来说,寄存器的大小等于 long。
我想编写一个执行系统调用的通用函数。像
long my_syscall2(long number, long arg1, long arg2);
我希望它尽可能便携。所有架构的实现显然不同。函数的签名是否也需要不同?我可以使用 long
还是应该使用其他东西?
以下是我找到的可能的解决方案:
- 内核使用一些dark magic:(__SYSCALL_DEFINEx调用__SC_LONG获取类型,__SC_LONG包含魔法)。我在某处听说用户 space 中的类型与内核 space 中的类型并不总是相同,所以我不知道是否可以使用它。
- musl-libc uses long for all architectures that it supports except x32:(在 [arch]/syscall_arch.h 中定义)。
- 我可以找到我想要支持的所有处理器架构和编译器的文档,查找寄存器大小和整数类型大小,并选择与寄存器大小相同的任何整数类型。
所以我猜问题是"Is there some rule that says 'type of system call arguments are always long
with some exceptions like x32' or do I need to look up documentation for every architecture and compiler?"
编辑:我知道有些系统调用将指针和其他类型作为参数。我想编写可以使用通用参数类型调用任何系统调用的通用函数。这些通用参数类型应该足够大以容纳任何实际参数类型。我知道这是可能的,因为存在 syscall() 函数。
Edit2:这是这个问题的另一个部分解决方案。
这些函数的实现目前如下所示:
static __inline long my_syscall2(long number, long arg1, long arg2)
{
unsigned long ret;
__asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(number), "D"(arg1), "S"(arg2)
: "rcx", "r11", "memory");
return ret;
}
有趣的部分是 "=a"(ret)
,这意味着存储在寄存器 a
中的系统调用 return 值应该保存到变量 ret
中。我可以编写一个宏来创建系统调用并将结果存储到由呼叫者。它看起来像这样:
#define my_syscall2(RET, NUMBER, ARG1, ARG2) \
__asm__ __volatile__ ("syscall" : "=a"(RET) : "a"(NUMBER), "D"(ARG1), "S"(ARG2) \
: "rcx", "r11", "memory");
它会像这样使用:
long result;
void * arg1;
int arg2;
my_syscall2(result, <syscall number>, arg1, arg2);
这样我就不需要知道寄存器大小和足以容纳寄存器值的整数类型。
我建议您使用现有的 syscall 系统调用,而不是尝试自己编写一个。它似乎完全按照你的意愿行事。查看手册页的 "Architecture-specific requirements" 部分,讨论您提出的有效问题。
系统调用参数在寄存器中传递。因此,大小被限制为 CPU 寄存器的大小。也就是说,32 位在 32 位架构上,64 位在 64 位架构上。浮点数不能以这种方式传递给内核。传统上内核不使用浮点指令(并且可能无法使用,因为 FPU 状态通常不会在进入内核时保存)所以尽量避免在您自己的系统调用中使用浮点数。
使用较小类型零或符号参数的系统调用会扩展它们。使用较大参数类型的系统调用可能会将参数拆分到多个寄存器中。
具有许多参数的系统调用(如 mmap()
)可以通过将参数作为指向结构的指针传递来实现,但这具有可衡量的性能开销,因此请避免设计具有多个参数的系统调用五个参数。
归根结底,使用适合您要发送的值的类型。让 libc 处理将数据放在正确的位置。
没有通用的解决方案。如果你想让你的代码超多架构,你可以这样做:
#if ARCH_WITH_32BIT_REGS
typedef uint32_t reg_size_int_t;
#elif ARCH_WITH_64BIT_REGS
typedef uint64_t reg_size_int_t;
#elif ARCH_WITH_16BIT_REGS
typedef uint16_t reg_size_int_t;
....
#endif
reg_size_int_t syscall_1( reg_size_t nr, reg_size_t arg0);
...
但对于大多数常用架构来说,寄存器的大小等于 long。