简体   繁体   English

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

[英]What is the type of system call arguments on Linux?

I want to write a generic function that does a system call. 我想写一个执行系统调用的泛型函数。 Something like 就像是

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

I want it to be as portable as possible. 我希望它尽可能便携。 The implementation is obviously different for all architectures. 对于所有体系结构,实现明显不同。 Does the signature of the function also need to be different? 功能的签名是否也需要不同? Can I use long or should I use something else? 我可以long使用long还是应该使用别的东西?

Here are the possible solutions that I found: 以下是我找到的可能的解决方案:

  • The kernel uses some dark magic : (__SYSCALL_DEFINEx calls __SC_LONG to get the type, __SC_LONG contains magic). 内核使用了一些黑魔法 :( __SYSCALL_DEFINEx调用__SC_LONG来获取类型,__SC_LONG包含魔法)。 I heard somewhere that types in user space aren't always the same as in kernel space, so I don't know if I can use it. 我听说用户空间中的类型并不总是与内核空间中的类型相同,所以我不知道是否可以使用它。
  • musl-libc uses long for all architectures that it supports except x32 : (defined in [arch]/syscall_arch.h). 除了x32之外, musl-libc 对它支持的所有体系结构使用long :(在[arch] /syscall_arch.h中定义)。
  • I could find documentation for all processor architectures and compilers that I want to support, look up register sizes and integer types sizes and pick any integer type that has the same size as registers. 我可以找到我想要支持的所有处理器体系结构和编译器的文档,查找寄存器大小和整数类型大小,并选择与寄存器大小相同的任何整数类型。

So I guess the question is "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?" 所以我想问题是“是否有一些规则说'系统调用参数的类型总是long有一些像x32这样的例外',或者我是否需要查找每个架构和编译器的文档?”

Edit: I know that some system calls take pointers and other types as parameters. 编辑:我知道一些系统调用将指针和其他类型作为参数。 I want to write generic functions that can call any system call, with generic parameter types. 我想编写可以调用任何系统调用的泛型函数,使用泛型参数类型。 These generic parameter types should be big enough to hold any of the actual parameter types. 这些通用参数类型应足够大,以容纳任何实际参数类型。 I know it's possible because syscall() function exists. 我知道这是可能的,因为syscall()函数存在。

Edit2: Here is another partial solution to this problem. Edit2:这是此问题的另一个部分解决方案。

Implementations of these functions currently look like this: 这些功能的实现目前如下所示:

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;
}

The interesting part is "=a"(ret) , this means that syscall return value that is stored in register a should be saved into variable ret . 有趣的部分是"=a"(ret) ,这意味着存储在寄存器a中的系统调用返回值应保存到变量ret Instead of writing a function that creates local variable, makes syscall, saves its return value into the variable and returns the variable I could write a macro that makes syscall and stores the result into a variable provided by caller. 使用syscall而不是编写创建局部变量的函数,而是将其返回值保存到变量中并返回变量,我可以编写一个宏来进行系统调用并将结果存储到调用者提供的变量中。 It would look like this: 它看起来像这样:

#define my_syscall2(RET, NUMBER, ARG1, ARG2) \
  __asm__ __volatile__ ("syscall" : "=a"(RET) : "a"(NUMBER), "D"(ARG1), "S"(ARG2) \
                      : "rcx", "r11", "memory");

And it would be used like this: 它会像这样使用:

long result;
void * arg1;
int arg2;
my_syscall2(result, <syscall number>, arg1, arg2);

This way I don't need to know register size and integer type that is big enough to hold a value of the register. 这样我就不需要知道足够大的寄存器大小和整数类型来保存寄存器的值。

System call arguments are passed in registers. 系统调用参数在寄存器中传递。 There size is thus limited to the size of a CPU register. 因此,大小限于CPU寄存器的大小。 That is, 32 bit on a 32 bit architecture, 64 bit on a 64 bit architecture. 也就是说,32位架构上的32位,64位架构上的64位。 Floating point numbers cannot be passed to the kernel this way. 浮点数不能以这种方式传递给内核。 Traditionally the kernel does not use floating point instructions (and may not be able to as the FPU state is usually not saved on entry to the kernel) so try to avoid floating point numbers in your own system calls. 传统上,内核不使用浮点指令(并且可能无法在进入内核时保存FPU状态),因此请尽量避免在自己的系统调用中使用浮点数。

System calls that use arguments of smaller types zero or sign extend them. 使用较小类型零或符号的参数的系统调用会扩展它们。 System calls that use larger argument types may split the argument into multiple registers. 使用较大参数类型的系统调用可能会将参数拆分为多个寄存器。

System calls (like mmap() ) that have to many parameters may be implemented by passing the parameters as a pointer to a structure, but this has a measurable performance overhead, so avoid designing system calls with more than five parameters. 具有许多参数的系统调用(如mmap() )可以通过将参数作为指针传递给结构来实现,但这具有可测量的性能开销,因此避免设计具有五个以上参数的系统调用。

At the end of the day, use the types appropriate for the value you want to send. 在一天结束时,使用适合您要发送的值的类型。 Let the libc deal with putting the data in the right places. 让libc处理将数据放在正确的位置。

I suggest that you use the existing syscall system call, rather than try to write one on your own. 我建议您使用现有的系统调用系统调用,而不是尝试自己编写一个。 It seems to do exactly what you want. 它似乎完全符合你的要求。 Look at the "Architecture-specific requirements" section of the manual page for a discussion of the valid questions you raised. 查看手册页的“体系结构特定要求”部分,以讨论您提出的有效问题。

There's no general solution. 没有通用的解决方案。 If you want to make your code ultra-multiarch you can just do something like that: 如果你想让你的代码超多任务,你可以做这样的事情:

#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);
...

But for most common-used architectures size of register is equal to long. 但对于大多数常用的体系结构,寄存器的大小等于long。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM