linux 高端内存页框管理:永久内核映射、临时内核映射以及非连续内存分配
摘要:高端内存页框的内核映射分为三种情况:永久内核映射、临时内核映射和非连续内存映射。那么这三者有什么区别和联系呢?临时内核映射如何保证不会被阻塞呢?本文主要为你解答这些疑问,并详细探讨高端内存映射的前两种方式。
本文来源:linux 高端内存页框管理:永久内核映射、临时内核映射以及非连续内存分配 http://blog.csdn.net/trochiluses/article/details/13016023
内核将高端内存划分为3部分:VMALLOC_START~VMALLOC_END、KMAP_BASE~FIXADDR_START和FIXADDR_START~4G。
对 于高端内存,可以通过 alloc_page() 或者其它函数获得对应的 page,但是要想访问实际物理内存,还得把 page 转为线性地址才行(为什么?想想 MMU 是如何访问物理内存的),也就是说,我们需要为高端内存对应的 page 找一个线性空间,这个过程称为高端内存映射。
对应高端内存的3部分,高端内存映射有三种方式:
映射到”内核动态映射空间”(noncontiguous memory allocation)
这种方式很简单,因为通过 vmalloc() ,在”内核动态映射空间”申请内存的时候,就可能从高端内存获得页面(参看 vmalloc 的实现),因此说高端内存有可能映射到”内核动态映射空间”中。
持久内核映射(permanent kernel mapping)
如果是通过 alloc_page() 获得了高端内存对应的 page,如何给它找个线性空间?
内核专门为此留出一块线性空间,从 PKMAP_BASE 到 FIXADDR_START ,用于映射高端内存。在 2.6内核上,这个地址范围是 4G-8M 到 4G-4M 之间。这个空间起叫”内核永久映射空间”或者”永久内核映射空间”。这个空间和其它空间使用同样的页目录表,对于内核来说,就是 swapper_pg_dir,对普通进程来说,通过 CR3 寄存器指向。通常情况下,这个空间是 4M 大小,因此仅仅需要一个页表即可(注意理解这句话:一个页表(不是页表项),大小为4K,可以映射4M的空间),内核通过来 pkmap_page_table 寻找这个页表。通过 kmap(),可以把一个 page 映射到这个空间来。由于这个空间是 4M 大小,最多能同时映射 1024 个 page。因此,对于不使用的的 page,及应该时从这个空间释放掉(也就是解除映射关系),通过 kunmap() ,可以把一个 page 对应的线性地址从这个空间释放出来。
临时映射(temporary kernel mapping)
内核在 FIXADDR_START 到 FIXADDR_TOP 之间保留了一些线性空间用于特殊需求。这个空间称为”固定映射空间”在这个空间中,有一部分用于高端内存的临时映射。
这块空间具有如下特点:
(1)每个 CPU 占用一块空间
(2)在每个 CPU 占用的那块空间中,又分为多个小空间,每个小空间大小是 1 个 page,每个小空间用于一个目的,这些目的定义在 kmap_types.h 中的 km_type 中。
当要进行一次临时映射的时候,需要指定映射的目的,根据映射目的,可以找到对应的小空间,然后把这个空间的地址作为映射地址。这意味着一次临时映射会导致以前的映射被覆盖。通过 kmap_atomic() 可实现临时映射。
896M边界以上的页框并不映射在内核线性地址空间的第4个GB,因此内核不能直接访问它们。所以,返回所分配页框线性地址的页分配器函数并不对高端内存可用。
在64位平台上不存在这个问题,因为可以使用的线性地址空间大于能安装的RAM,也就是说这些体系结构的ZONE_HIGHMEM是空的。linux使用如下方法来使用高端内存:
1)高端内存页框的分配只能通过alloc_pages( )函数和它的快捷函数alloc_page( )。这些函数不返回线性地址,而是返回第一分配页框的页描述符的线性地址。
2)没有线性地址的高端内存中的页框不能被内核访问。
内核采用三种不同的机制将页框映射到高端内存:永久内核映射、临时内核映射、非连续内存分配。本节讨论前两种。
建立永久内核映射可能阻塞当前进程;也就是高端内存上没有页表项可以用作页框的窗口的时候。因此,这种方法不能用在中断处理函数和可延迟函数。临时内核映射不会阻塞当前进程,但是只有很少的临时内核映射可以建立起来。
需要注意的是,无论哪种方法,128M的线性地址用于高端内存映射,无法保证寻址范围同时到达的物理内存。
2.永久内核映射:注意,下列多有函数应用的范围是内核空间
宏定义与关键变量定义:
pkmap_page_table:高端内存主内核页表中,一个用于永久内核映射的专用页表锁在的地址
LAST_PKMAP: 上述页表所含有的表项(512或者1024)
PKMAP_BASE:该页表所映射线性地址的start地址
pkmap_count:对页表项提供计数器的数组
page_address_htable:散列表,用于记录高端页框与永久内核映射的线性地址之间的关系
page_address_map:一个数据结构,包含指向页描述符的指针和分配给页框的线性地址;用于为高端内存的每个页框提供当前映射,它被包含在page_address_htable这个hansh表中
关键函数:
page_address( page):返回页框对应的线性地址
Void * kmap(struct page * page):返回对应page的线性地址
Void * kmap_high(struct page * page): 同上,不过接受的参数是高端内存的页框描述符
map_new_virtual( ):插入页框的物理地址到pkmap_page_table,在page_address_htable散列表中加入一个元素
它是高端页框到内核地址空间的长期映射。使用主内核页表中的一个专门页表,地址存放在pkmap_page_table变量中。页表中的表项数由LAST_PKMAP宏产生。页表照样包含512或者1024项,这取决于PAE是否激活,因此,内核一次访问最多2M或者4M的高端内存。
该页表映射的线性地址从PKMAP_BASE开始,pkmap_count数组包涵LAST_PKMAP个计数器,pkmap_page_table页表中的每一个项都有一个。我们区分下列三种情况
计数器为0:对应页表项没有映射任何的高端内存页框,并且是可用的。
计数器为1:对应的页表项没有映射任何内存页框,但是它不可用,因为从它最后一次使用以来,对应的TLB表项还未被刷新。
计数器为n:相应的页表项映射一个高端内存页框,这意味着正好有n-1个内核成分在使用这个页框。
当分配项的值等于0时为自由项,等于1时为缓冲项,大于1时为映射项。映射页面的分配基于分配表的扫描,当所有的自由项都用完时,系统将清除所有的缓冲项,如果连缓冲项都用完时,系统将进入等待状态。
为了记录高端内存页框与永久内核映射的线性地址之间的联系,内核使用了page_address_htable散列表。该表包含一个page_address_map数据结构,用于为高端内存的每个页框进行当前映射。而该数据结构还包涵一个指向页描述符号的指针和分配给该页框的线性地址。
对应数据结构关系图如下:
page_address()函数返回页框对应的线性地址,如果页框在高端内存中并没有被映射,则返回NULL。这个函数接受一个页描述符指针page作为参数,并区分以下两种情况:
1)页框不在高端内存中:
__va( ( unsigned long) (page - meme_map) << 12)
2) 页框在高端内存中,该函数就得到page_address_htable中寻找。如果在散列表中找到页框,page_address()就返回它的线性地址,否则就返回NULL。
代码实现如下:
371 #define mk_pte(page, pgprot) pfn_pte(page_to_pfn(page), (pgprot))