【问题标题】:How does the Linux kernel "listen" to the C library?Linux 内核如何“监听”C 库?
【发布时间】:2015-07-08 17:02:37
【问题描述】:

我正在尝试构建一个关于 Linux 内核和用户空间中的工作方式的“大图”,但我很困惑。 我知道用户空间利用系统调用与内核“对话”,但我不知道如何。我尝试阅读 C 库和内核源代码,但它们很复杂且不易理解。我还阅读了几本有关操作系统概念性事实的书籍,例如管理进程、内存、设备,但它们并没有使“转换”(用户空间->内核)清晰。 那么,用户空间和内核空间之间的转换究竟发生在哪里呢? C 库如何运行机器中运行的 Linux 内核中的代码?

打个比方:想象有一座房子。房子上锁了。打开房子的钥匙就在房子里面。屋子里只有一个人,内核。用户空间是试图进入房屋的人。我的问题是:内核如何知道屋外有人想要钥匙,以及哪种机制允许用该钥匙打开房屋?

【问题讨论】:

  • 他们使用门铃。这个问题取决于平台。在x86-64 上,内核设置处理器将syscalls 分派给内核。 syscall 是一个汇编指令,在类比中充当门铃。
  • “内核设置处理器以向内核分派系统调用”是什么意思?
  • 一般来说,内核通过将值写入特定寄存器和内存位置来配置处理器。一旦内核在启动时加载,这是合理的。处理器使用此信息来处理syscall 指令和许多其他内核预期的东西。
  • 一个related question on *syscalls and the 'C' library。你也可以看看somesource。根据下面的答案,调用将不是纯“C”,但至少需要一些编译器扩展或汇编程序。 细节是“平台相关的”,但用户空间和内核之间的访问保护必须提供syscall 类似物。

标签: c linux-kernel system-calls userspace


【解决方案1】:

这很简单——人们可以使用门铃让内核知道它在外面等着。而在我们的案例中,这个门铃通常是一个特殊的 CPU 异常、软件中断或允许用户空间应用程序使用并且内核可以处理的专用指令。

所以程序是这样的:

  1. 首先您需要知道系统调用号。每个系统调用都有其唯一的编号,并且内核内部有一个表格,将这些编号映射到特定的函数。每个体系结构对于相同的编号可以有不同的表条目。在两种不同的架构上,相同的编号可能会映射到不同的系统调用。

  2. 然后你设置你的论点。这也是特定于体系结构的,但与在通常的函数调用之间传递参数没有太大区别。通常,您会将参数放在特定的 CPU 寄存器中。此架构的 ABI 对此进行了描述。

  3. 然后你进入系统调用。根据架构,这可能意味着导致一些异常或执行专用 CPU 指令。

  4. 内核具有特殊的处理函数,当调用系统调用时,该函数在内核模式下运行。它将暂停进程执行,存储特定于该进程的所有信息(称为context switch),读取系统调用号和参数并调用正确的系统调用例程。它还将确保将返回值放在适当的位置以供用户空间读取,并在系统调用例程完成时安排进程返回(恢复其上下文)。

例如,要让内核知道您想在 x86_64 上调用 syscall,您可以使用 sysenter 指令和 %rax 寄存器中的 syscall 号。使用寄存器传递参数(如果我没记错的话)%rdi%rsi%rdx%rcx%r8%r9

您还可以使用在 32 位 x86 CPU 上使用的旧方法 - 软件中断号 0x80(int 0x80 指令)。同样,系统调用号在%rax 寄存器中指定,参数转到(再次,如果我没记错的话)%ebx%ecx%edx%esi%edi%ebp

ARM 非常相似 - 您将使用“主管调用”指令 (SVC #0)。您的系统调用号将转到r7 寄存器,所有参数将转到寄存器r0-r6,系统调用的返回值将存储在r0

其他架构和操作系统使用类似的技术。细节可能不同 - 软件中断号可能不同,参数可能使用不同的寄存器甚至使用堆栈传递,但核心思想是相同的。

【讨论】:

  • 这些寄存器是虚拟的还是物理的? (内核为每个进程创建虚拟内存,寄存器也是虚拟的吗?还是C库在设置系统调用时直接写入处理器?)
  • @yuri:我不知道你说的虚拟寄存器是什么意思。用户空间总是使用真正的 CPU 寄存器。系统中可能有多个进程,但当时每个 CPU 上只能运行一个。如果此过程暂停,则会发生上下文切换并将所有寄存器值保存到内存中。当再次安排进程时,它们会被恢复。这就是寄存器永远不会被不同进程覆盖的原因,即使它们都共享它们。
【解决方案2】:

许多处理器都有调用特定“陷阱”或“中断”的指令,Linux 内核专门为系统调用设置了这样的“陷阱”或“中断”。

该库以某种方式设置处理器寄存器,然后执行特殊的陷阱或中断指令,使处理器进入特权模式并调用内核的陷阱/中断处理函数,该函数将寄存器中的值解码并调用相应的函数来处理系统调用。

这是最常见的方式,基本上它适用于几乎所有需要在内核和用户空间之间隔离的系统。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-06
    • 2015-12-14
    • 2017-08-18
    • 2017-04-08
    • 2015-02-04
    相关资源
    最近更新 更多