Linux 上的系统调用参数类型是什么?

What is the type of system call arguments on Linux?

我想编写一个执行系统调用的通用函数。像

long my_syscall2(long number, long arg1, long arg2);

我希望它尽可能便携。所有架构的实现显然不同。函数的签名是否也需要不同?我可以使用 long 还是应该使用其他东西?

以下是我找到的可能的解决方案:

所以我猜问题是"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。