【问题标题】:What is the type of system call arguments on Linux?Linux 上的系统调用参数的类型是什么?
【发布时间】:2016-06-08 07:45:36
【问题描述】:

我想编写一个执行系统调用的通用函数。类似的东西

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

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

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

  • 内核使用了一些dark magic:(__SYSCALL_DEFINEx 调用__SC_LONG 来获取类型,__SC_LONG 包含魔法)。我在某处听说用户空间中的类型与内核空间中的类型并不总是相同,所以我不知道我是否可以使用它。
  • musl-libc uses long for all architectures that it supports except x32:(在 [arch]/syscall_arch.h 中定义)。
  • 我可以找到我想要支持的所有处理器架构和编译器的文档,查找寄存器大小和整数类型大小,并选择与寄存器大小相同的任何整数类型。

所以我想问题是“是否有一些规则说‘系统调用参数的类型总是long,除了 x32 之类的例外’,还是我需要查找每个架构和编译器的文档?”

编辑:我知道有些系统调用将指针和其他类型作为参数。我想用泛型参数类型编写可以调用任何系统调用的泛型函数。这些泛型参数类型应该足够大以容纳任何实际参数类型。我知道这是可能的,因为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中的系统调用返回值应该保存到变量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);

这样我就不需要知道寄存器的大小和足够大的整数类型来保存寄存器的值。

【问题讨论】:

  • 如何处理不带参数的系统调用?
  • @black 我会定义 6 个函数 my_syscall{0-5}
  • 它实际上比这里简洁地回答要复杂得多(除非您指定架构),但请随意使用我的 syscall() 宏和 syscall0()-syscall6() 函数的公共领域实现github.com/technosaurus/BQC ... 所有这些都转换为 long 参数
  • @technosaurus 看来你一直在使用long,所以从技术上讲它并不能回答这个问题,但是你的库看起来很有趣,我一定会在写我自己的之前仔细看看,谢谢。
  • 我还没有想出一个好的编程方法来确定体系结构是否使用 long long 进行系统调用,但是 AFAIK 唯一不使用 long 的体系结构是 64 位体系结构上的 x32 变体当定义 ILP32 时,像 x86_64、PPC64 和 aarch64(可能还有 mips)。我最终可能会在 ARCHDATA 中指定它并为系统调用创建一个单独的 typedef,但我没有测试它们的好方法(我通常使用原住民 linux 构建和 qemu)

标签: c linux-kernel system-calls


【解决方案1】:

系统调用参数在寄存器中传递。因此,大小仅限于 CPU 寄存器的大小。也就是说,32 位架构上的 32 位,64 位架构上的 64 位。浮点数不能以这种方式传递给内核。传统上,内核不使用浮点指令(并且可能无法使用,因为 FPU 状态通常不会在进入内核时保存),因此请尽量避免在您自己的系统调用中使用浮点数。

使用较小类型参数的系统调用零或符号扩展它们。使用较大参数类型的系统调用可能会将参数拆分为多个寄存器。

具有多个参数的系统调用(如mmap())可以通过将参数作为指向结构的指针传递来实现,但这具有可衡量的性能开销,因此请避免设计具有超过五个参数的系统调用。

最后,使用适合您要发送的值的类型。让 libc 处理将数据放在正确的位置。

【讨论】:

    【解决方案2】:

    我建议您使用现有的syscall 系统调用,而不是尝试自己编写一个。它似乎完全按照您的意愿行事。查看手册页的“架构特定要求”部分,了解您提出的有效问题的讨论。

    【讨论】:

    • 有趣的是,syscall() 不是系统调用,而是相关系统调用指令的库包装器。
    • 是的,它还需要带有 gnu 扩展的标准 c 库,并将错误代码存储在全局 errno 变量中,而不是返回它。我承认,这些不是主要问题,但如果有一种简单的方法可以不使用所有这些,我就在徘徊。
    • 您也可以修改syscall源代码以满足您的特定需求。
    【解决方案3】:

    没有通用的解决方案。如果你想让你的代码超级多架构,你可以这样做:

    #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。

    【讨论】:

      猜你喜欢
      • 2017-06-03
      • 2014-01-23
      • 1970-01-01
      • 2014-04-22
      • 1970-01-01
      • 2012-09-02
      • 2012-07-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多