13.分配页之选择页(zone_watermark_ok和get_page_from_freelist)

 

 

所有API函数都追溯到alloc_pages_node,从某种意义上说,该函数是伙伴系统主要实现的“发射台”。
<gfp.h>
static inline struct page *alloc_pages_node(int nid, gfp_t gfp_mask,unsigned int order)
{
  if (unlikely(order >= MAX_ORDER))
    return NULL;
  /* 未知结点即当前结点 */
  if(nid< 0)
    nid = numa_node_id();
  return __alloc_pages(gfp_mask, order,NODE_DATA(nid)->node_zonelists + gfp_zone(gfp_mask));
}

 

只执行了一个简单的检查,避免分配过大的内存块。如果指定负的结点ID(不存在),内核自动地使用当前执行CPU对应的结点ID。接下来的工作委托给__alloc_pages,只需传递一组适当的参数。请注意,gfp_zone用于选择分配内存的内存域。

 

 

1. 选择页
我们先把注意力转向页面选择是如何工作的。
 辅助函数
首先我们需要定义一些函数使用的标志,用于控制到达各个水印指定的临界状态时的行为。
 
mm/page_alloc.c
#define ALLOC_NO_WATERMARKS 0x01 /* 完全不检查水印 */
#define ALLOC_WMARK_MIN 0x02 /* 使用pages_min水印 */
#define ALLOC_WMARK_LOW 0x04 /* 使用pages_low水印 */
#define ALLOC_WMARK_HIGH 0x08 /* 使用pages_high水印 */
#define ALLOC_HARDER 0x10 /* 试图更努力地分配,即放宽限制 */
#define ALLOC_HIGH  0x20 /* 设置了__GFP_HIGH */
#define ALLOC_CPUSET 0x40 /* 检查内存结点是否对应着指定的CPU集合 */

 

 
前几个标志表示在判断页是否可分配时,需要考虑哪些水印。默认情况下(即没有因其他因素带来的压力而需要更多的内存),只有内存域包含页的数目至少为zone->pages_high时,才能分配页。这对应于ALLOC_WMARK_HIGH标志。如果要使用较低(zone->pages_low)或最低(zone->pages_min)设置,则必须相应地设置ALLOC_WMARK_MIN或ALLOC_WMARK_LOW。ALLOC_HARDER通知伙伴系统在急需内存时放宽分配规则。在分配高端内存域的内存时,ALLOC_HIGH进一步放宽限制。最后,ALLOC_CPUSET告知内核,内存只能从当前进程允许运行的CPU相关联的内存结点分配,当然该选项只对NUMA系统有意义。
设置的标志在zone_watermark_ok函数中检查,该函数根据设置的标志判断是否能从给定的内存域分配内存。
mm/page_alloc.c
int zone_watermark_ok(struct zone *z, int order, unsigned long mark,int classzone_idx, int alloc_flags)
{
  /* free_pages可能变为负值,没有关系 */
  long min = mark;
  long free_pages = zone_page_state(z, NR_FREE_PAGES) -(1 << order) + 1;
  int o;
  if (alloc_flags & ALLOC_HIGH)
    min -= min / 2;
  if (alloc_flags & ALLOC_HARDER)
    min -= min / 4;
  if (free_pages <= min + z->lowmem_reserve[classzone_idx])
    return 0;
  for(o= 0;o <order;o++){
    /* 在下一阶,当前阶的页是不可用的 */  
    free_pages -= z->free_area[o].nr_free << o;
    /* 所需高阶空闲页的数目相对较少 */
    min >>= 1;
    if (free_pages <= min)
      return 0;
  }
  return 1;
}

 

zone_per_state用来访问每个内存域的统计量。在上述代码中,得到的是空闲页的数目。
在解释了ALLOC_HIGH和ALLOC_HARDER标志之后(将最小值标记降低到当前值的一半或四分之一,使得分配过程努力或更加努力),该函数会检查空闲页的数目是否小于最小值与lowmem_reserve中指定的紧急分配值之和。如果不小于,则代码遍历所有小于当前阶的分配阶,从free_pages减去当前分配阶的所有空闲页(左移o位是必要的,因为nr_free记载的是当前分配阶的空闲页块数目)。同时,每升高一阶,所需空闲页的最小值折半。如果内核遍历所有的低端内存域之后,发现内存不足,则不进行内存分配。
 
理解:注意,nr_free记录的是当前分配阶的空闲页块数目,这里突出了一个块字,根据阶的不同,块的大小也就不同,而free_pages得到的是整个内存域空闲页的数目,故需要左移,如果当前阶内存块为空则向更高阶申请内存,所以减去的是比当前阶小的空闲内存页,得到的便是整个内存域大于等于该阶的内存页数目。
 
get_page_from_freelist是伙伴系统使用的另一个重要的辅助函数。它通过标志集和分配阶来判断是否能进行分配。如果可以,则发起实际的分配操作。
mm/page_alloc.c
static struct page *get_page_from_freelist(gfp_t gfp_mask, unsigned int order,struct zonelist *zonelist, int alloc_flags)
{
  struct zone **z;
  struct page *page = NULL;
  int classzone_idx = zone_idx(zonelist->zones[0]);
  struct zone *zone;
...
  /*
  * 扫描zonelist,寻找具有足够空闲空间的内存域。
  * 请参阅kernel/cpuset.c中cpuset_zone_allowed()的注释。
  */
  z = zonelist->zones;
  do {
...
    zone = *z;
    if ((alloc_flags & ALLOC_CPUSET) &&!cpuset_zone_allowed_softwall(zone, gfp_mask))
      continue;
    if (!(alloc_flags & ALLOC_NO_WATERMARKS)) {
      unsigned long mark;
      if (alloc_flags & ALLOC_WMARK_MIN)
        mark = zone->pages_min;
      else if (alloc_flags & ALLOC_WMARK_LOW)
        mark = zone->pages_low;
      else
        mark = zone->pages_high;
      if (!zone_watermark_ok(zone, order, mark,classzone_idx, alloc_flags))
        continue;
}
...

 

该函数的一个参数是指向备用列表的指针。在预期内存域没有空闲空间的情况下,该列表确定了扫描系统其他内存域(和结点)的顺序。该数据结构的布局和语义详见3.内存初始化整体大概流程(建立结点及内存域备用列表)
遍历备用列表的所有内存域,用最简单的方式查找一个适当的空闲内存块。首先,解释ALLOC_*标志(cpuset_zone_allowed_softwall是另一个辅助函数,用于检查给定内存域是否属于该进程允许运行的CPU)。zone_watermark_ok接下来检查所遍历到的内存域是否有足够的空闲页,并试图分配一个连续内存块。如果两个条件之一不能满足,即或者没有足够的空闲页,或者没有连续内存块可满足分配请求,则循环进行到备用列表中的下一个内存域,作同样的检查。
如果内存域适用于当前的分配请求,那么buffered_rmqueue试图从中分配所需数目的页:
mm/page_alloc.c
...
  page = buffered_rmqueue(*z, order, gfp_mask);
    if (page) {
      zone_statistics(zonelist, *z);
      break;
    }
  } while (*(++z) != NULL);
  return page;
}

 

buffered_rmqueue详见未知,如果分配成功,则将页返回给调用者。否则,进入下一个循环,选择备用列表中的下一个内存域。
 
posted @ 2022-03-20 18:01  while(true);;  阅读(362)  评论(0)    收藏  举报