用户空间和内核空间之间有什么区别?


72

当代表用户程序(即系统调用)执行内核时,是否使用了内核空间?还是所有内核线程(例如调度程序)的地址空间?

如果是第一个,则意味着普通用户程序不能拥有超过3GB的内存(如果划分为3GB + 1GB)?另外,在那种情况下内核如何使用高级内存,因为逻辑上将映射1GB内核空间,因此高级内存中的页面将映射到哪个虚拟内存地址?

Answers:


93

当代表用户程序(即系统调用)执行内核时,是否使用了内核空间?还是所有内核线程(例如调度程序)的地址空间?

是的,是的。

在继续之前,我们应该声明有关内存的信息。

内存获取分为两个不同的区域:

  • 用户空间,这是正常用户进程运行的一组位置(即除内核之外的所有内容)。内核的作用是管理在此空间中运行的应用程序,避免彼此和机器混乱。
  • 内核空间,它是存储内核代码并在其下执行的位置。

在用户空间下运行的进程只能访问内存的有限部分,而内核可以访问所有内存。在用户空间中运行的进程也无权访问内核空间。用户空间进程只能通过内核公开的接口(系统调用)访问内核的一小部分。如果进程执行系统调用,则会将软件中断发送到内核,然后内核将分派适当的中断处理程序,并在处理程序完成后继续其工作。

内核空间代码具有在“内核模式”下运行的属性,(在典型的台式机-x86-计算机上)是您在环0下执行的代码通常在x86架构中,有4个保护环。环0(内核模式),环1(可由虚拟机管理程序或驱动程序使用),环2(可由驱动程序使用,但是我不太确定)。环3是典型应用程序的运行环境。它是特权最小的环,并且在其上运行的应用程序可以访问处理器指令的子集。环0(内核空间)是特权最高的环,可以访问所有机器指令。例如,“普通”应用程序(如浏览器)不能使用x86汇编指令lgdt加载全局描述符表或hlt暂停处理器。

如果是第一个,则意味着普通用户程序不能拥有超过3GB的内存(如果划分为3GB + 1GB)?另外,在那种情况下内核如何使用高级内存,因为逻辑上将映射1GB内核空间,因此高级内存中的页面将映射到哪个虚拟内存地址?

有关此问题的答案,请在此处参考wag 的出色答案


4
如果我在某个地方犯了错误,请不要犹豫告诉我。我是内核编程的新手,我将到目前为止所学的知识以及在网络上找到的其他信息都转交给了我。这意味着我对文本中可能显示的概念的理解可能存在不足。
NlightNFotis

谢谢!我想现在我对它的理解更好了。为了确保我正确地获得它,我还有一个问题。再次考虑将前3GB用于用户空间,将128MB内核空间用于高级内存,剩余的896MB(低内存)在启动时是否静态映射?
Poojan

1
@NlightNFotis我说几乎有15个人认为您所说的都是正确的(或者您让我们思考;))
Braiam

我以为x86环-1适用于管理程序?en.wikipedia.org/wiki/Protection_ring
Dori

1
注意虚拟内存和物理内存之间的差异。您询问的大部分内容都与虚拟内存有关。这被映射到物理内存,当物理内存接近3GB并使用PAE时,这变得很复杂。然后,当使用64位内核时,它又变得简单了,在这种情况下,为内核保留了负地址,为用户空间保留了正地址。现在32位进程可以使用4GB的虚拟空间。64位进程可以使用更多的资源-通常价值48位(当前在x86-64上)。
ctrl-alt-delor

16

CPU环是最明显的区别

在x86保护模式下,CPU始终处于4个振铃之一。Linux内核仅使用0和3:

  • 0代表内核
  • 3个用户

这是内核vs用户态的最难,最快速的定义。

为什么Linux不使用环1和2:https//stackoverflow.com/questions/6710040/cpu-privilege-rings-why-rings-1-and-2-arent-used

如何确定当前环?

当前铃声是通过以下方式选择的:

  • 全局描述符表:GDT条目的内存表,每个条目都有一个Privl对环进行编码的字段。

    LGDT指令将地址设置为当前描述符表。

    另请参阅:http : //wiki.osdev.org/Global_Descriptor_Table

  • 段寄存器CS,DS等指向GDT中条目的索引。

    例如,CS = 0意味着GDT的第一个条目当前对于执行代码是活动的。

每个戒指能做什么?

CPU芯片的物理构造是:

  • 环0可以做什么

  • 环3无法运行多个指令并无法写入多个寄存器,最值得注意的是:

    • 不能改变自己的戒指!否则,它可能会将自己设置为环0,并且环将无用。

      换句话说,不能修改确定当前环的当前段描述符

    • 无法修改页表:https : //stackoverflow.com/questions/18431261/how-does-x86-paging-work

      换句话说,不能修改CR3寄存器,分页本身会阻止页表的修改。

      出于安全性/易于编程的原因,这可防止一个进程看到其他进程的内存。

    • 无法注册中断处理程序。通过写入存储位置来配置这些地址,这也可以通过分页来防止。

      处理程序在环0中运行,并且会破坏安全模型。

      换句话说,不能使用LGDT和LIDT指令。

    • 无法执行IO指令(如in和)out,因此可以进行任意硬件访问。

      否则,例如,如果任何程序可以直接从磁盘读取,则文件权限将无用。

      更确切地说,要感谢Michael Petch:操作系统实际上可能在环3上允许IO指令,这实际上是由Task状态段控制的。

      如果环3最初没有它,则无法授予自己这样做的权限。

      Linux总是不允许这样做。另请参阅:https : //stackoverflow.com/questions/2711044/why-doesnt-linux-use-the-hardware-context-switch-via-the-tss

程序和操作系统如何在环之间转换?

  • 当CPU开启时,它会在环0中开始运行初始程序(虽然不错,但是很不错)。您可以认为此初始程序是内核(但通常是引导加载程序,然后仍在环0中调用内核)。

  • 当用户级进程希望内核为其执行某些操作(例如写入文件)时,它将使用生成中断的指令,例如int 0x80syscall向内核发出信号。x86-64 Linux syscall你好世界示例:

    .data
    hello_world:
        .ascii "hello world\n"
        hello_world_len = . - hello_world
    .text
    .global _start
    _start:
        /* write */
        mov $1, %rax
        mov $1, %rdi
        mov $hello_world, %rsi
        mov $hello_world_len, %rdx
        syscall
    
        /* exit */
        mov $60, %rax
        mov $0, %rdi
        syscall
    

    编译并运行:

    as -o hello_world.o hello_world.S
    ld -o hello_world.out hello_world.o
    ./hello_world.out
    

    GitHub上游

    发生这种情况时,CPU会调用内核在启动时注册的中断回调处理程序。这是一个注册处理程序并使用它的裸机示例

    该处理程序在环0中运行,该环决定内核是否允许该操作,执行该操作并在环3中重新启动userland程序。x86_64

  • 当使用exec系统调用时(或内核将启动时/init),内核准备新的userland进程的寄存器和内存,然后跳转到入口点并将CPU切换到ring 3

  • 如果程序试图做一些顽皮的事情,例如写入禁止的寄存器或内存地址(由于页面调度),CPU还会在环0中调用一些内核回调处理程序。

    但是由于用户空间很顽皮,内核这次可能会终止进程,或者发出警告并带有信号。

  • 内核启动时,它将设置一个固定频率的硬件时钟,该时钟会定期产生中断。

    该硬件时钟生成运行于环0的中断,并允许其安排要唤醒的用户区进程。

    这样,即使进程未进行任何系统调用,调度也可能发生。

多环有什么意义?

分离内核和用户域有两个主要优点:

  • 可以更容易地制作程序,因为您可以确定一个程序不会干扰另一个程序。例如,一个用户态进程不必担心由于页面调度而覆盖另一程序的内存,也不必担心将硬件置于另一进程的无效状态。
  • 它更安全。例如,文件许可权和内存分离可能会阻止黑客应用读取您的银行数据。当然,这假定您信任内核。

怎么玩呢?

我创建了一个裸机设置,应该是直接操作环的好方法:https : //github.com/cirosantilli/x86-bare-metal-examples

不幸的是,我没有耐心举一个用户区示例,但是我确实进行了分页设置,因此用户区应该是可行的。我很乐意看到拉取要求。

另外,Linux内核模块在环0中运行,因此您可以使用它们来尝试特权操作,例如,读取控制寄存器:https : //stackoverflow.com/questions/7415515/how-to-access-the-control-registers程序获取段中的-cr0-cr2-cr3- / 7419306#7419306

这是一个方便的QEMU + Buildroot设置,可以在不杀死主机的情况下进行尝试。

内核模块的缺点是其他kthreads正在运行,并且可能会干扰您的实验。但是从理论上讲,您可以使用内核模块来接管所有的中断处理程序并拥有系统,这实际上是一个有趣的项目。

负环

尽管英特尔手册中并未实际提及负环,但实际上有一些CPU模式具有比环0本身更多的功能,因此非常适合“负环”名称。

一个示例是虚拟化中使用的管理程序模式。

有关更多详细信息,请参见:https : //security.stackexchange.com/questions/129098/what-is-protection-ring-1

在ARM中,环被称为“异常级别”,但主要思想保持不变。

ARMv8中存在4个异常级别,通常用作:

  • EL0:用户区

  • EL1:内核(ARM术语中的“主管”)。

    svc指令(SuperVisor调用)一起输入,该指令以前称为swi 统一汇编之前,它是用于进行Linux系统调用的指令。Hello world ARMv8示例:

    .text
    .global _start
    _start:
        /* write */
        mov x0, 1
        ldr x1, =msg
        ldr x2, =len
        mov x8, 64
        svc 0
    
        /* exit */
        mov x0, 0
        mov x8, 93
        svc 0
    msg:
        .ascii "hello syscall v8\n"
    len = . - msg
    

    GitHub上游

    在Ubuntu 16.04上使用QEMU进行测试:

    sudo apt-get install qemu-user gcc-arm-linux-gnueabihf
    arm-linux-gnueabihf-as -o hello.o hello.S
    arm-linux-gnueabihf-ld -o hello hello.o
    qemu-arm hello
    

    这是一个注册SVC处理程序并进行SVC调用的裸机示例。

  • EL2:系统管理程序,例如Xen

    hvc指令一起输入(HyperVisor调用)。

    虚拟机管理程序是针对操作系统的,就像操作系统是针对用户的。

    例如,Xen允许您在同一系统上同时运行多个操作系统(例如Linux或Windows),并且为了安全性和调试的方便性,它们相互隔离,就像Linux的用户界面程序一样。

    虚拟机管理程序是当今云基础架构的关键部分:它们允许多个服务器在单个硬件上运行,从而使硬件使用率始终接近100%,并节省了大量资金。

    例如,直到2017年AWS 迁移到KVM时才使用Xen 。

  • EL3:再上一层。TODO示例。

    输入smc说明(安全模式调用)

ARMv8架构参考模型DDI 0487C.a - D1章-的AArch64系统级编程模型-图D1-1说明了这美丽的:

在此处输入图片说明

请注意,也许是由于事后观察的好处,ARM如何比x86具有更好的特权级别命名约定,而又不需要否定级别:0为最低,3为最高。较高的级别往往比较低的级别创建的频率更高。

可以使用以下MRS指令查询当前的EL :https : //stackoverflow.com/questions/31787617/what-is-the-current-execution-mode-exception-level-etc

ARM不需要提供所有异常级别,以实现不需要该功能以节省芯片面积的实现。ARMv8“异常级别”说:

一个实现可能不包括所有的异常级别。所有实现都必须包括EL0和EL1。EL2和EL3是可选的。

例如,QEMU默认为EL1,但是可以使用命令行选项启用EL2和EL3:https : //stackoverflow.com/questions/42824706/qemu-system-aarch64-entering-el1-when-emulation-a53-power-up

代码片段已在Ubuntu 18.10上进行了测试。


3

如果是第一个,则意味着普通用户程序不能拥有超过3GB的内存(如果划分为3GB + 1GB)?

是的,在普通的linux系统上就是这种情况。有一组“ 4G / 4G”补丁在某个点上浮动,使用户和内核地址空间完全独立(以性能为代价,因为这使内核更难访问用户内存),但我认为它们曾经被合并到上游,并且随着x86-64的兴起,兴趣减弱了

另外,在那种情况下内核如何使用高级内存,因为逻辑上将映射1GB内核空间,因此高级内存中的页面将映射到哪个虚拟内存地址?

linux过去的工作方式(在与地址空间相比内存较小的系统上仍然有效)是将整个物理内存永久映射到地址空间的内核部分。这使内核无需重新映射即可访问所有物理内存,但显然它无法扩展到具有大量物理内存的32位计算机。

因此,低内存和高内存的概念诞生了。“低”内存被永久映射到内核地址空间。“高”内存不是。

当处理器运行系统调用时,它以内核模式运行,但仍在当前进程的上下文中。因此,它可以直接访问当前进程的内核地址空间和用户地址空间(假设您未使用上述4G / 4G补丁程序)。这意味着将“高”内存分配给用户态进程没有问题。

使用“高”内存用于内核是一个更大的问题。要访问未映射到当前进程的高内存,必须将其暂时映射到内核的地址空间。这意味着额外的代码和性能损失。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.