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

Poo*_*jan 98 linux drivers kernel

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

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

Nli*_*tis 118

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

是的,是的。

在我们进一步讨论之前,我们应该先说明一下内存。

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

  • 用户空间,这是一组正常用户进程运行的位置(即除内核之外的所有内容)。内核的作用是管理在这个空间中运行的应用程序,以免相互干扰,以及机器。
  • 内核空间,它是内核代码存储和执行的位置。

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

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

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

对于这个问题的答案,请参考wag here的优秀答案

  • 如果我在某处犯了错误,请不要犹豫告诉我。我是内核编程的新手,我把到目前为止学到的知识以及我在网上找到的一些其他信息都倾倒在这里。这意味着我对文本中可能展示的概念的理解可能存在缺陷。 (5认同)

Cir*_*郝海东 25

CPU环是最明显的区别

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

  • 0 代表内核
  • 3 为用户

这是内核与用户空间的最严格和快速的定义。

为什么 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 芯片的物理构造使得:

  • ring 0 可以做任何事情

  • ring 3 不能运行多个指令并写入多个寄存器,最显着的是:

    • 不能换自己的戒指!否则,它可以将自己设置为环 0,而环将毫无用处。

      换句话说,不能修改当前段描述符,它决定了当前环。

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

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

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

    • 不能注册中断处理程序。这些是通过写入内存位置来配置的,这也可以通过分页来防止。

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

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

    • 不能执行in和这样的 IO 指令out,因此可以任意访问硬件。

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

      更准确地说,感谢Michael Petch:操作系统实际上可以允许环 3 上的 IO 指令,这实际上是由任务状态段控制的。

      如果环 3 首先没有它,那么它不可能允许自己这样做。

      Linux 总是不允许它。另见:https : //stackoverflow.com/questions/2711044/why-doesnt-linux-use-the-hardware-context-switch-via-the-tss

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

  • 当 CPU 打开时,它开始在环 0 中运行初始程序(很好,但它是一个很好的近似值)。您可以将这个初始程序视为内核(但它通常是一个引导加载程序,然后调用仍在 ring 0 中的内核)。

  • 当用户态进程希望内核为它做一些事情,比如写入文件时,它会使用一条指令来生成中断,例如int 0x80syscall向内核发出信号。x86-64 Linux 系统调用 hello world 示例:

    .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
    
    Run Code Online (Sandbox Code Playgroud)

    编译并运行:

    as -o hello_world.o hello_world.S
    ld -o hello_world.out hello_world.o
    ./hello_world.out
    
    Run Code Online (Sandbox Code Playgroud)

    GitHub 上游.

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

    这个处理程序在 ring 0 中运行,它决定内核是否允许这个动作,执行这个动作,并在 ring 3 中重新启动用户态程序。 x86_64

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

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

    但是由于用户空间是顽皮的,内核这次可能会杀死进程,或者给它一个信号警告。

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

    该硬件时钟生成运行 ring 0 的中断,并允许它安排唤醒哪些用户级进程。

    这样,即使进程没有进行任何系统调用,也可以进行调度。

拥有多个戒指有什么意义?

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

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

如何玩弄它?

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

不幸的是,我没有耐心制作用户空间示例,但我确实进行了分页设置,因此用户空间应该是可行的。我很想看到一个拉取请求。

或者,Linux 内核模块在 ring 0 中运行,因此您可以使用它们来尝试特权操作,例如读取控制寄存器:https : //stackoverflow.com/questions/7415515/how-to-access-the-control-registers -cr0-cr2-cr3-from-a-program-getting-segmenta/7419306#7419306

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

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

负环

虽然 Intel 手册中实际上没有引用负环,但实际上有 CPU 模式比环 0 本身具有更多功能,因此非常适合“负环”名称。

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

有关更多详细信息,请参阅:

手臂

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

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

  • EL0:用户空间

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

    svc指令(SuperVisor Call)一起输入,以前称为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
    
    Run Code Online (Sandbox Code Playgroud)

    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
    
    Run Code Online (Sandbox Code Playgroud)

    这是一个具体的裸机示例,它注册 SVC 处理程序并执行 SVC 调用

  • EL2:管理程序,例如Xen

    hvc指令输入(HyperVisor Call)。

    管理程序之于操作系统,就像操作系统之于用户空间。

    例如,Xen 允许您在同一系统上同时运行多个操作系统,例如 Linux 或 Windows,并且为了安全和易于调试,它将操作系统彼此隔离,就像 Linux 对用户程序所做的那样。

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

    例如,AWS 一直使用 Xen,直到 2017 年它转向 KVM 成为新闻

  • EL3:又一个层次。待办事项示例。

    smc指令输入(安全模式调用)

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

在此处输入图片说明

随着ARMv8.1 虚拟化主机扩展 (VHE)的出现,ARM 的情况发生了一些变化。这个扩展允许内核在 EL2 中高效运行:

在此处输入图片说明

VHE 的创建是因为 KVM 等 Linux 内核虚拟化解决方案已经超越 Xen(参见上文提到的 AWS 向 KVM 的迁移),因为大多数客户端只需要 Linux VM,并且您可以想象,所有这些都在一个在项目中,KVM 比 Xen 更简单,而且可能更高效。所以现在主机 Linux 内核在这些情况下充当管理程序。

请注意,ARM 可能是事后诸葛亮,在特权级别的命名约定比 x86 更好,而无需负级别:0 是较低的,3 是最高的。较高级别往往比较低级别更频繁地创建。

当前EL可以通过MRS指令查询: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-emulating-a53-power-up

在 Ubuntu 18.10 上测试的代码片段。

  • Ciro,你的回答应该获得一些奖牌!美丽的!太感谢了!!! (2认同)