2

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

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

4

2 回答 2

12

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

所以程序是这样的:

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

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

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

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

例如,要让内核知道您想在 x86_64 上调用 syscall,您可以在寄存器中使用sysenter带有 syscall 编号的指令。%rax使用寄存器传递参数(如果我没记错的话)%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.

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

于 2015-04-29T12:21:44.993 回答
1

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

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

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

于 2015-04-29T12:11:40.897 回答