一:通俗解释
内核空间和用户空间的地址都是虚拟地址,都要经过 MMU 的翻译,变成物理地址。用户空间的虚拟地址,通过查询页表来翻译,而内核空间虚拟地址是所有进程共享的,而且从效率角度看,如果同样走页表翻译的流程,速度太慢;于是,内核在初始化时,就创建内核空间的映射(因为所有进程共享,有一份就够了),并且,采用的是线性映射,而不是走页表翻译这种类似哈希表的方式。这样,内核地址的翻译,简化为一条偏移加减指令就行,相比走页表,效率大大提高(不过,内核空间并非完全不用页表,此处讲原理所以简化,详细的看尾注)。
那么问题来了,在 Linux 刚引入的时候,i386的4G进程空间典型的是3Guser + 1G kernel 的划分。那按前面的线性方法, 1G 内核空间,只能映射 1G 物理地址空间,这对内核来说太少了(这种情况下,内核无法使用超过1G的物理内存)。所以,折衷方案是, Linux 内核只对 1G 内核空间的前 896 MB 按前面所说的方法做线性映射, 剩下的 128 MB 的内核空间, 采用动态映射的方式,即按需映射的方式,这样,内核态的访问空间更多了。 这个直接映射的部分, 就是所谓的 NORMAL 区, 也就是所谓低端内存,而动态映射的部分,就是所谓高端内存。到了 64位时代,内核空间大大增大,这种限制就没了,内核空间可以完全进行线性映射,不过,基于某种原因,仍保留有动态映射这部分:
动态映射不全是为了内核空间可以访问更多的物理内存,还有一个重要原因:当内核需要连续多页面的空间时,如果内核空间全线性映射,那么,可能会出现内核空间碎片化而满足不了这么多连续页面分配的需求。基于此,内核空间也必须有一部分是非线性映射,从而在这碎片化物理地址空间上,用页表构造连续虚拟地址空间,这就是所谓vmalloc空间。
(节选自:https://www.zhihu.com/question/34787574/answer/60214771 larmbr宇)
假设机器上装了2G的内存条,那么内核只把内存条上的896MB直接映射到内核空间中去,这下,内核的虚拟地址空间也只剩下128MB了(总共才1G),剩下的1G零128MB的物理内存怎么办?这样,内核什么时候需要,就什么时候把这些剩下的物理内存映射到那128MB虚拟地址空间上,这就叫临时映射,用的是kmap。当然,这128MB虚拟地址空间一次也只能映射128MB的物理地址空间,想要访问另外的物理空间,只能用kunmap把这一映射取消,再kmap重新映射另外128MB的物理地址......按需临时分配,很罗嗦很烦人,效率也不高。
(节选自:http://oldblog.donghao.org/2010/11/kernel-x86-64aauaeuai.html)
二:Linux虚拟地址空间划分
通常32位Linux的虚拟地址空间中,0~3G为用户空间,3~4G为内核空间。注意这里是32位虚拟地址空间划分,64位虚拟地址空间划分是不同的。
三:Linux内核高端内存的由来
当内核模块代码访问内存时,代码中的内存地址都为逻辑(虚拟)地址,而对应到真正的物理内存地址,需要对地址做一对一的映射(线性映射),如逻辑(虚拟)地址0xc0000003对应的物理地址为0x3,0xc0000004对应的物理地址为0x4,……,逻辑(虚拟)地址与物理地址对应的关系为:物理地址 = 逻辑地址 – 0xC0000000。
按照上述简单的地址映射关系,内核逻辑(虚拟)地址空间范围为0xc0000000~ 0xffffffff,那么对应的物理内存范围就为0x0 ~ 0x40000000,即内核只能访问1G物理内存。所以,若机器中安装8G物理内存,那么内核就只能访问前1G物理内存,后面7G物理内存将会无法访问。
因此,不能将内核地址空间0xc0000000 ~ 0xfffffff全部用来做简单的线性地址映射。x86架构中将物理地址空间划分三部分:ZONE_DMA、ZONE_NORMAL和ZONE_HIGHMEM。ZONE_HIGHMEM即为高端内存,这就是内存高端内存概念的由来。
在x86结构中,三种类型的区域如下:
ZONE_DMA | 内存开始的16MB |
ZONE_NORMAL | 16MB~896MB |
ZONE_HIGHMEM | 896MB ~ 结束 |
四:Linux内核高端内存的理解
高端内存HIGH_MEM对应的虚拟地址空间范围为0xF8000000 ~ 0xFFFFFFFF,那么内核是如何借助128MB的空间范围实现访问到所有物理内存呢?
当内核想访问高于896MB物理地址内存时,从0xF8000000 ~ 0xFFFFFFFF地址空间范围内找一段相应大小空闲的逻辑(虚拟)地址空间,借用这段逻辑地址空间,建立到想访问的那段物理内存(即填充内核PTE页面表)的映射,临时用一会,用完后归还。这样别人也可以借用这段地址空间访问其他物理内存,这就实现了使用有限的地址空间,访问所有所有物理内存。如下图。
例如内核想访问2G开始的一段大小为1MB的物理内存,即物理地址范围为0x80000000 ~ 0x800FFFFF。访问之前先找到一段1MB大小的空闲逻辑地址空间,假设找到的空闲逻辑地址空间为0xF8700000 ~0xF87FFFFF,因此,用这1MB的逻辑地址空间映射到物理地址空间0x80000000 ~ 0x800FFFFF的内存。
当内核访问完0x80000000 ~ 0x800FFFFF物理内存后,就将0xF8700000 ~ 0xF87FFFFF逻辑地址空间释放。这样其他进程或代码也可以使用0xF8700000 ~ 0xF87FFFFF这段地址访问其他物理内存。
从上面的描述,我们可以知道高端内存的最基本思想:借一段地址空间,建立临时地址映射,用完后释放,达到这段地址空间可以循环使用,访问所有物理内存。
看到这里,不禁有人会问:万一有内核进程或模块一直占用某段逻辑地址空间不释放,怎么办?若真的出现的这种情况,则会导致物理内存的高端内存地址空间越来越紧张。
五:Linux内核高端内存的划分
对于高端内存,可以通过 alloc_page() 或者其它函数获得对应的 page,但是要想访问实际物理内存,还得把 page 转为逻辑(虚拟)地址才行(为什么?想想 MMU 是如何访问物理内存的),也就是说,我们需要为高端内存对应的 page 找一个线性空间,这个过程称为高端内存映射。
内核将高端内存对应的虚拟空间划分为3部分:
VMALLOC_START ~ VMALLOC_END;
KMAP_BASE ~ FIXADDR_START;
FIXADDR_START ~ 4G。
如下图所示:
对应高端内存的3部分,高端内存映射有三种方式:
a:映射到”内核动态映射空间”(noncontiguous memory allocation),这种方式很简单,因为通过 vmalloc() ,在”内核动态映射空间”申请内存的时候,就可能从高端内存获得页面(参看 vmalloc 的实现),因此说高端内存有可能映射到”内核动态映射空间”中。
b:持久内核映射(permanentkernel mapping),如果是通过 alloc_page() 获得了高端内存对应的 page,如何给它找个线性空间?
内核专门为此留出一块线性空间,从 PKMAP_BASE 到 FIXADDR_START ,用于映射高端内存。在 2.6内核上,这个地址范围是 4G-8M 到 4G-4M 之间。这个空间起叫”内核永久映射空间”或者”永久内核映射空间”。这个空间和其它空间使用同样的页目录表,对于内核来说,就是swapper_pg_dir,对普通进程来说,通过 CR3 寄存器指向。通常情况下,这个空间是 4M 大小,因此仅仅需要一个页表即可,内核通过来 pkmap_page_table 寻找这个页表。通过 kmap(),可以把一个 page 映射到这个空间来。由于这个空间是 4M 大小,最多能同时映射 1024 个 page。因此,对于不使用的的 page,及应该时从这个空间释放掉(也就是解除映射关系),通过 kunmap() ,可以把一个 page 对应的线性地址从这个空间释放出来。
c:临时映射(temporarykernel mapping),内核在 FIXADDR_START 到 FIXADDR_TOP 之间保留了一些线性空间用于特殊需求。这个空间称为”固定映射空间”在这个空间中,有一部分用于高端内存的临时映射。这块空间具有如下特点:
(1)每个 CPU 占用一块空间
(2)在每个 CPU 占用的那块空间中,又分为多个小空间,每个小空间大小是 1 个 page,每个小空间用于一个目的,这些目的定义在 kmap_types.h 中的 km_type 中。
当要进行一次临时映射的时候,需要指定映射的目的,根据映射目的,可以找到对应的小空间,然后把这个空间的地址作为映射地址。这意味着一次临时映射会导致以前的映射被覆盖。通过 kmap_atomic() 可实现临时映射。
六:常见问题:
1、用户空间(进程)是否有高端内存概念?
用户进程没有高端内存概念。只有在内核空间才存在高端内存。用户进程最多只可以访问3G物理内存,而内核进程可以访问所有物理内存。
2、64位内核中有高端内存吗?
目前现实中,64位Linux内核不存在高端内存,因为64位内核可以支持超过512GB内存。若机器安装的物理内存超过内核地址空间范围,就会存在高端内存。
3、用户进程能访问多少物理内存?内核代码能访问多少物理内存?
32位系统用户进程最大可以访问3GB,内核代码可以访问所有物理内存。
64位系统用户进程最大可以访问超过512GB,内核代码可以访问所有物理内存。
(以上节选自:)
(http://ju.outofmemory.cn/entry/173153)