Linux内核高端内存

本文内容主要来自ilinuxkernel.com,并对该部分内容进行了扩充。

下图展示的部分内容包含了本文的知识点,值得细细分析。

Linux内核地址空间划分

通常32位Linux进程地址空间划分0~3G为用户空间,3~4G为空间。注意这里是32位进程地址空间划分,64位进程地址空间划分是不同的。

Linux内核高端内存的由来

当内核模块代码或线程访问内存时,代码中的内存地址都为逻辑地址,而对应到真正的物理内存地址,需要地址一对一的映射,如逻辑地址0xc0000003对应的物理地址为0x3,0xc0000004对应的物理地址为0x4,… …,逻辑地址与物理地址对应的关系为:
物理地址 = 逻辑地址 – 0xC0000000

假设按照上述简单的地址映射关系,那么内核逻辑地址空间访问为0xc0000000 ~ 0xffffffff,那么对应的物理内存范围就为0x0 ~ 0x40000000,即只能访问1G物理内存。若机器中安装8G物理内存,那么内核就只能访问前1G物理内存,后面7G物理内存将会无法访问,因为内核的地址空间已经全部映射到物理内存地址范围0x0 ~ 0x40000000。即使安装了8G物理内存,那么物理地址为0x40000001的内存,内核该怎么去访问呢?代码中必须要有内存逻辑地址的,0xc0000000 ~ 0xffffffff的地址空间已经被用完了,所以无法访问物理地址0x40000000以后的内存。

显然不能将内核地址空间0xc0000000 ~ 0xfffffff全部用来简单的地址映射。x86架构中将物理地址空间划分三部分:ZONE_DMA、ZONE_NORMAL和ZONE_HIGHMEM。ZONE_HIGHMEM即为高端内存,这就是高端内存概念的由来。

在x86结构中,三种类型的区域如下:

  • ZONE_DMA 物理内存开始的16MB
  • ZONE_NORMAL 物理内存的16MB~896MB
  • ZONE_HIGHMEM 物理内存的896MB ~ 结束

Linux内核高端内存的理解

前面我们解释了高端内存的由来。 Linux将物理地址空间划分为三部分ZONE_DMA、ZONE_NORMAL和ZONE_HIGHMEM,高端内存HIGH_MEM地址空间映射的内核线性地址范围为0xF8000000 ~ 0xFFFFFFFF(896MB~1024MB)。

当内核想访问高于896MB物理地址内存时,从0xF8000000 ~ 0xFFFFFFFF地址空间范围内找一段相应大小空闲的逻辑地址空间,借用一会。借用这段逻辑地址空间,建立映射到想访问的那段物理内存(即填充内核页表),临时用一会,用完后归还。这样别人也可以借用这段地址空间访问其他物理内存,实现了使用有限的地址空间,访问所有所有物理内存。如下图:

例如内核想访问2G开始的一段大小为1MB的物理内存,即物理地址范围为0x80000000 ~ 0x800FFFFF。访问之前先找到一段1MB大小的空闲地址空间,假设找到的空闲地址空间为0xF8700000 ~ 0xF87FFFFF,用这1MB的逻辑地址空间映射到物理地址空间0x80000000 ~ 0x800FFFFF的内存。

当内核访问完0x80000000 ~ 0x800FFFFF物理内存后,就将0xF8700000 ~ 0xF87FFFFF内核线性空间释放。这样其他进程或代码也可以使用0xF8700000 ~ 0xF87FFFFF这段地址访问其他物理内存。

从上面的描述,我们可以知道高端内存的最基本思想:借一段地址空间,建立临时地址映射,用完后释放,达到这段地址空间可以循环使用,访问所有物理内存。

高端内存的映射

0xF8000000 ~ 0xFFFFFFFF(896MB~1024MB)的128MB内核线性地址空间被划分为3部分:VMALLOC_START~VMALLOC_END、KMAP_BASE~FIXADDR_START和FIXADDR_START~4G。

对于高端内存,可以通过 alloc_page() 或者其它函数获得对应的 page,但是要想访问实际物理内存,还得把 page 转为线性地址才行,也就是说,我们需要为高端内存对应的 page 找一个线性空间,这个过程称为高端内存映射。

对应128MB内核线性地址空间的3部分,高端内存映射有三种方式:

  • 映射到”内核动态映射空间”(noncontiguous memory allocation)

这种方式很简单,因为通过 vmalloc() ,在”内核动态映射空间”申请内存的时候,就从高端内存获得页面,因此说高端内存有可能映射到”内核动态映射空间”中。

  • 持久内核映射(permanent kernel mapping)

内核专门留出一块线性空间,从 PKMAP_BASE 到 FIXADDR_START ,用于映射高端内存。在 2.6内核上,这个地址范围是 4G-8M 到 4G-4M 之间。这个空间叫”持久内核映射空间”。通过kmap(),可以把一个 page 映射到这个空间来。因为允许永久映射的数量有限,当不再需要高端内存时,应该解除映射,这可以通过kunmap()函数来完成。

  • 临时映射(temporary kernel mapping)

内核在 FIXADDR_START 到 FIXADDR_TOP(4GB)之间保留了一些线性空间用于特殊需求。这个空间称为”固定映射空间”,在这个空间中,有一部分用于高端内存的临时映射。
这个空间具有如下特点:

  1. 每个 CPU 占用一块空间
  2. 在每个 CPU 占用的那块空间中,又分为多个小空间,每个小空间大小是 1 个 page,每个小空间用于一个目的,这些目的定义在 kmap_types.h 中的 km_type 中。

当要进行一次临时映射的时候,需要指定映射的目的,根据映射目的,可以找到对应的小空间,然后把这个空间的地址作为映射地址。这意味着一次临时映射会导致以前的映射被覆盖。通过kmap_atomic()可实现临时映射。

常见问题

1. 用户空间是否有高端内存概念?

用户空间没有高端内存概念,只有内核空间才存在高端内存。

2. 64位内核中有高端内存吗?

目前现实中,64位Linux内核不存在高端内存,因为64位内核可以支持超过512GB内存。若机器安装的物理内存超过内核地址空间范围,就会存在高端内存。

3. 高端内存和物理地址、线性地址的关系?

高端内存只和物理地址有关系,和线性地址没有直接关系。


参考资料:

  1. ilinuxkernel Linux内核高端内存