Random Names
根据您所处的环境,系统调用名称可能使用略有不同的命名约定。
内核头文件(例如 asm/unistd.h)使用 NR_xxx 之类的名称,但不提供任何其他实用程序代码。 C 库头文件(例如 syscall.h 和 sys/syscall.h)使用 SYS_xxx 之类的名称,目的是将它们与 syscall(…) 一起使用。这些定义将完全相同——因此 NR_foo 将与 SYS_foo 具有相同的值。
开发人员选择使用哪一个有点武断,而且很可能掩盖了他们的背景(内核开发人员倾向于 __NR_xxx)。如果您正在编写用户级 C 代码和 syscall(…) 函数,您可能应该坚持使用 SYS_xxx。
简称“NR”本身就是指“数字”。您可能会看到“系统调用号”和“NR”以及“系统调用 NR”交替使用。
另一个有趣的区别是,某些架构命名空间系统调用以某种方式特定于其端口。或者他们没有。这是不同内核维护者做出不同选择的另一种情况,并且没有一个中央权威来注意到和执行事情。例如,ARM 有一些 ARM_NR_xxx 系统调用,他们认为它们是“私有的”,并且他们有一些 NR_arm_xxx 系统调用来表明他们对 xxx 系统调用有一个自定义包装器!
要记住的另一个边缘情况是不同的架构可能对不同的入口点使用相同的名称。这并不常见,但在查看带有变体的系统调用时可能会出现。例如,较旧的架构端口可能具有 setuid 和 setuid32,而较新的端口可能只有 setuid。旧端口的 setuid 采用 16 位参数,而新端口的 setuid 采用 32 位参数,等效于 setuid32。与 statfs 和 statfs64 等文件系统调用有很多相似之处。
内核实现
内核源代码的 cs/ 链接主要是尽力而为。它们专注于公共 C 入口点,但根据您的执行环境,这可能不是内核执行的第一个位置。不幸的是,它们目前只是 Google 内部的,因为我们还没有找到任何好的公共索引来代替。
每个架构都可以将系统调用从其初始入口点指向自定义蹦床代码。例如,ARM 的 fstatfs64 实现在 sys_fstatfs64_wrapper 中开始执行,它作为汇编代码位于 arch/arm/ 下。这反过来调用 sys_fstatfs64,它是公共 fs/ 树中的 C 代码。通常这些蹦床并不复杂,但它们可能会为整体执行添加额外的检查。如果您看到与 C 代码相关的令人困惑的行为,您可能想要深入研究。
在 64 位内核上使用 32 位 ABI 时,您可能会遇到试图混合结构的系统调用兼容层。这在用户空间为 32 位但内核为 64 位的 x86 和 ARM 系统上经常出现。这些将使用像 compat_sys_xxx 而不是 sys_xxx 和 COMPAT_SYSCALL_XXX 包装器而不是 SYSCALL_XXX 这样的约定。它们负责从用户态获取 32 位结构,将它们转换为内核使用的 64 位结构,然后调用 64 位 C 代码。通常这种转换不是问题,但如果代码检测到数据结构有问题,它会在系统调用的通用实现执行之前出错。
Android/ARC++ 容器在 alt-syscall 层下执行。这允许定义自定义系统调用表,以便硬禁用所有进程的任何系统调用(无需 seccomp),或者为公共实现的入口/出口点添加额外检查,或者不管任何参数都将事物存根。所有这些代码都将在系统调用的通用实现执行之前运行。由于这些表是在我们的内核(而不是上游)中手动维护的,新的系统调用不会自动添加到白名单中,因此您可能会看到像 ENOSYS 这样的令人困惑的错误,但只有在容器内部运行时才会出现。如果您看到不当行为,您应该检查是否为该进程启用了 alt-syscall,如果是,请查看 security/chromiumos/ 下的包装器。
X86-64 syscall table
Compiled from Linux 4.14.0 headers.
X86 syscall table
Compiled from Linux 4.14.0 headers.