【转载】linux内核笔记之高端内存映射

时间:2024-01-11 19:18:26

原文:linux内核笔记之高端内存映射

在32位的系统上,内核使用第3GB~第4GB的线性地址空间,共1GB大小。内核将其中的前896MB与物理内存的0~896MB进行直接映射,即线性映射,将剩余的128M线性地址空间作为访问高于896M的内存的一个窗口。

引入高端内存映射这样一个概念的主要原因就是我们所安装的内存大于1G时,内核的1G线性地址空间无法建立一个完全的直接映射来触及整个物理内存空间,而对于80x86开启PAE的情况下,允许的最大物理内存可达到64G,因此内核将自己的最后128M的线性地址空间腾出来,用以完成对高端内存的暂时性映射。

而在64位的系统上就不存在这样的问题了,因为可用的线性地址空间远大于可安装的内存。下图描述了内核1GB线性地址空间是如何划分的。

【转载】linux内核笔记之高端内存映射

其中PAGE_OFFSET表示内核使用的1GB线性地址的起始处(第3GB),high_memory往右的部分则表示高端内存,共 128M 的线性地址。可以用来完成上述映射目的的区域为vmalloc area,Persistent kernel mappings区域和固定映射线性地址空间中的FIX_KMAP区域,这三个区域对应的映射机制分别为 非连续内存分配 , 永久内核映射和 临时内核映射 。

永久内核映射

在内核初始化页表管理机制时,专门用pkmap_page_table这个变量保存了PKMAP_BASE对应的页表项的地址,由pkmap_page_table来维护永久内核映射区的页表项的映射,页表项总数为LAST_PKMAP个。

这里的永久并不是指调用kmap()建立的映射关系会一直持续下去无法解除,而是指在调用kunmap()解除映射之间这种映射会一直存在,这是相对于临时内核映射机制而言的。

需要注意一点的是,当永久内核映射区没有空闲的页表项可供映射时,请求映射的进程会被阻塞,因此永久内核映射请求不能发生在中断和可延迟函数中。

临时内核映射

临时内核映射和永久内核映射相比,其最大的特点就是不会阻塞请求映射页框的进程,因此临时内核映射请求可以发生在中断和可延迟函数中。系统中的每个CPU都有自己的13个临时内核映射窗口,根据不同的需求(用于内核控制路径),选择不同的窗口来创建映射。每个CPU的映射窗口集合用 enum km_type 数据结构表示,该数据结构中的每个符号,如 KM_BOUNCE_READ 、 KM_USER0 或 KM_PTE0 ,标识了窗口的线性地址,其实是一个下标。当要内核建立一个临时映射时,通过 cpu_id 和 窗口下标 来确定线性地址。

临时内核映射的实现也比永久内核映射要简单,当一个进程申请在某个窗口创建映射,即使这个窗口已经在之前就建立了映射,新的映射也会建立并且覆盖之前的映射,所以说这种映射机制是临时的,并且不会阻塞当前进程。

非连续内存分配

非连续内存分配是指将物理地址不连续的页框映射到线性地址连续的线性地址空间,主要应用于大容量的内存分配。采用这种方式分配内存的主要优点是避免了外部碎片,而缺点是必须打乱内核页表,而且访问速度较连续分配的物理页框慢。

非连续内存分配的线性地址空间是从 VMALLOC_START 到 VMALLOC_END ,每当内核要用vmalloc类的函数进行非连续内存分配,就会申请一个vm_struct结构来描述对应的vmalloc区,两个vmalloc区之间的间隔至少为一个页框的大小,即PAGE_SIZE。

总结

由于内核的线性地址空间有限,因此采取上面介绍的三种方式来映射高端内存。但是每种映射的本质都是通过页表来建立线性地址与物理地址之间的联系。

永久内核映射和临时内核映射,都由内核指定了需要进行映射的页面,也就是说指定了页描述符(页描述符和物理页框之间的关系是固定不可变的)。在永久内核映射中,内核只需要在永久内核映射区找到空闲的,也就是未被映射的线性地址对应的页表项,然后将其分配给page即可,若找不到则将阻塞申请建立映射的进程;而临时内核映射更直接,连进行映射的线性地址窗口都是固定的,若是其已经分配给了某个页框,则直接抢过来用,因此之前的映射就被覆盖了,体现出了临时性。

非连续内存分配,内核不用指定具体的页框,只需指定要申请的内存大小,内核将在非连续内存分配区找到一块相应大小虚拟地址空间,然后再由伙伴系统分配页框,还要通过slab分配器为一些数据结构分配内存,最后再用同样的方式(设置PTE表项)来建立映射。