Linux内核-内存管理之高端内存

 看了linux内核内存管理,参考网上的意见整理了一下,认为两个比较好的博文:

http://blog.csdn.net/chobit_s/article/details/6029527

http://blog.csdn.net/do2jiang/article/details/5495675


1.页框管理

       Linux采用4KB页框大小作为标准的内存分配单元。内核必须记录每个页框的状态,这种状态信息保存在一个类型为page的页描述符中,所有的页描述存放在mem_map中。virt_to_page(addr)产生线性地址对应的页描述符地址。pfn_to_page(pfn)产生对应页框号的页描述符地址。

       在页框描述符中,几个关键的字段我认为:flags、_count、_mapcount。

       由于CPU对内存的非一致性访问,系统的物理内存被划分为几个节点(每个节点的描述符为pg_data_t),每个节点的物理内存又可以分为3个管理区:ZONE_DMA(低于16M的页框地址),ZONE_NORMAL(16MB-896MB的页框地址)和ZONE_HIGHMEM(高于896MB的页框地址)。每个管理区带有一个管理区分配器,该构件必须分配一个包含足够多的空闲页框的内存区,使它能满足内存请求。

       每个管理区又有自己的描述符,描述了该管理区空闲的页框,保留页数目等。每个页描述符都有到内存节点和到节点管理区的连接(被放在flag的高位字段)。

       内核调用一个内存分配函数时,必须指明请求页框所在的管理区,内核通常指明它愿意使用哪个管理区。

2.保留的页框池

       如果有足够的空闲内存可用、请求就会被立刻满足。否则,必须回收一些内存,并且将发出请求的内核控制路径阻塞,直到有内存被释放。但是有些控制路径不能被阻塞,例如一些内核路径产生一些原子内存分配请求。尽管无法保证一个原子内存分配请求不失败,但是内核会减少这中概率。为了做到如此,内核采取的方案为原子内存分配请求保留一个页框池,只有在内存不足时才使用。页框池有ZONE_DMA和ZONE_NORMAL两个区贡献出一些页框。

       常用的请求页框和释放页框函数:

                 alloc_pages(gfp_mask, order): 获得连续的页框,返回页描述符地址,是其他类型内存分配的基础。
              __get_free_pages(gfp_mask, order): 获得连续的页框,返回页框对应的线性地址。线性地址与物理地址是内核直接映射方式。不能用于大于896M的高端内存。

              __free_pages(page,order);

              __free_pages(addr,order);

3.“每CPU”页框高速缓存

       内存经常请求和释放单个页框。为了提升系统性能,每个内存管理区定义了一个"每CPU"页框高速缓存区。所以“每CPU”高速缓存包含一些预先分配的页框,它们被用于满足本地CPU发出的单一内存请求。实现“每CPU”页框高速缓存的主要数据结构是存放在内存管理区描述符的pageset字段中一个per_cpu_pageset数组数据结构。该数组包含每个CPU提供的一个元素;这个元素由两个per_cpu_pages描述符组成。

       实际上每个内存管理区和每个CPU提供两个高速缓存:一个热高速缓存和一个冷高速缓存。热高速缓存存放的内容可能就在CPU硬件高速缓存中。如果内核或用户进程在刚分配页框后就立即向页框写,那么从热高速缓存中获得页框就对系统有利。如果页框将要被DMA操作,则从冷高速缓存中获得页框是方便的。

       内核中使用两个位标来监视热高速缓存和冷高速缓存的大小:如果页框个数低于下界low,内核从伙伴系统中分配batch个单一页框来补充对应的高速缓存;否则,如果页框高于上界,则从高速缓存中释放batch个页框到伙伴系统中。

       buffered_rmqueue()函数从指定的内存管理区中分配页框。它使用CPU页框高速缓存来处理单一的页框请求。free_hot_page和free_cold_page函数来释放页框到CPU页框高速缓存。

        

4.高端内存页框的内核映射

       高端内存是指物理地址大于 896M 的内存。对于这样的内存,无法在“内核直接映射空间”进行映射。因为“内核直接映射空间”最多只能从 3G 到 4G,只能直接映射 1G 物理内存,对于大于 1G 的物理内存,无能为力。实际上,“内核直接映射空间”也达不到 1G, 还得留点线性空间给“内核动态映射空间” 呢。因此,Linux 规定“内核直接映射空间” 最多映射 896M 物理内存。

      对于高端内存,可以通过 alloc_page() 或者其它函数获得对应的 page,但是要想访问实际物理内存,还得把 page 转为线性地址才行(为什么?想想 MMU 是如何访问物理内存的),也就是说,我们需要为高端内存对应的 page 找一个线性空间,这个过程称为高端内存映射。高端内存映射有三种方式:

(1)永久内核映射

       永久内核映射允许内核建立到高端页框内核地址空间的长期映射。当空闲页表项不存在时,也就是高端内存中没有页表项用用作页框的“窗口”时,永久内核映射可能阻塞当前进程。因此永久内核映射不能用用于中断处理程序和可延迟函数。

        永久内核映射使用主内核页表中的一个专门页表,其地址存放在pkmap_page_table中。页表的表项有LAST_PKMAP产生。该页表映射的线性地址为从PKMAP_BASE开始,即内核专门为此留出一块线性空间,从 PKMAP_BASE 到 FIXADDR_START ,用于映射高端内存。在 2.4 内核上,这个地址范围是 4G-8M 到 4G-4M 之间。这个空间起叫“内核永久映射空间”或者“永久内核映射空间”

        如果是通过 alloc_page() 获得了高端内存对应的 page,如何给它找个线性空间?(就是上面的PKMAP_BASE 到 FIXADDR_START)。

  这个空间和其它空间使用同样的页目录表,对于内核来说,就是 swapper_pg_dir,对普通进程来说,通过 CR3 寄存器指向。

  通常情况下,这个空间是 4M 大小,因此仅仅需要一个页表即可,内核通过来 pkmap_page_table 寻找这个页表。

  通过 kmap(), 可以把一个 page 映射到这个空间来。

  由于这个空间是 4M 大小,最多能同时映射 1024 个 page。因此,对于不使用的的 page,及应该时从这个空间释放掉(也就是解除映射关系),通过 kunmap() ,可以把一个 page 对应的线性地址从这个空间释放出来。

(2)临时内核映射

       建立临时映射决不会要求阻塞当前进程,不过,它的缺点就是只有很少的临时内核映射可以同时建立起来。使用临时内核映射必须保证没有其他的内核控制路径使用同样的映射。

       内核在 FIXADDR_START 到 FIXADDR_TOP 之间保留了一些线性空间用于特殊需求。这个空间称为“固定映射空间“, 在这个空间中,有一部分用于高端内存的临时映射。这块空间具有如下特点: 每个 CPU 占用一块空间;在每个 CPU 占用的那块空间中,又分为多个小空间,每个小空间大小是 1 个 page,每个小空间用于一个目的,这些目的定义在 kmap_types.h 中的 km_type 中。

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

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

(3)映射到“内核动态映射空间”

       这种方式很简单,因为通过 vmalloc() ,在“内核动态映射空间”申请内存的时候,就可能从高端内存获得页面(参看 vmalloc 的实现),因此说高端内存有可能映射到“内核动态映射空间” 中。


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


 

注:Linux内核中采用了一种同时适用于32位和64位系统的内存分页模型,对于32位系统来说,两级页表足够用了,而在x86_64系统中,用到了四级页表

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值