【问题标题】:How do operating systems allow userspace programs to interact with kernelspace programs?操作系统如何允许用户空间程序与内核空间程序交互?
【发布时间】:2016-12-19 13:03:37
【问题描述】:

这不是一个关于特定操作系统的问题,但让我们以 Windows 为例。用户空间程序使用 Windows API 与内核空间进行通信。但是,我不明白这怎么可能。根据 MS 网站的说法,该 API 存在于用户空间中。如果我理解正确的话,为了访问内核空间,它必须在内核空间中。那么,Windows API 获得与内核空间对话的额外特权的机制是什么?该机制在哪个空间运行?这种东西是否适用于所有现代 PC 操作系统?

【问题讨论】:

  • 查找“系统调用”。
  • 您好,我知道该机制被称为系统调用。我的问题与操作系统如何允许信息在两个内存空间之间传输有关。系统调用大概是一个函数,它被编译成在用户空间运行的程序。它不应该访问内核空间。为什么会这样?例如,是否存在第三个中间空间?
  • @MichaelStachowsky 如有任何疑问。

标签: operating-system


【解决方案1】:

正如您已经知道的那样,Windows 内核向用户空间程序公开了许多设施。 (如果你好奇there's a list of system calls)。这些系统调用都由一个唯一编号标识,该编号不是 Microsoft 提供的公开文档接口的一部分。相反,当您从程序中调用公开公开的函数时,会在安装(或更新)Windows 时安装一个 DLL,该 DLL 的入口点只是一个普通的非特权用户模式函数调用。这个 DLL 知道公共接口和当前运行内核中可用系统调用之间的映射。这些映射并不总是 1:1,这允许在不破坏现有代码的情况下使用稳定的接口进行调整和增强。

当一些用户态代码调用这些函数之一时,它的作用是为系统调用准备参数,然后启动跳转到内核模式。这种跳跃的具体发生方式取决于 Windows 当前运行的体系结构。事实上,它不仅在 x86 和 Arm 之间存在差异,甚至在 AMD 和 Intel x86 系统之间也存在差异。为简单起见,我将在这里只讨论现代 Intel x86 32 位机箱(使用SYSENTER instruction)。在 x86 上,大多数其他变体都相对较小,例如 int 2Eh was used prior to SYSENTER support。

在启动初期,操作系统会做大量工作来准备启用用户空间和系统调用。了解这一点对于了解系统调用的实际工作方式至关重要。

首先让我们稍微回顾一下,考虑一下我们所说的用户态和内核模式到底是什么意思。在 x86 上,当我们谈论特权代码与非特权代码时,我们谈论的是“环”。实际上有 4 个(忽略管理程序),但由于各种原因,除了 ring0(内核)和 ring3(用户空间)之外,没有人真正使用过任何东西。当我们在 x86 上运行代码时,正在执行的地址 (EIP) 和正在读取/写入的数据来自段。

段大多只是在 x86 上的虚拟寻址成为事物之前的日子遗留下来的历史事故。然而,它们在这里对我们很重要,因为有一些特殊的寄存器可以定义当我们执行指令或引用内存时当前正在使用哪些段。 x86 上的段都定义在一个大表中,称为全局描述符表或GDT。 (还有一个本地描述符表,LDT,但这里不会进一步讨论当前的讨论)。我们在这里讨论的重点是表条目的(神秘)布局包括 2 位,称为 DPL,它定义了当前活动段的特权级别。您会注意到 2 位正好足以定义 4 级权限。

所以简而言之,当我们谈论“在内核模式下执行”时,我们实际上只是指我们的活动代码段 (CS) 和数据段选择器指向 GDT 中 DPL 设置为 0 的条目。同样对于用户态,我们有a CS 和数据段选择器指向 DPL 设置为 3 且无法访问内核地址的 GDT 条目。 (还有其他选择器,但为了简单起见,我们现在只考虑“代码”和“数据”)。

回到内核启动期间的早期:在启动期间内核创建GDT entries we need。 (这些必须按特定顺序排列,SYSENTER 才能工作,但这主要只是一个实现细节)。还有一些“机器特定的寄存器”可以控制我们的处理器的行为方式。这些只能由特权代码设置。其中三个重要的是:

  • IA32_SYSENTER_ESP
  • IA32_SYSENTER_EIP
  • IA32_SYSENTER_CS

回想一下,我们在用户空间 (ring3) 中运行了一些想要转换到 ring0 的代码。让我们假设它已经根据调用约定保存了它需要的所有寄存器,并将参数放入调用期望的正确寄存器中。然后我们点击 SYSENTER 指令。 (实际上我认为它使用KiFastSystemCall)。 SYSENTER 指令是特殊的。它根据内核在机器特定寄存器 IA32_SYSENTER_CS 中设置的值修改当前代码和数据段选择器。 (堆栈/数据段值计算为 IA32_SYSENTER_CS 的偏移量)。随后,堆栈指针本身 (ESP) 被设置为内核堆栈,该堆栈之前为处理系统调用而设置并保存到 MSR IA32_SYSENTER_ESP 中,同样对于 EIP,来自 IA32_SYSENTER_EIP 的指令指针。

由于 CS 选择器现在指向 DPL 设置为 0 的 GDT 条目,而 EIP 指向内核堆栈上的内核模式代码,我们此时正在内核中运行。

从这里开始,内核模式代码可以从内核和用户空间读取和写入内存(需要适当注意),以承担执行系统调用所需的实际工作。系统调用的参数可以根据调用约定从寄存器等中读取,但任何实际上是返回用户空间的指针或内核对象句柄的参数也可以访问以读取更大的数据块。

当系统调用结束时,这个过程基本上是相反的,我们最终回到用户态,选择器使用 DPL 3。

【讨论】:

    【解决方案2】:

    它的 CPU 作为中间体,通过 CPU 寄存器在用户内存空间(在用户模式下可访问)和受保护的内存空间(在内核模式下可访问)之间传输信息。

    这是一个例子:

    假设用户用高级语言编写程序。现在当程序执行时,CPU 会生成虚拟地址。

    现在在任何读/写操作发生之前,虚拟地址被转换为物理地址。因为翻译机制(内存管理单元),只能在内核模式下访问,导致其存储在受保护的内存中,翻译发生在内核模式,物理地址最终保存到CPU的某个寄存器中,然后才读/发生写操作。

    【讨论】:

    • 我明白了。但是,沟通又是如何进行的呢?我知道内核空间程序可以与用户空间通信。寄存器不受保护吗?如果没有可用怎么办?
    • 通用寄存器不受保护,它们是供程序使用的。实际上,操作系统每次切换任务时,都会保存旧任务的寄存器,并恢复新任务的寄存器。此外,不允许程序使用寄存器将无法将值从一个内存位置移动到另一个位置(mov [loc2], [loc1] 无效),同时使一切变得更慢(程序几乎每次操作都需要访问内存)。
    • 这个答案忽略了实际使用的系统调用机制,并且在描述使用寄存器传输数据或 MMU 的操作方面并不是很准确,这与问题并不特别相关。跨度>
    猜你喜欢
    • 1970-01-01
    • 2013-02-23
    • 1970-01-01
    • 2011-10-24
    • 2016-07-19
    • 2011-12-30
    • 1970-01-01
    • 1970-01-01
    • 2012-09-30
    相关资源
    最近更新 更多