10

我对内核和用户空间的结构以及占用的内存部分感到困惑。我目前(可能是错误的)理解是这样的:

  1. 创建一个进程,并将该进程的虚拟内存分为用户空间和内核空间区域,其中用户空间区域包含进程的数据、代码、堆栈、堆等和内核空间区域包含诸如进程和内核代码的页表之类的东西。我不确定内核代码是什么......驱动程序代码或类似的东西?

  2. 此外,系统调用表是否总是映射到进程内核空间中的同一区域?(说“进程的内核空间”是否正确?

  3. 如果我编写自己的驱动程序/模块并插入它,那么驱动程序代码是否会自动复制到创建的每个新进程的内核空间中?如果不是......这究竟是如何工作的?

提前感谢您的任何输入,可以帮助澄清我的问题的文献/链接也可以。

干杯,砖

4

2 回答 2

32

您的总体思路基本上是正确的,但是进行此调整:整台机器只有一个“内核空间”,所有进程共享它。

当一个进程处于活动状态时,它可以在“用户模式”或“内核模式”下运行。

在用户模式下,CPU 正在执行的指令位于内存映射的用户空间一侧。该程序正在运行它自己的代码,或者来自用户空间库的代码。在用户模式下,进程的能力有限。CPU中有一个标志告诉它不允许使用特权指令,并且内核内存虽然存在于进程的内存映射中,但不可访问。(你不会想让任何程序只读写内核的内存——所有的安全性都会消失。)

当一个进程想要做一些事情而不是在它自己的(用户空间)虚拟内存中移动数据时,例如打开一个文件,它必须进行系统调用。每个 CPU 架构都有自己独特的系统调用的古怪方法,但它们都归结为:执行一条神奇的指令,CPU 打开“特权模式”标志,并跳转到内核空间中的一个特殊地址,即“系统调用”入口点”。

现在该进程正在内核模式下运行。正在执行的指令位于内核内存中,它们可以读写任何想要的内存。内核检查进程刚刚发出的请求并决定如何处理它。

open示例中,内核接收 2 或 3 个参数对应于int open(const char *filename, int flags[, int mode]). 第一个参数提供了内核空间何时需要访问用户空间的示例。您说过open("foo", O_RDONLY),字符串"foo"是用户空间中程序的一部分。系统调用机制只传递了一个指针,而不是字符串,因此内核必须从用户内存中读取字符串。

为了找到请求的文件,内核可能会咨询文件系统驱动程序(以确定文件的位置)和块设备驱动程序(从磁盘加载必要的块)或网络设备驱动程序和协议(从远程源加载文件)。所有这些都是内核的一部分,即在内核空间中,无论它们是内置的还是作为模块加载的。

如果请求不能立即得到满足,内核可能会使进程进入睡眠状态。这意味着该进程将从 CPU 中取出,直到从磁盘或网络收到响应。另一个进程现在可能有机会运行。稍后,当响应到来时,您的进程再次开始运行(仍处于内核模式)。现在它找到了文件,open系统调用可以完成(检查权限,创建文件描述符)并返回用户空间。

返回用户空间是一件简单的事情,只需将 CPU 重新置于非特权模式并将寄存器恢复到用户->内核转换之前的状态,指令指针指向魔法系统调用指令之后的指令。

除了系统调用之外,还有其他一些事情会导致从用户模式到内核模式的转换,包括:

  1. 页面错误 - 如果您的进程访问没有分配物理地址的虚拟内存地址,则 CPU 进入内核模式并跳转到页面错误处理程序。然后内核决定虚拟地址是否有效,它要么创建一个物理页面并在用户空间中恢复它停止的进程,要么发送一个 SIGSEGV。
  2. 中断 - 某些硬件(网络、磁盘、串行端口等)通知 CPU 需要注意。CPU 进入内核模式并跳转到处理程序,内核对其作出响应,然后恢复中断前正在运行的用户空间进程。

加载模块是通过系统调用完成的,系统调用要求内核将模块的代码和数据复制到内核空间并在内核模式下运行其初始化代码。

这很长,所以我停下来。我希望专注于用户内核转换的演练已经提供了足够的例子来巩固这个想法。

于 2013-06-21T03:16:03.643 回答
3

进程的虚拟内存映射中没有内核空间区域。虚拟内存映射有:文本、bss、数据、堆、加载程序的堆栈和共享库。在 Linux 上,您可以检查 /proc/$PID/maps 的任何用户空间进程作为示例。

当用户空间进程通过系统调用访问某些内核域代码时,内核代码在其堆栈中代表该进程执行。显然,从系统调用返回后,所有内核/驱动程序代码都将出栈。为了进一步澄清,如果某一时刻内核代码的某些部分没有被任何进程使用,那么它将不会成为任何进程的虚拟内存映射的一部分。

如果您使用的是 Linux,我会推荐 Robert Love 的“Linux 内核开发”一书。

于 2013-06-20T20:36:01.480 回答