Linux用户虚拟地址和内核虚拟地址的关系

知乎larmbr宇的回答

  1. 现在 Linux 内核是4级页表结构,3级页表的时代是10年前了。 X86_64 架构下,无论 Intel 还是 AMD 的 CPU, 都是四级的硬件页表,所以软件层面的页表至少要4级(否则,进程访问的空间将受限, 因为有一级页表被固定住了,所以3级页表时代,X86_64 只能访问 512GB 空间, 而 X86_64 的设计可访问空间达到 131 072( = 2^47) GB。打个比方就是,省,市,区,县 四级行政规划,硬要嵌套进三级规划,只能表达市,区,县三级,省一级给固定住了, 访问范围缩小了)。

  2. 不过你会问:i386 只有三级硬件页表:PUD -> PMD -> PTE, 怎么嵌入四级软件页表结构? 答案就是虚设一层。打个比方:北京是省级行政单位,如果要按省,市,区,县结构来表达某县,就是: 北京(省)北京(市)XX区XX县, 有一层完全就是占个位而已。有兴趣了解 Linux 页表的变迁历史,可以参考这篇文章:Linux内核4级页表的演进

  3. 内核空间,用户空间的地址都是虚拟地址,都要经过 MMU 的翻译,变成物理地址。用户空间的虚拟地址,就是按前面所述的走四级页表来翻译。 内核空间虚拟地址是所有进程共享的,重要的是,从效率角度看, 如果同样走四级页表翻译的流程,速度太慢;于是,内核在初始化时,就创建内核空间的映射(因为所有进程共享,有一份就够了),并且,采用的就是线性映射,而不是走页表翻译这种类似哈希表的方式。这样,内核地址的翻译,简化为一条偏移加减指令就行,相比走页表,效率大大提高(不过,内核空间并非完全不用页表,此处讲原理所以简化,详细的看尾注).

  4. 至于为什么用户空间不能也像内核空间这么做,原因是用户地址空间是随进程创建才产生的,它的页面可能散布在不同的物理内存中,无法这么做。另外,走页表的过程,不止是翻译的过程,还是一个权限检查的过程,对于不可控的用户态地址,这安全性检查必不可省。而内核空间,只有一份,所有可以提前固定下来一片连续的物理地址空间,按线性方法来映射。这是很正常的优化方法。

  5. 那么问题来了,在 Linux 刚引入的时候, i386 4G 的进程空间典型的是 3G user + 1G kernel 的划分,这教科书上都有说。 那按前面的线性方法, 1G 内核空间,只能映射 1G 物理地址空间,这对内核来说,太掣肘了。所以,折衷方案是, Linux 内核只对 1G 内核空间的前 896 MB 按前面所说的方法线性映射, 剩下的 128 MB 的内核空间, 采用动态映射[1]的方式,即按需映射的方式 ,这样,内核态的访问空间更多了。到了 64 位时代, 内核空间大大增大, 这种限制就没了,内核空间可以完全进行线性映射,不过,基于[1]的缘故, 仍保留有动态映射这部分。

[1] 动态映射不全是为了内核空间可以访问更多的物理内存,还有一个重要原因: 当内核需要连续多页面的空间时,如果内核空间全线性映射,那么,可能会出现内核空间碎片化而满足不了这么多连续页面分配的需求。基于此,内核空间也必须有一部分是非线性映射,从而在这碎片化物理地址空间上,用页表构造连续虚拟地址空间,这就是所谓vmalloc空间。

The kernel virtual memory area in x64 Linux

在课本上介绍的内核的地址空间都为1GB,然而这是在x86Linux上的情况。在x64Linux上内核虚拟地址空间又为多少呢?下面引用quora上的回答:

Linux for AMD64 (x86_64) uses a 48-bit (“canonical mode”) address space which is divided into two regions. The kernel’s virtual address space is 128TB and the user virtual address space is also 128TB (thus the two together are a 48-bit address space, though that is NOT contiguous).


参考资料:

  1. 知乎 larmbr宇
  2. quora Jim Dennis