是的,内核有自己的堆栈、堆、数据结构和代码,独立于每个用户进程。
在内核中运行的代码本身并不被视为“进程”。代码是特权,这意味着它可以修改内核中的任何数据,在处理器寄存器中设置特权位,发送中断,与设备交互,执行特权指令等。它不像代码中的代码那样受到限制用户进程。
所有内核内存和用户进程内存都存储在计算机的物理内存中(如果数据已从内存中交换,则可能存储在磁盘上)。
回答其余问题的关键是了解物理内存和虚拟内存之间的区别。请记住,如果您使用虚拟内存地址来访问数据,则在从确定的物理地址获取数据之前,该虚拟地址会被转换为物理地址。
每个进程都有自己的虚拟地址空间。这意味着一个进程中的某个虚拟地址 a 可以映射到与另一个进程中的相同虚拟地址 a 不同的物理地址。虚拟内存有很多重要的用途,但我不打算在这里讨论它们。重要的一点是虚拟内存强制执行内存隔离。这意味着进程 A 无法访问进程 B 的内存。进程 A 的所有虚拟地址都映射到一组物理地址,而进程 B 的所有虚拟地址都映射到另一组物理地址。只要两组物理地址不重叠,进程就无法看到或修改彼此的内存。用户进程不能直接访问物理内存地址——它们只能使用虚拟地址进行内存访问。
有时两个进程可能有一些虚拟地址映射到相同的物理地址,例如如果它们都映射同一个文件,都使用共享库等。
现在回答您关于内核地址空间和用户地址空间的问题。
内核可以有一个独立于每个用户进程的虚拟地址空间。这就像在每次上下文切换时更改 cr3 寄存器(在 x86 处理器中)中的页面目录指针一样简单。由于内核具有不同的虚拟地址空间,因此只要内核的虚拟内存地址都没有映射到与用户进程的任何地址空间中的任何虚拟地址相同的物理地址,任何用户进程都无法访问内核内存。
这可能会导致一个小问题。如果用户进程进行系统调用并将指针作为参数传递(例如,read 系统调用中指向缓冲区的指针),内核如何知道哪个物理地址对应于该缓冲区?指针中的虚拟地址映射到内核空间中的不同物理地址,因此内核不能仅仅取消引用指针。有两种选择:
内核可以遍历用户进程页目录/表来找到缓冲区对应的物理地址。然后内核可以读取/写入该物理地址。
内核可以将其所有映射包含在用户地址空间中(如您所述,位于用户地址空间的顶部)。现在,当内核通过系统调用接收到一个指针时,它可以直接访问指针,因为它与进程共享地址空间。
内核通常采用第二种选择,因为它更方便、更高效。选项 1 效率较低,因为每次发生上下文切换时,地址空间都会发生变化,因此需要刷新 TLB,现在您会丢失所有缓存的映射。由于最近发现的 Meltdown 漏洞,内核已经开始以不同的方式做事,所以我在这里简化了一些事情。
这会导致另一个问题。如果内核在用户进程地址空间中包含它的映射,是什么阻止用户进程访问内核内存?内核在页表中设置保护位,导致处理器禁止用户进程访问映射到包含内核内存的物理地址的虚拟地址。
查看这些slides 了解更多信息。