首发于术道经纬
Linux的进程地址空间[一]

Linux的进程地址空间[一]

所谓进程地址空间(process address space),就是从进程的视角看到的地址空间,是进程运行时所用到的虚拟地址的集合。

32位系统的进程地址空间

以IA-32处理器为例,其虚拟地址为32位,因此其虚拟地址空间的范围为 2^{32}=4GB ,Linux系统将地址空间按3:1比例划分,其中用户空间(user space)占3GB,内核空间(kernel space)占1GB。

假设物理内存也是4GB(事实上,虚拟地址空间的范围不一定需要和物理地址空间的大小相同),则虚拟地址空间和物理地址空间的转换如下图所示:

因为内核的虚拟地址空间只有1GB,但它需要访问整个4GB的物理空间,因此从物理地址0~896MB的部分(ZONE_DMA+ZONE_NORMAL),直接加上3GB的偏移(在Linux中用PAGE_OFFSET表示),就得到了对应的虚拟地址,这种映射方式被称为线性/直接映射(Direct Map)。

而896M~4GB的物理地址部分(ZONE_HIGHMEM)需要映射到(3G+896M)~4GB这128MB的虚拟地址空间,显然也按线性映射是不行的。

采用的是做法是,ZONE_HIGHMEM中的某段物理内存和这128M中的某段虚拟空间建立映射,完成所需操作后,需要断开与这部分虚拟空间的映射关系,以便ZONE_HIGHMEM中其他的物理内存可以继续往这个区域映射,即动态映射的方式。

用户空间的进程只能访问整个虚拟地址空间的0~3GB部分,不能直接访问3G~4GB的内核空间部分,但出于对性能方面的考虑,Linux中内核使用的地址也是映射到进程地址空间的(被所有进程共享),因此进程的虚拟地址空间可视为整个4GB(虽然实际只有3GB)。

64位系统的进程地址空间

在64位系统中,进程地址空间的大小就不固定了,以ARMv8-A为例,它的page大小可以是4KB, 16KB或者64KB(默认为4KB,选一种来用,不要混用),可采用3级页表或4级页表,因此可以有多种组合的形式。

以采用4KB的页,4级页表,虚拟地址为48位的系统为例(从ARMv8.2架构开始,支持虚拟地址和物理地址的大小最多为52位),其虚拟地址空间的范围为 2^{48}=256TB ,按照1:1的比例划分,内核空间和用户空间各占128TB。

256TB已经很大很大了,但是面对64位系统所具备的16EB的地址范围,根本就用不完。为了以后扩展的需要(比如虚拟地址扩大到56位),用户虚拟空间和内核虚拟空间不再是挨着的,但同32位系统一样,还是一个占据底部,一个占据顶部,所以这时user space和kernel space之间偌大的区域就空出来了。

但这段空闲区域也不是一点用都没有,它可以辅助进行地址有效性的检测。如果某个虚拟地址落在这段空闲区域,那就是既不在user space,也不在kernel space,肯定是非法访问了。使用48位虚拟地址,则kernel space的高16位都为1,如果一个试图访问kernel space的虚拟地址的高16位不全为1,则可以判断这个访问也是非法的。同理,user space的高16位都为0。这种高位空闲地址被称为canonical。

在64位系统中,内核空间的映射变的简单了,因为这时内核的虚拟地址空间已经足够大了,即便它要访问所有的物理内存,直接映射就是,不再需要ZONE_HIGHMEM那种动态映射机制了。

64位系统中用户空间的映射和32位系统没有太大的差别。

ARM公司宣称64位的ARMv8是兼容32位的ARM应用的,所有的32位应用都可以不经修改就在ARMv8上运行。那32位应用的虚拟地址在64位内核上是怎么分布的呢?事实上,64位内核上的所有进程都是一个64位进程。要运行32位的应用程序, Linux内核仍然从64位init进程创建一个进程, 但将用户地址空间限制为4GB。通过这种方式, 我们可以让64位Linux内核同时支持32位和64位应用程序。

要注意的是, 32位应用程序仍然对应128TB的内核虚拟地址空间, 并且不与内核共享自己的4GB虚拟地址空间, 此时用户应用程序具有完整的4GB虚拟地址。而32位内核上的32位应用程序只有3GB真正意义上的虚拟地址空间。

那进程地址空间到底是由哪些元素构成的呢?请看下文分解。


参考:

jake.dothome.co.kr/pt64,里面对ARM64的各种page size和页表级数的组合做了详尽介绍。

kernel.org/doc/Document

thinkiii.blogspot.com/2, 这是本文后半部分的图片来源和主要参考。


原创文章,转载请注明出处。

编辑于 04-17

文章被以下专栏收录