内存管理:03高端内存简介

摘要:
ZONE_ HIGHMEM是高端存储器,这是高端存储器概念的起源。如果发生这种情况,物理内存的高端内存地址空间将变得越来越紧张。想想MMU如何访问物理内存。)也就是说,我们需要为与高端内存相对应的页面找到一个线性空间。这个过程称为高端内存映射。

一:通俗解释

        内核空间和用户空间的地址都是虚拟地址,都要经过 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位虚拟地址空间划分是不同的。

内存管理:03高端内存简介第1张

三: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 ~ 结束

内存管理:03高端内存简介第2张

四:Linux内核高端内存的理解

        高端内存HIGH_MEM对应的虚拟地址空间范围为0xF8000000 ~ 0xFFFFFFFF,那么内核是如何借助128MB的空间范围实现访问到所有物理内存呢?

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

内存管理:03高端内存简介第3张

        例如内核想访问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。

        如下图所示:

内存管理:03高端内存简介第4张

 

        对应高端内存的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://ilinuxkernel.com/?p=1013

 内存管理:03高端内存简介第5张

(http://ju.outofmemory.cn/entry/173153)

免责声明:文章转载自《内存管理:03高端内存简介》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Treeylee的抽象工厂设计模式<收藏>linux 服务器发现了挖矿病毒下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

虚拟机CentOS7三台集群配置网络

准备工作,VMware中安装一台Centos7,然后完全克隆出两个,一共三台虚拟机,下面是对三台虚拟机的网络进行配置    这三台环境一模一样,分别命名为:Master、Node1、Node2,在配置网络之前,三台虚拟机先同时开启,开始集群网络配置: 第一、进入虚拟机网络编辑器,选择VMnet8, 选择NAT模式,确定子网IP段,我的电脑一进来默认是192...

Ruby开发环境的搭建

1.Ruby的下载 https://rubyinstaller.org/downloads/ 2.Ruby的安装 3.Eclipse配置Ruby开发环境 插件地址:http://rubyeclipse.sourceforge.NET/nightlyBuild/updateSite/  经过测试很多Ruby插件地址都是无效的 所以用eclipse 中...

ARM寻址方式

立即数寻址: 操作数本身就在指令中 例子:ADD R0,R0,#0X3F(注意:立即数需要在数据前面加上一个#号) 寄存器寻址: 利用寄存器中的数值作为操作数,数据存在寄存器中 例子:ADD R0,R1,R2 寄存器间接寻址: 数据存放在内存中,寄存其中存放的不是操作数本身,而是其在内存中的地址。通常需要加上一个[] 例子:LDR R0,[R2] 基地址变...

Centos 7 解决free -m 下buff/cache缓存很高

Linux服务器运行一段时间后,由于其内存管理机制,会将暂时不用的内存转为buff/cache,这样在程序使用到这一部分数据时,能够很快的取出,从而提高系统的运行效率,所以这也正是linux内存管理中非常出色的一点,所以乍一看内存剩余的非常少,但是在程序真正需要内存空间时,linux会将缓存让出给程序使用,这样达到对内存的最充分利用,所以真正剩余的内存是f...

创建TIff虚拟打印机

1.需要2个软件 GhostScript是PostScript解释程序,文件转换器。http://www.ghostscript.com/(下载地址: http://downloads.ghostscript.com/public/gs871w32.exe) RedMon是用于打印机端口监听。http://pages.cs.wisc.edu/~ghost...

Windows平台下PHP开发环境的配置

Windows平台下PHP开发环境的配置 一、基本环境 1、Windows XP 32位 2、Apache 2.2.25,下载地址:http://mirror.bit.edu.cn/apache/httpd/binaries/win32/httpd-2.2.25-win32-x86-openssl-0.9.8y.msi 3、PHP 5.2.17,下载地址:...