一步一图带你深入理解 Linux 虚拟内存管理( 五 )


在内核中使用 start_stack 标识栈的起始位置,RSP 寄存器中保存栈顶指针 stack pointer,RBP 寄存器中保存的是栈基地址 。
在栈空间的下边也有一段待分配区域用于扩展栈空间,在栈空间的上边就是内核空间了 , 进程虽然可以看到这段内核空间地址,但是就是不能访问 。这就好比我们在饭店里虽然可以看到厨房在哪里,但是厨房门上写着 “厨房重地,闲人免进”,我们就是进不去 。

一步一图带你深入理解 Linux 虚拟内存管理

文章插图
4.2 64 位机器上进程虚拟内存空间分布上小节中介绍的 32 位虚拟内存空间布局和本小节即将要介绍的 64 位虚拟内存空间布局都可以通过 cat /proc/pid/maps 或者 pmap pid 来查看某个进程的实际虚拟内存布局 。
我们知道在 32 位机器上,指针的寻址范围为 2^32,所能表达的虚拟内存空间为 4 GB 。
那么我们理所应当的会认为在 64 位机器上,指针的寻址范围为 2^64,所能表达的虚拟内存空间为 16 EB。虚拟内存地址范围为:0x0000 0000 0000 0000 0000 - 0xFFFF FFFF FFFF FFFF。
好家伙 !!! 16 EB 的内存空间 , 笔者都没见过这么大的磁盘,在现实情况中根本不会用到这么大范围的内存空间,
事实上在目前的 64 位系统下只使用了 48 位来描述虚拟内存空间,寻址范围为2^48 ,所能表达的虚拟内存空间为 256TB 。
其中低 128 T 表示用户态虚拟内存空间 , 虚拟内存地址范围为:0x0000 0000 0000 0000- 0x0000 7FFF FFFF F000。
高 128 T 表示内核态虚拟内存空间,虚拟内存地址范围为:0xFFFF 8000 0000 0000- 0xFFFF FFFF FFFF FFFF。
这样一来就在用户态虚拟内存空间与内核态虚拟内存空间之间形成了一段 0x0000 7FFF FFFF F000-0xFFFF 8000 0000 0000的地址空洞,我们把这个空洞叫做 canonical address 空洞 。
一步一图带你深入理解 Linux 虚拟内存管理

文章插图
那么这个 canonical address 空洞是如何形成的呢?
我们都知道在 64 位机器上的指针寻址范围为 2^64,但是在实际使用中我们只使用了其中的低 48 位来表示虚拟内存地址,那么这多出的高 16 位就形成了这个地址空洞 。
大家注意到在低 128T 的用户态地址空间:0x0000 0000 0000 0000 - 0x0000 7FFF FFFF F000 范围中,所以虚拟内存地址的高 16 位全部为 0。
如果一个虚拟内存地址的高 16 位全部为 0 ,那么我们就可以直接判断出这是一个用户空间的虚拟内存地址 。
同样的道理,在高 128T 的内核态虚拟内存空间:0xFFFF 8000 0000 0000 - 0xFFFF FFFF FFFF FFFF 范围中 , 所以虚拟内存地址的高 16 位全部为 1。
也就是说内核态的虚拟内存地址的高 16 位全部为 1 ,如果一个试图访问内核的虚拟地址的高 16 位不全为 1 ,则可以快速判断这个访问是非法的 。
这个高 16 位的空闲地址被称为 canonical。如果虚拟内存地址中的高 16 位全部为 0 (表示用户空间虚拟内存地址)或者全部为 1 (表示内核空间虚拟内存地址),这种地址的形式我们叫做 canonical form,对应的地址我们称作 canonical address。
那么处于 canonical address 空洞 :0x0000 7FFF FFFF F000 - 0xFFFF 8000 0000 0000 范围内的地址的高 16 位 不全为 0 也不全为 1。如果某个虚拟地址落在这段 canonical address 空洞区域中,那就是既不在用户空间 , 也不在内核空间,肯定是非法访问了 。
未来我们也可以利用这块 canonical address 空洞,来扩展虚拟内存地址的范围 , 比如扩展到 56 位 。
在我们理解了 canonical address 这个概念之后,我们再来看下 64 位 Linux 系统下的真实虚拟内存空间布局情况:
一步一图带你深入理解 Linux 虚拟内存管理

文章插图
从上图中我们可以看出 64 位系统中的虚拟内存布局和 32 位系统中的虚拟内存布局大体上是差不多的 。主要不同的地方有三点:
  1. 就是前边提到的由高 16 位空闲地址造成的canonical address 空洞 。在这段范围内的虚拟内存地址是不合法的 , 因为它的高 16 位既不全为 0 也不全为 1,不是一个 canonical address,所以称之为 canonical address 空洞 。
  2. 在代码段跟数据段的中间还有一段不可以读写的保护段,它的作用是防止程序在读写数据段的时候越界访问到代码段 , 这个保护段可以让越界访问行为直接崩溃,防止它继续往下运行 。
  3. 用户态虚拟内存空间与内核态虚拟内存空间分别占用 128T , 其中低128T 分配给用户态虚拟内存空间,高 128T 分配给内核态虚拟内存空间 。

    推荐阅读