【发布时间】:2018-01-21 16:52:16
【问题描述】:
我正在用 C 语言编写一个仅依赖于 Linux 内核的独立程序。
研究了相关的manualpages,了解到在x86-64上,Linux系统调用入口点通过rax、rdi、rsi这七个寄存器接收系统调用号和六个参数, rdx、r10、r8 和 r9。
这是否意味着每个系统调用都接受六个参数?
我研究了几个 libc 实现的源代码,以了解它们如何执行系统调用。有趣的是,musl 包含两种不同的系统调用方法:
-
这个汇编源文件定义了一个
__syscall函数,它将系统调用号和正好六个参数移动到ABI中定义的寄存器。该函数的通用名称暗示它可以用于任何系统调用,尽管它总是向内核传递六个参数。 -
这个 C 头文件定义了七个独立的
__syscallN函数,N指定了它们的数量。这表明仅传递系统调用所需的确切参数数量的好处超过了拥有和维护七个几乎相同的函数的成本。
所以我自己试了一下:
long
system_call(long number,
long _1, long _2, long _3, long _4, long _5, long _6)
{
long value;
register long r10 __asm__ ("r10") = _4;
register long r8 __asm__ ("r8") = _5;
register long r9 __asm__ ("r9") = _6;
__asm__ volatile ( "syscall"
: "=a" (value)
: "a" (number), "D" (_1), "S" (_2), "d" (_3), "r" (r10), "r" (r8), "r" (r9)
: "rcx", "r11", "cc", "memory");
return value;
}
int main(void) {
static const char message[] = "It works!" "\n";
/* system_call(write, standard_output, ...); */
system_call(1, 1, message, sizeof message, 0, 0, 0);
return 0;
}
我运行了这个程序和verified,它确实将It works!\n 写入标准输出。这给我留下了以下问题:
- 为什么我可以传递比系统调用更多的参数?
- 这是合理的、有记录的行为吗?
- 我应该将未使用的寄存器设置为什么?
-
0好吗?
-
- 内核将如何处理它不使用的寄存器?
- 它会忽略它们吗?
- 七函数方法是否由于指令较少而更快?
- 这些函数中的其他寄存器会发生什么变化?
【问题讨论】:
-
如果您向
__syscall传递的参数多于系统调用所需的参数,它们将无用但无害复制到相应的寄存器中。syscall指令将控制权转移到内核,内核将控制权转移到系统调用实现的入口点。如果该实现不使用某些寄存器,它将假定它们是未使用的,就像它通常所做的那样,并忽略它们中保存的值(这又是 harmless)。相反,如果它完全使用它们,实现会将它们用作临时寄存器。
标签: linux-kernel operating-system x86-64 system-calls abi