【问题标题】:Are system calls directly send to the kernel?系统调用是否直接发送到内核?
【发布时间】:2018-02-13 12:37:59
【问题描述】:

我有几个假设,很可能其中一些是不正确的。请纠正我哪里错了。

我们可以对用 C 编写的程序中的函数进行如下分类:

  • 发送到动态加载的库的函数:
  1. 这些被发送到将它们转换为多个标准 C 函数的库
  2. 库将它们传递给 libc,在那里它们被转换为多个系统调用。
  3. Libc 将这些传递到内核并在其中执行它们,并将返回值发送回 libc。
  4. Libc 将收集返回值,将它们按 c 函数分组,并使用它们为每个 c 函数创建 1 个返回值。这些返回将被发送回动态加载的库。
  5. 此库将收集所有退货并使用它们创建 1 个退货,然后将其发送回原始程序。
  • 在代码中或静态编译库的一部分中定义的函数:一切都与上述类别相同,但:
  1. 他们的程序已经将它们转换为已知的标准 C 函数,或者在另一种情况下转换为调用动态加载的库的函数。
  2. 标准 c 函数被发送到 libc,其他函数被发送到动态加载的库(它们将按上述方式处理)。
  3. 程序将根据两种函数的返回创建 1 个最终返回
  • 作为标准 C 函数的函数:它们只会被发送到 libc,libc 将以与上述相同的方式与内核通信,并且将向程序发送 1 个返回

  • 作为系统调用的函数:它们不会直接发送到内核,而是必须传递给 libc,尽管它不会做任何额外的工作。

安全检查(权限、写入未分配的内存,...)总是由内核完成,尽管 libc 和上面的库也可能首先检查它。

所有符合 POSIX 的系统都遵循这些规则

【问题讨论】:

  • 有两个关键区别:(1)用户模式和内核模式之间是否存在转换(在我看来,系统调用的关键定义)和(2)是否不是被调用函数位于动态链接(共享)或静态链接库中。几乎所有其他事情都由此而来。也许还有一个区别(3)被调用的函数是“程序的一部分”还是“其他人的库”。也许还有一个区别(4)是否涉及vDSO。
  • 作为系统调用的函数 这在术语上是矛盾的。函数就是函数,系统调用就是系统调用。一些函数比其他函数更接近于系统调用 - 由于更改了*at() 接口,例如openat(),它可能没有你想象的那么接近。我不确定为什么在 C 中“函数”和“系统调用”之间存在这种区别。如果您正在编写 C,那么您正在使用函数。您几乎需要直接汇编程序来执行实际的系统调用。
  • 这是对事物进行分类的一个很好的尝试……但这是一个糟糕的问题。它从“不清楚你在问什么”(帖子中没有?)到“太宽泛”(Please correct me where they are wrong. 用于许多陈述)不等。您在标题中有一个确切的问题 - 发布问题是为了详细说明该问题,而不是介绍许多其他内容。

标签: c linux linux-kernel posix libc


【解决方案1】:

在 Linux 和其他一些 POSIX 系统(如 FreeBSD)上可能不一样。

在 Linux 上,ABI 定义了如何完成系统调用。阅读Linux kernel interfaces。系统调用在syscalls(2) 中列出(但另请参阅/usr/include/asm*/unistd.h ...)。另请阅读vdso(7)。 assembler HowTo 解释了更多细节,但仅适用于 32 位 i686。

大多数Linux libc 都是免费软件,您可以研究它们的源代码。恕我直言musl-libc的源代码可读性很强。

为了简化一点,大多数系统调用(例如write(2))都是libc 中的小型 C 函数,其中:

  1. 使用 SYSENTER 机器指令调用内核(并注意按照内核约定传递系统调用号及其参数,这不是通常的 C ABI)。内核认为系统调用只是该机器指令(以及关于它的约定)。

  2. 处理失败情况,将其传递给errno(3) 并返回-1。
    (IIRC,失败时,进位或溢出标志位在内核从SYSENTER返回;但我可能在细节上错了)

  3. 通过返回结果来处理成功案例。

您可以在没有libc 的情况下使用一些汇编代码来调用系统调用。这是不寻常的,但已经完成(例如在BusyBox 或Bones 中)。

所以write 的libc 代码正在做一些微小的额外工作(传递参数、处理失败和errno 和成功案例)。

由于vDSO,一些系统调用(可能是getpid 和clock_gettime)避免了SYSENTER 机器指令(和用户模式-> 内核模式切换)的开销。

【讨论】:

    【解决方案2】:

    不,你不能对这样的事情进行分类。当你用 C 编程时(但这在几乎所有其他语言中都没有区别),只有 函数 并且无论它们的真实状态是什么,你都以完全相同的方式调用它们。这由 ABI 定义(如何传递参数、获取返回值等)并由编译器/链接器强制执行。当然,有些函数只是存根。例如共享库函数的存根(存根可能需要加载库、动态链接到真实函数等)或系统调用(这更具技术性,并且因内核而异)。但是从你的程序的角度来看,一切都是一样的(这就是为什么一开始很难理解fread和read之间的区别:你用同样的方式称呼它们,它们几乎做同样的工作,有什么区别?)。

    POSIX 对内核只字未提...它只是列出了一组具有最少语义的函数(加上一些命令、工具等)的 C(以及以前的 ADA)API。这些实现完全免费。

    【讨论】:

      猜你喜欢
      • 2014-03-02
      • 2015-01-01
      • 1970-01-01
      • 2018-09-15
      • 2010-09-20
      • 2019-08-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多