系统调用是否直接发送到内核?

Are system calls directly send to the kernel?

我有几个假设,很可能其中一些是不正确的。不对的地方请指正

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

  1. 这些被发送到将它们翻译成多个标准 C 函数的库
  2. 库将它们传递给 libc,在那里它们被翻译成多个系统调用。
  3. Libc 将它们传递给执行它们的内核,并且 returns 被发送回 libc。
  4. Libc 将收集返回值,按 c 函数对它们进行分组,并使用它们为每个 c 函数创建 1 return。这些 return 将被发送回动态加载的库。
  5. 该库将收集所有 return 并使用它们创建 1 个 return 并发送回原始程序。
  1. 他们的程序已经翻译成已知的标准 C 函数,或者在其他情况下翻译成调用动态加载库的函数。
  2. 标准的 c 函数被发送到 libc,其他的被发送到动态加载的库(它们将在其中按上述方式处理)。
  3. 程序将根据来自两种类型函数的 return 创建 1 个最终 return

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

所有 POSIX 兼容的系统都遵循这些规则

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

在 Linux 上,ABI defines how a system call is done. Read about Linux kernel interfaces. The system calls are listed in syscalls(2) (but see also /usr/include/asm*/unistd.h ...). Read also vdso(7). The assembler HowTo 解释了更多细节,但仅适用于 32 位 i686。

大多数Linux libc都是免费软件,你可以研究他们的源代码。恕我直言,musl-libc 的源代码非常易读。

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

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

  2. 处理失败案例,将其传递给 errno(3) 并返回 -1
    (IIRC,失败时,当内核 returns 来自 SYSENTER 时,进位 - 或者可能是溢出 - 标志位被设置;但我在细节上可能是错误的)

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

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

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

一些系统调用(可能是 getpidclock_gettime)避免了 SYSENTER 机器指令(和用户模式 ​​-> 内核模式切换)的开销,这要归功于 vDSO.

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

POSIX 只字不提内核......它只是列出了一组具有最小语义的函数的 C(和以前的 ADA)API(加上一些命令,工具等)。实施这些是完全免费的。