AOSP 非显而易见的 syscall() 实现

AOSP non-obvious syscall() implementation

据我所知 Linux ARM 的 ABI 声明系统调用 return 值是通过 r0 传递的,如果它是负数,则应该将其作为 errno 值取反进行线程处理。即系统调用以一些错误结束。 AOSP does this check 以某种奇特的方式:

ENTRY(syscall)
    mov     ip, sp
    stmfd   sp!, {r4, r5, r6, r7}
    .cfi_def_cfa_offset 16
    .cfi_rel_offset r4, 0
    .cfi_rel_offset r5, 4
    .cfi_rel_offset r6, 8
    .cfi_rel_offset r7, 12
    mov     r7, r0
    mov     r0, r1
    mov     r1, r2
    mov     r2, r3
    ldmfd   ip, {r3, r4, r5, r6}
    swi     #0
    ldmfd   sp!, {r4, r5, r6, r7}
    .cfi_def_cfa_offset 0
    cmn     r0, #(MAX_ERRNO + 1) /* Set C flag if r0 is between -4095 and -1, set Z flag if r0 == -4096 */
    bxls    lr                   /* return if Z is set or C is clear */
    neg     r0, r0
    b       __set_errno_internal
END(syscall)

看起来此函数不会威胁 r0 < -4096 作为错误条件。虽然它应该。当然,真正的 errno 将适合允许的间隙,但无论如何,以这种方式执行检查而不是仅仅测试 r0 是否存在负面影响的理由是什么?

P.S。如果我遗漏了什么或者只是我的分析不正确 - 任何评论表示赞赏。

对 errno 值的 Apper 限制 4095 是有意的,在代码中使用这些知识是可以的。实际上,这修改了价值模型,由系统调用和一些其他内核函数返回,到:

  • [-4095 ... -1] - 错误
  • 其他值 - 结果

此模型允许更多可能的结果值。例如,如果将结果解释为无符号长整数,则结果的值范围从 [0... (MAX/2)] 扩展到 [0 ... (MAX - 4096)]。这很有用,例如当结果是指针时,请参阅 IS_ERR 宏的 implementation

通常情况下,mmap 的 return 值如果解释为有符号数,则为负数。

Linux的system-callABI/API就是这样设计的,只允许有限范围的-errno值,避免用完一半的范围一小组错误代码的可能 return 值。 (没有人需要超过 4095 个错误代码吗?)

正如@Timothy 指出的那样,mmap 是一个很好的例子:它需要 return 一个指针。 mmap 的 return 值始终为 page-aligned,因此在具有 4k 页或更大页面(即几乎(?)所有内容)的系统上,-4095 是最大负数您可以在不使用有效页面地址的情况下使用。

-4095 = 2^32 - 4095 在 32 位 2 的补码系统中。最高page-start地址是2^32-4096。这就是选择 this 范围作为“错误”范围的明显动机。

(但请注意,您仍然不能映射虚拟地址 space、even for a 32-bit process under a 64-bit kernel 的最高页,即使 -4096UL 将是 non-error return 来自 mmap。内核 内部 使用相同的 in-band -errno 指针代码机制,而不仅仅是 system-call ABI,因此它保留了首页,以防止出现同时也是错误代码的有效指针。这也避免了极端情况的可能性,例如指向数组 one-past-end 的指针为 0(NULL)。)


意味着错误的值的范围在理论上是 platform-dependent 和系统调用 ABI 的一部分,但 AFAIK Linux 在它支持的每个平台上使用 [-4095..-1]。非 x86 或 ARM-specific 的 C 源文件中的 MUSL libc 实现 hard-codes -4096UL/src/internal/syscall_ret.c.

正如@Tsyvarev 指出的那样,Linux 将 MAX_ERRNO 定义为 4095 in include/linux/err.h,而不是 arch-specific header. (一条评论指出,如果需要的话,它可以被制作成 arch-specific,并且它是根据内核指针的工作方式而特别选择的。)

x86-64 System V ABI 也有一个关于 Linux 系统调用的信息性(非规范性)部分,其中指定了 -4095-1 的范围。 (Search for -4095). (Other platforms using x86-64 System V can vary their syscall ABI, like FreeBSD 或 MacOS)


有趣的事实:这意味着 sys_getpriority 不能直接 return nice 级别。相反 it returns 20-nice, which maps the -20..19 range of nice values to 40..1。 libc 包装函数必须在通用 retval > -4096UL 检查检测到 non-error return 值后解码 nice 值。


有趣的事实 #2:某些 Linux 平台(例如 MIPS 和 PowerPC)上的系统调用 ABI returns -1 在 return 值和 errno 代码中在一个单独的寄存器中。资料来源:syscall inline-asm wrapper library 中的评论。 (看起来是一个不错的库;避免了某些系统调用包装器 header 所具有的缺失 "memory" 破坏等问题。)


相关: