migrate type——buddy system的好搭档

          Linux一共定义5中migrate type,如下:

#define MIGRATE_UNMOVABLE     0
#define MIGRATE_RECLAIMABLE   1
#define MIGRATE_MOVABLE       2  /* 未见有使用该标志位的代码*/
#define MIGRATE_PCPTYPES      3 /* the number of types on the pcp lists */
#define MIGRATE_RESERVE       3
#define MIGRATE_ISOLATE       4 /* can't allocate from here */
#define MIGRATE_TYPES         5

          为什么要定义migrate type,可以参考linux内核对伙伴系统的改进--migrate_type。简要来说就是Linux将可移动或不可移动的内存分开分配,以便最大程度的减少碎片,提升分配大块内存的能力。基于这种思路,可见上述5种migrate type最重要的就是MIGRATE_UNMOVABLE,MIGRATE_MOVABLE。另一个重要的type就是MIGRATE_RESERVE,这表示page被reserve,不接受buddy system管理,可参见函数setup_zone_migrate_reserve。在启动时,会依据water mark min来reserve一些page做备用。
这三个type是用到最多的三种,下面的fallback机制也用到了
static int fallbacks[MIGRATE_TYPES][MIGRATE_TYPES-1] = {
     [MIGRATE_UNMOVABLE]   = { MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE,   MIGRATE_RESERVE },
     [MIGRATE_RECLAIMABLE] = { MIGRATE_UNMOVABLE,   MIGRATE_MOVABLE,   MIGRATE_RESERVE },
     [MIGRATE_MOVABLE]     = { MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE, MIGRATE_RESERVE },
     [MIGRATE_RESERVE]     = { MIGRATE_RESERVE,     MIGRATE_RESERVE,   MIGRATE_RESERVE }, /* Never used */
};

migrate type在memmap_init中初始化为MIGRATE_MOVABLE,如下:

void __meminit memmap_init_zone(unsigned long size, int nid, unsigned long zone,
          unsigned long start_pfn, enum memmap_context context)
{
     struct page *page;
     unsigned long end_pfn = start_pfn + size;
     unsigned long pfn;
     struct zone *z;

     if (highest_memmap_pfn < end_pfn - 1)
          highest_memmap_pfn = end_pfn - 1;

     z = &NODE_DATA(nid)->node_zones[zone];
     for (pfn = start_pfn; pfn < end_pfn; pfn++) {
          ……
          page = pfn_to_page(pfn);
          ……
          SetPageReserved(page);
         
/*
          * Mark the block movable so that blocks are reserved for
          * movable at startup. This will force kernel allocations
          * to reserve their blocks rather than leaking throughout
          * the address space during boot when many long-lived
          * kernel allocations are made. Later some blocks near
          * the start are marked MIGRATE_RESERVE by
          * setup_zone_migrate_reserve()
          *
          * bitmap is created for zone's valid pfn range. but memmap
          * can be created for invalid pages (for alignment)
          * check here not to call set_pageblock_migratetype() against
          * pfn out of zone.
          */

          if ((z->zone_start_pfn <= pfn)
              && (pfn < z->zone_start_pfn + z->spanned_pages)
              && !(pfn & (pageblock_nr_pages - 1)))
               set_pageblock_migratetype(page, MIGRATE_MOVABLE);

          INIT_LIST_HEAD(&page->lru);
          ……
     }

}

上面的函数,遍历memmap数组,也就是该zone所有页面的page结构体,
1)首先设置page->flags的PG_reserved位,意思是不受内存管理系统控制。没错,此时仍在bootmem控制下,之后再__free_pages_bootmem时,会清除该位,将其纳入buddy系统。
2)之后set_pageblock_migratetype将所有page的migrate type设为MIGRATE_MOVABLE。之前提到的按照water mark min来reserve一些页面,顺序上在这个函数的后面才会被调用。
3)再看一下migrate type的初始化函数,set_pageblock_migratetype -> set_pageblock_flags_group
void set_pageblock_flags_group(struct page *page, unsigned long flags,
                         int start_bitidx, int end_bitidx)
{
     struct zone *zone;
     unsigned long *bitmap;
     unsigned long pfn, bitidx;
     unsigned long value = 1;

     zone = page_zone(page);
     pfn = page_to_pfn(page);
     bitmap = get_pageblock_bitmap(zone, pfn);
     bitidx = pfn_to_bitidx(zone, pfn);

     for (; start_bitidx <= end_bitidx; start_bitidx++, value <<= 1)
          if (flags & value)
               __set_bit(bitidx + start_bitidx, bitmap);
          else
               __clear_bit(bitidx + start_bitidx, bitmap);
}
     没有想象的那么简单吧,其中有一个bitmap,他是什么呢?如下:
static unsigned long __init usemap_size(unsigned long zonesize)
{
     unsigned long usemapsize;

     usemapsize = roundup(zonesize, pageblock_nr_pages);
     usemapsize = usemapsize >> pageblock_order;
     usemapsize *= NR_PAGEBLOCK_BITS;
     usemapsize = roundup(usemapsize, 8 * sizeof(unsigned long));

     return usemapsize / 8;
}

static void __init setup_usemap(struct pglist_data *pgdat,
                    struct zone *zone, unsigned long zonesize)
{
     unsigned long usemapsize = usemap_size(zonesize);
     zone->pageblock_flags = NULL;
     if (usemapsize)
          zone->pageblock_flags = alloc_bootmem_node(pgdat, usemapsize);
}
               所谓的bitmap就是zone->pageblock_flags。每个zone被分割成若干个pageblock,每个pageblock分配3位来表示migrate type。pageblock其实就是freearea中order最大的一块内存,从下面的代码可以看出来:
                    free_area_init_core -> set_pageblock_order(pageblock_default_order())
               
static inline int pageblock_default_order(void)
{
     if (HPAGE_SHIFT > PAGE_SHIFT)
          return HUGETLB_PAGE_ORDER;

     return MAX_ORDER-1;
}
               在使用的时候,用get_pageblock_migratetype(page)来获得本page的migrate type。因为只要没有做migrate type的迁移,每个page的migrate type总是和他所在的pageblock是一致的。migrate type只会在fallback机制中改变。也就是分配内存,在本migrate type中无内存可分配,就要去备用的migrate type列表中找最大块内存,迁移过来,再不济还有reserved的页面。

               下面分析一下migrate type是如何改变的。还是有点小复杂的。
               
          1)首先所有讲到fallback机制的资料上都会提醒我们注意,在从backup migratetype中取内存时,是从大的order往小order递减,直到我们需要的order,如果没有就返回NULL
          2)第二点值得注意的是,并不是fallback机制一起作用,page的migratetype就会改变。而是在满足一定的条件的情况下才会发生。条件就是红圈中的,以及下面的page moved >= pageblock / 2。什么意思呢?就是说,当找到的这块大内存的order超过pageblock_order的一半,以7800为例,default pageblock_order是MAX_ORDER-1 = 10,则这块内存order要超过5,就是32个page。第二个条件就是,这块大内存所在的pageblock上有超过一半的空闲buddy。若满足这两点,这块内存所在的pageblock的migratetype会被修改,那么属于他且已分配出去的buddy在释放时,会被合并到新的migratetype中。
          3)我们在测试上面所说的第二个条件时,会先将pageblock中空闲的buddy移到新的migratetype中,但并不真正的改变其migratetype。因为涉及到获取migratetype的操作,都是通过函数get_pageblock_migratetype来实现的。所以只要不修改整块pageblock的migratetype就不会影响到其下所有buddy的migratetype属性。
          4)当找到的这块大内存超过pageblock时,会将其下所有的pageblock的migratetype都修改成我们所需要的migratetype。其实这里反映出的思想和上面是一样的,就是找到的空闲内存足够大,就迁移migratetype。
          5)最后值得注意的一点就是,红圈中的条件判断其实不完整,真实代码中的判断是这样的:
if (unlikely(current_order >= (pageblock_order >> 1)) ||
                         start_migratetype == MIGRATE_RECLAIMABLE ||
                         page_group_by_mobility_disabled) { …… } 
     page_group_by_mobility_disabled表示迁移特性关闭,也就是说页面并不受migratetype所限制,那页面应该可以随心所欲的迁移,实际上确实如此,后面如果page_group_by_mobility_disabled,也并不需要判断有多少free buddy,就可以迁移页面了。
     另外一个,start_migratetype == MIGRATE_RECLAIMABLE就值得推敲了。所有代码中都没有看到对其的使用。网络上对其的解释是:
          可回收页:不能直接移动,但可以删除,其内容可以从某些源重新生成  
     所以我认为MIGRATE_RECLAIMABLE因为很容易被释放(或叫回收),他也是MIGRATE_UNMOVABLE和MIGRATE_MOVABLE的第一替补所以不管找到的空闲页面有多大,都应该尽可能的迁移给他。