Linux 内存管理 — 高端内存的映射方式

高端内存是指物理地址大于 896M 的内存。对于这样的内存,无法在“内核直接映射空间”进行映射。为什么?因为“内核直接映射空间”最多只能从 3G 到 4G,只能直接映射 1G 物理内存,对于大于 1G 的物理内存,无能为力。实际上,“内核直接映射空间”也达不到 1G, 还得留点线性空间给“内核动态映射空间” 呢。因此,Linux 规定“内核直接映射空间” 最多映射 896M 物理内存。对 于高端内存,可以通过 alloc_page() 或者其它函数获得对应的 page,但是要想访问实际物理内存,还得把 page 转为线性地址才行(为什么?想想 MMU 是如何访问物理内存的),也就是说,我们需要为高端内存对应的 page 找一个线性空间,这个过程称为高端内存映射。高端内存映射有三种方式:1、映射到“内核动态映射空间”这种方式很简单,因为通过 vmalloc() ,在”内核动态映射空间“申请内存的时候,就可能从高端内存获得页面(参看 vmalloc 的实现),因此说高端内存有可能映射到”内核动态映射空间“ 中。2、永久内核映射如果是通过 alloc_page() 获得了高端内存对应的 page,如何给它找个线性空间?内核专门为此留出一块线性空间,从PKMAP_BASE到FIXADDR_START,用于映射高端内存。在2.4内核上,这个地址范围是4G-8M到4G-4M之间。这个空间起叫“内核永久映射空间”或者“永久内核映射空间”这个空间和其它空间使用同样的页目录表,对于内核来说,就是swapper_pg_dir,对普通进程来说,通过 CR3 寄存器指向。通常情况下,这个空间是4M大小,因此仅仅需要一个页表即可,内核通过来 pkmap_page_table 寻找这个页表。通过 kmap(), 可以把一个 page 映射到这个空间来由于这个空间是4M大小,最多能同时映射1024个page。因此,对于不使用的的page,及应该时从这个空间释放掉(也就是解除映射关系),通过kunmap() ,可以把一个 page 对应的线性地址从这个空间释放出来。3、临时映射

内核在FIXADDR_START到FIXADDR_TOP之间保留了一些线性空间用于特殊需求。这个空间称为“固定映射空间”

在这个空间中,有一部分用于高端内存的临时映射。

这块空间具有如下特点:

1、每个CPU占用一块空间

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

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

通过 kmap_atomic() 可实现临时映射。

下图简单简单表达如何对高端内存进行映射

-------------------------

高端内存含义为:线性地址空间 PAGE_OFFSET + 896M 至4G的最后128M线性地址 <==映射==> 896M以上的物理页框,非直接映射。有3种方法:非连续内存区映射,永久内核映射,临时内核映射(固定映射) 从 PAGE_OFFSET开始的线性地址区域为: PAGE_OFFSET(3G)|物理内存映射 –8M– vmalloc区 –4K– vmalloc区 –8K– 永久内核映射–临时内核映射(固定映射)|4G1. 非连续区映射1.1 每个非连续内存区都对应一个类型为 vm_struct的描述符,通过next字段,这些描述符被插入到一个vmlist链表中。1.2 三种非连续区的类型: VM_ALLOC — 物理内存(调用alloc_page)和线性地址同时申请,物理内存是 __GFP_HIGHMEM类型(分配顺序是HIGH,NORMAL, DMA )(可见vmalloc不仅仅可以映射__GFP_HIGHMEM页框,它的主要目的是为了将零散的,不连续的页框拼凑成连续的内核逻辑地址空间…) VM_MAP — 仅申请线性区,物理内存另外申请,是VM_ALLOC的简化版 VM_IOREMAP — 仅申请线性区,物理内存另外申请(这里的物理内存一般都是高端内存,大于896M的内存)2. 永久内核映射2.1 永久内存映射允许建立长期映射。使用主内核页表中swapper_pg_dir的一个专门页表。 pkmap_page_table: 专门的页表。页表表项数由LAST_PKMAP(512或1024)产生。 page_address_htable: 存放地址的 pkmap_count: 包含LAST_PKMAP个计数器的数组。 PKMAP_BASE: 页表线性地址从PKMAP_BASE开始。2.2 如果LAST_PKMAP个项都用完,则把当前进程置为 TASK_UNINTERRUPTIBLE,并调用schedule()3. 临时内存映射3.1 可以用在中断处理函数和可延迟函数的内部,从不阻塞。因为临时内存映射是固定内存映射的一部分,一个地址固定给一个内核成分使用。3.2 每个CPU都有自己的一个13个窗口(一个线性地址及页表项)的集合。enum km_type { KM_BOUNCE_READ, KM_SKB_SUNRPC_DATA, KM_SKB_DATA_SOFTIRQ, KM_USER0, KM_USER1, KM_BIO_SRC_IRQ, KM_BIO_DST_IRQ, KM_PTE0, KM_PTE1, KM_IRQ0, KM_IRQ1, KM_SOFTIRQ0, KM_SOFTIRQ1, KM_TYPE_NR};所有固定映射的固定线性地址enum fixed_addresses { FIX_HOLE, FIX_VSYSCALL, ….#ifdef CONFIG_HIGHMEM FIX_KMAP_BEGIN, /* reserved pte’s for temporary kernel mappings */ FIX_KMAP_END = FIX_KMAP_BEGIN+(KM_TYPE_NR*NR_CPUS)-1,#endif ……. __end_of_permanent_fixed_addresses, /* temporary boot-time mappings, used before ioremap() is functional */#define NR_FIX_BTMAPS 16 FIX_BTMAP_END = __end_of_permanent_fixed_addresses, FIX_BTMAP_BEGIN = FIX_BTMAP_END + NR_FIX_BTMAPS – 1, FIX_WP_TEST, __end_of_fixed_addresses};3.3 注意 fixed_addresses 的地址从上至下是倒着的,FIX_HOLE的地址等于 0xfffff000,是一个洞#define __fix_to_virt(x) (FIXADDR_TOP – ((x) << PAGE_SHIFT))#define __FIXADDR_TOP 0xfffff000

-------------------------

VMALLOC_RESERVE和896MLINUX 内核虚拟地址空间到物理地址空间一般是固定连续影射的。假定机器内存为512M,从3G开始,到3G + 512M 为连续固定影射区。zone_dma, zone_normal为这个区域的。固定影射的VADDR可以直接使用(get a free page, then use pfn_to_virt()等宏定义转换得到vaddr)或用kmalloc等分配. 这样的vaddr的物理页是连续的。得到的地址也一定在固定影射区域内。如果内存紧张,连续区域无法满足,调用vmalloc分配是必须的,因为它可以将物理不连续的空间组合后分配,所以更能满足分配要求。vmalloc可以映射高端页框,也可以映射底端页框。vmalloc的作用只是为了提供逻辑上连续的地址。。。但vmalloc分配的vaddr一定不能与固定影射区域的vaddr重合。因为vaddr到物理页的影射同时只能唯一。所以vmalloc得到的 vaddr要在3G + 512m 以上才可以。也就是从VMALLOC_START开始分配。 VMALLOC_START比连续固定影射区大最大vaddr地址还多8-16M(2*VMALLOC_OFFSET)–有个鬼公式在#define VMALLOC_OFFSET 8*1024#define VMALLOC_START (high_memory – 2*VMALLOC_OFFSET) & ~(VMALLOC_OFFSET-1)high_memory 就是固定影射区域最高处。空开8-16M做什么? 为了捕获越界的mm_fault.同样,vmalloc每次得到的VADDR空间中间要留一个PAGE的空(空洞),目的和上面的空开一样。你vmalloc(100)2次,得到的2个地址中间相距8K。如果连续分配无空洞,那么比如p1=vmalloc(4096);p2=vmalloc(4096);如果p1使用越界到p2中了,也不会mm_falut. 那不容易debug.下面说明VMALLOC_RESERVE和896M的问题。上面假设机器物理512M的case. 如果机器有1G物理内存如何是好?那vmalloc()的vaddr是不是要在3G + 1G + 8M 空洞以上分配?超过寻址空间了吗。这时,4G 下面保留的VMALLOC_RESERVE 128m 就派上用场了。也就是说如果物理内存超过896M, high_memory也只能在3G + 896地方。可寻址空间最高处要保留VMALLOC_RESREVE 128M给vmalloc用。所以这128M的VADDR空间是为了vmalloc在物理超过了896M时候使用。如果物理仅仅有512M, 一般使用不到。因为VMALLOC_START很低了。如果vmalloc太多了才会用到。high_memory在arch/i386/kernel, mm的初始化中设置。根据物理内存大小和VMALLOC_RESERVE得到数值.所以说那128M的内核线性地址仅仅是为了影射1G以上的物理内存的不对的。如果物理内存2G,1G以下的vmalloc也用那空间影射。总之,内核的高端线性地址是为了访问内核固定映射以外的内存资源看vmalloc分配的东西可以用

show_vmalloc(){struct vm_struct **p, *tmp;for(p = &vmlist; (tmp = *p); p = &tmp->next) { printk(“%p %p %d/n”, tmp, tmp->addr, tmp->size}}

用户空间当然可以使用高端内存,而且是正常的使用,内核在分配那些不经常使用的内存时,都用高端内存空间(如果有),所谓不经常使用是相对来说的,比如内核的一些数据结构就属于经常使用的,而用户的一些数据就属于不经常使用的。用户在启动一个应用程序时,是需要内存的,而每个应用程序都有3G的线性地址,给这些地址映射页表时就可以直接使用高端内存。而且还要纠正一点的是:那128M线性地址不仅仅是用在这些地方的,如果你要加载一个设备,而这个设备需要映射其内存到内核中,它也需要使用这段线性地址空间来完成,否则内核就不能访问设备上的内存空间了。总之,内核的高端线性地址是为了访问内核固定映射以外的内存资源实际上高端内存是针对内核一段特殊的线性空间提出的概念,和实际的物理内存是两码事。进程在使用内存时,触发缺页异常,具体将哪些物理页映射给用户进程是内核考虑的事情。在用户空间中没有高端内存这个概念。

—————————————————————–

以下讨论仅限i386平台,一般考虑典型情况

    linux内核对整个系统的物理内存是通过类型为struct page的数组mem_map来管理的。系统中的伙伴系统分配算法最终是通过操作这个数组来记录物理内存的分配、回收等操作。在这里不要被系统的高端内存、低端内存等概念搞混淆了,高、低端内存的分类主要在于区分物理内存地址是否可以直接映射到内核线性地址空间中。

我们知道,linux的内核地址空间大小为1G(用户空间0~3G,内核空间3G~4G,这种分法最常见),因此如果把这1G线性地址空间全部拿来直接一一映射物理内存的话,在内核态的所有进程(线程)能使用的物理内存总共最多只有1G,为了能使在内核态的所有进程能使用更多的物理内存,linux采取了一种变通的形式:它将1G内核线性地址空间分为几部分,第一部分为1G的前896M,这部分内核线性空间与物理内存的0~896M一一映射(相差一个为0xc0000000的常数),后面128M的线性空间拿来动态映射剩下的所有物理内存,由于动态映射的方法不一样,后面的128M又分成了几个部分,有兴趣的可以查看相关资料。在这里,前面896M线性空间对应的物理内存就是所谓的低端物理内存,剩下的物理内存就是高端物理内存。

从上面高、低端物理内存命名的由来我们可以知道,高、低端物理内存与具体的内存分配算法无关,它们都是被mem_map数组控制起来,再由伙伴分配系统实施管理。

    关于进程及其内存分配

首先要明白一个概念:进程中使用的所有地址都是虚地址,在linux下这个虚地址就是所谓的线性地址。linux中进程可运行在用户态和内核态,(典型配置情况下)当进程运行在用户态时,它使用的线性地址只能位于0~3G范围内,当进程运行于内核态时,它使用的线性地址地址范围为3G~4G。

为了把线性地址转化为物理地址,每个进程都有自己私有的页目录和页表。linux在建立进程页目录时,把用户地址空间的页目录项(0~767项)清空而将内核页目录表(swapper_pg_dir)的第768项到1023项拷贝到进程的页目录表的第768项到1023项中。由于内核在初始化时也只映射了物理内存的前896M,我们可以知道内核也目录表只能保证第768项开始的224项中有有效映射。从这里我们可以知道,所有的进程都共享了其内核线性地址空间。

当一个进程在内核空间发生缺页故障的时候,这主要发生在访问内核空间动态映射区线性地址,在其处理程序中,就要通过0号进程的页目录(swapper_pg_dir)来同步本进程的内核页目录,实际上就是拷贝0号进程的内核页目录到本进程中(内核页表与进程0共享,故不需要复制)。如果进程0的该地址处的内核页目录也不存在,则出错,具体代码可以参考vmalloc的实现源码。

当进程运行于用户态时,若其需要申请内存空间,内核首先会在其用户线性空间中分配需要的线性地址空间,再通过伙伴分配系统分配物理内存并把分配的物理内存跟用户空间线性地址映射起来,最后再修改进程的页目录项及页表项写入这些映射关系。

每个人在他的人生发轫之初,总有一段时光,

Linux 内存管理 — 高端内存的映射方式

相关文章:

你感兴趣的文章:

标签云: