在32位的系统上,内核占有从第3GB~第4GB的线性地址空间,共1GB大小,内核将其中的前896MB与物理内存的0~896MB进行直接映射,即线性映射,将剩余的128M线性地址空间作为访问高于896M的内存的一个窗口。引入高端内存映射这样一个概念的主要原因就是我们所安装的内存大于1G时,内核的1G线性地址空间无法建立一个完全的直接映射来触及整个物理内存空间,而对于80x86开启PAE的情况下,允许的最大物理内存可达到64G,因此内核将自己的最后128M的线性地址空间腾出来,用以完成对高端内存的暂时性映射。而在64位的系统上就不存在这样的问题了,因为可用的线性地址空间远大于可安装的内存。下图描述了内核1GB线性地址空间是如何划分的
其中可以用来完成上述映射目的的区域为vmalloc area,Persistent kernel mappings区域和固定映射线性地址空间中的FIX_KMAP区域,这三个区域对应的映射机制分别为非连续内存分配,永久内核映射和临时内核映射。
相关阅读:
Linux高端内存映射(上)
Linux高端内存映射(中)
Linux高端内存映射(中)
永久内核映射
在内核初始化页表管理机制时,专门用pkmap_page_table这个变量保存了PKMAP_BASE对应的页表项的地址,由pkmap_page_table来维护永久内核映射区的页表项的映射,页表项总数为LAST_PKMAP个,具体可以看前面关于页表机制初始化的博文。这里的永久并不是指调用kmap()建立的映射关系会一直持续下去无法解除,而是指在调用kunmap()解除映射之间这种映射会一直存在,这是相对于临时内核映射机制而言的。
内核用一个pkmap_count数组来记录pkmap_page_table中每一个页表项的使用状态,其实就是为每个页表项分配一个计数器来记录相应的页表是否已经被用来映射。计数值分为以下三种情况:
计数值为0:对应的页表项没有映射高端内存,即为空闲可用的
计数值为1: 对应的页表项没有映射高端内存,但是不可用,因为上次映射后对应的TLB项还未被淸刷
计数值为n(n>1):对应的页表项已经映射了一个高端内存页框,并且有n-1个内核成分正在利用这种映射关系
下面结合代码进行具体的分析,先通过alloc_page(__GFP_HIGHMEM)分配到了一个属于高端内存区域的page结构,然后调用kmap(struct page*page)来建立与永久内核映射区的映射,需要注意一点的是,当永久内核映射区没有空闲的页表项可供映射时,请求映射的进程会被阻塞,因此永久内核映射请求不能发生在中断和可延迟函数中。
void *kmap(struct page *page) { might_sleep(); if (!PageHighMem(page))/*页框属于低端内存*/ return page_address(page);/*返回页框的虚拟地址*/ return kmap_high(page); }
如果页框是属于高端内存的话,则调用kmap_high()来建立映射
void *kmap_high(struct page *page) { unsigned long vaddr; /* * For highmem pages, we can't trust "virtual" until * after we have the lock. */ lock_kmap();/*获取自旋锁防止多处理器系统上的并发访问*/ /*试图获取页面的虚拟地址,因为之前可能已经有进程为该页框建立了到永久内核映射区的映射*/ vaddr = (unsigned long)page_address(page); /*虚拟地址不存在则调用map_new_virtual()为该页框分配一个虚拟地址,完成映射*/ if (!vaddr) vaddr = map_new_virtual(page); pkmap_count[PKMAP_NR(vaddr)]++;/*相应的页表项的计数值加1*/ BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2); unlock_kmap(); return (void*) vaddr; }