Linuxメモリ管理(2)-buddyシステム


本論文の目的はLinuxメモリ管理メカニズムにおけるパートナーシステムを分析することである.カーネルバージョンは2.6です.31.
1.パートナーシステムの概念
システムの実行中に、連続したページのセットを割り当てる必要があることがよくあります.頻繁な申請とメモリの解放は、メモリに多くの不連続なページが分散します.これにより、ある時点で大きな連続メモリを申請する場合、システムのメモリ残量は十分です.つまり、多くのページは空きですが、大きな連続したメモリが見つかりません.
Linuxカーネルでのパートナーシステムの使用(buddy system)アルゴリズムはメモリページを管理する.すべての空きページを11のチェーンテーブルに配置し、各チェーンテーブルはそれぞれ1,2,4,8,16,32,641282565,121024ページのメモリブロックを管理する.システムがメモリを割り当てる必要がある場合、buddyシステムから取得することができる.例えば、4ページを含む連続メモリを申請するには、buddyシステムから直接取得する4ページ連続メモリを管理するチェーンテーブルで取得します.システムがメモリを解放すると、解放されたメモリはbuddyシステムに対応するチェーンテーブルに戻され、メモリを解放した後、隣接する2つのメモリがさらに1つのより高いレベルのメモリブロックに結合できることが分かった場合、例えば4つのページを解放し、ちょうど隣接するメモリも4つのページの空きメモリである場合、この2つのメモリを統合し、buddyシステムが8つの連続ページを管理するチェーンテーブルに配置する.同様に、システムが3ページの連続メモリを申請する必要がある場合、4ページのチェーンテーブルでしか取得できず、残りの1ページはbuddyシステムで1ページを管理するチェーンテーブルに格納される.
buddyディスペンサが割り当てる最小単位はページです.1ページ未満のメモリを割り当てるにはslabディスペンサが必要ですが、slabはbuddyディスペンサに基づいています.
struct zone {
   ......
   struct free_area   free_area[MAX_ORDER];
   ......
}____cacheline_internodealigned_in_smp;

struct zoneのfree_area[]配列メンバーは各階層の空きメモリリストを格納し、配列の下付きは0~MAX_を取ることができるORDER-1、(MAX_ORDER=11).したがって、各階層のメモリチェーンテーブルはstruct free_area構造を用いて記録される.
struct free_area {
   struct list_head  free_list[MIGRATE_TYPES];
   unsigned long     nr_free;
};

struct free_areaには2人のメンバーがいますfree_List[]は、異なるmigrate type(移行タイプ)ページチェーンテーブルの配列です.(移行タイプとは別に、後述する)では、各移行タイプはstruct pageのチェーンテーブルであり、各struct pageのpage->lruで連結されている.nr_freeは、このorder空きページの数を表し、例えば、階層2の連続ページブロックが3つある場合、nr_free=3であり、実際にはこの階層の空きページ数は(2^2)*3=12である.
2.per-cpuの冷熱ページチェーン表
struct zone構造にはpageset[]メンバーがあります.
struct zone {
   ......
struct per_cpu_pageset  pageset[NR_CPUS];
......
}____cacheline_internodealigned_in_smp;
 
struct per_cpu_pageset {
   struct per_cpu_pages pcp;
} ____cacheline_aligned_in_smp;
 
struct per_cpu_pages {
   int count;    /* number ofpages in the list */
   int high;     /* highwatermark, emptying needed */
   int batch;    /* chunk sizefor buddy add/remove */
   struct list_head list;   /*the list of pages */
};

便宜上、per cpu pagesetをpcpと略称します.
pageset[]配列はper cpuの冷熱ページを格納するために使用される.CPUが1つのページを解放すると、このページがキャッシュに残っている場合は、ホットであり、その後すぐにアクセスされる可能性が高いと判断し、pagesetリストに配置し、他のページはコールドページと判断します.pagesetの冷熱ページチェーンテーブル要素の数は、per_によって制限されています.cpu_pagesのhighメンバー制御は、結局ホットページが多すぎると、実際に最初に追加されたページはもう暑くありません.
CPUが1ページを解放する場合、buddyシステムに急いで解放するのではなく、数の制限を超えるまでホットページまたはコールドページとしてpcpチェーンテーブルにページを配置しようとします.複数のページを解放するとbuddyシステムに直接解放されます.
per_cpu_pagesのcountメンバーは、チェーンテーブルのページの数を表します.batchは、パートナーシステムからいくつかのページを冷熱ページチェーンテーブルに置く必要がある場合、一度に何ページを持つかを示します.Listメンバーは冷熱ページチェーンテーブルであり、ヘッダーに近づくほど熱くなります.
一般に,カーネルが1ページのメモリを申請したい場合は,まずCPUの冷熱ページチェーンテーブルから申請する.ただし、コールドページを直接申請するとより合理的になる場合があります.cacheのページが無効になる場合があるため、カーネルはメモリページを申請する際にGPF_をマークします.COLDはコールドページを申請することを示します.
冷熱ページ割り当ては、1つのページを割り当てて回収する場合にのみ、複数のページがbuddyを直接操作することに注意してください.
3. alloc_pages_Node()buddyシステムにページを割り当てる
static inline struct page* alloc_pages_node(int nid, gfp_t gfp_mask,
                     unsigned int order)
{
   /* Unknown node is current node */
   if (nid < 0)
       nid = numa_node_id();
 
   return __alloc_pages(gfp_mask, order, node_zonelist(nid,gfp_mask));
}

この関数の3つのパラメータは次のとおりです.
Nid:ノードid、UMAシステムは0です.
gfp_mask:GFP(get free page)マスク、include/linux/gfp.hで定義します.
order:4ページを割り当てるとorder=2になります.
node_zonelist()は、ノードのzoneスタンバイリストであるNODEを返します.DATA(nid)->node_zonelists[].
この関数の最終呼び出し_alloc_pages_Nodemask()は実際の割り当て作業を行い、この関数の注釈は「This is the'heart'of the zoned buddy allocator」である.この関数はgpf_に基づいてflagsは適切なzoneを探して関数get_を呼び出しますpage_from_freelist()は次の作業を行い、この関数を簡略化した後の実現は以下の通りである.
static struct page *
get_page_from_freelist(gfp_tgfp_mask, nodemask_t *nodemask, unsigned int order,
       struct zonelist *zonelist, int high_zoneidx, int alloc_flags,
       struct zone *preferred_zone, int migratetype)
{
   struct zoneref *z;
   struct page *page = NULL;
   int classzone_idx;
   struct zone *zone;
 
   /*   ZONE_NORMAL,     0 */
   classzone_idx = zone_idx(preferred_zone);
 
/*
    * Scan zonelist, lookingfor a zone with enough free.
    * See alsocpuset_zone_allowed() comment in kernel/cpuset.c.
    */
   for_each_zone_zonelist_nodemask(zone, z, zonelist,
                     high_zoneidx, nodemask) {
       /*       NO_WATERMARKS */
       if (!(alloc_flags & ALLOC_NO_WATERMARKS)) {
          unsigned long mark;
          int ret;
 
          /*          */
          mark = zone->watermark[alloc_flags &ALLOC_WMARK_MASK];
          /*                */
          if (zone_watermark_ok(zone,order, mark,
                 classzone_idx, alloc_flags))
              goto try_this_zone;
       }
 
try_this_zone:
       page = buffered_rmqueue(preferred_zone,zone, order,
                     gfp_mask, migratetype);
       if (page)
          break;
   }
   return page;
}

ここで呼び出される関数は主に2つです.
zone_watermark_OK()は透かし制限(zone->watermark[])を判断するために使用され、割り当てるorderが透かし制限を超えている場合は、システムで使用可能なメモリページが不足しているため、割り当てを続行できません.
/*
 * Return 1 if free pages are above 'mark'.This takes into account the order
 * of the allocation.
 */
int zone_watermark_ok(struct zone *z, int order, unsigned long mark,
             int classzone_idx,int alloc_flags)
{
   /* free_pages my go negative - that's OK */
   long min = mark;
   long free_pages = zone_page_state(z, NR_FREE_PAGES) - (1 <lowmem_reserve[classzone_idx])
       return 0;
 
   /*      , order            ,   。 */
   for (o = 0; o < order; o++) {
       /* At the next order, this order's pages become unavailable */
       free_pages -= z->free_area[o].nr_free << o;
 
       /* Require fewer higher order pages to be free */
       min >>= 1;
 
       if (free_pages <= min)
          return 0;
   }
   return 1;
}

注意、init_per_zone_wmark_min()関数ではzoneごとの透かしとlowmem_が初期化されているreserveなどの制限.この関数はmodule_Init()が組み込まれ、カーネルに組み込まれているため、システム起動時に自動的に実行されます.
buffered_rmqueue()は、実際の割当てページの作業を行います.
static inline
struct page *buffered_rmqueue(structzone *preferred_zone,
          struct zone *zone, int order, gfp_t gfp_flags,
          int migratetype)
{
   unsigned long flags;
   struct page *page;
   int cold = !!(gfp_flags & __GFP_COLD);/*     COLD  */
   int cpu;
 
again:
   cpu  = get_cpu();
   if (likely(order == 0)) { /*          */
       /*  pcp    */
   } else { /*          */
       /*  buddy    */
   }
 
   __count_zone_vm_events(PGALLOC, zone, 1 << order);
   zone_statistics(preferred_zone, zone); /*       . */
   local_irq_restore(flags);
   put_cpu();
 
   VM_BUG_ON(bad_range(zone, page));
   /*      */
   if (prep_new_page(page, order, gfp_flags))
       goto again;
   return page;
 
failed:
   local_irq_restore(flags);
   put_cpu();
   return NULL;
}

ページを割り当てる場合は2つに分けられ,1ページだけ割り当てる場合はpcp上で直接行う.複数ページを割り当てる必要がある場合はbuddyシステムに割り当てます.
pcpに割り当てるページを申請します.
1.通過&zone_pcp(zone,cpu)->pcp pcp pcpチェーンテーブルを取得します.
2.pcp->count=0、すなわちpcpチェーンテーブルが空の場合、rmqueue_を使用します.bulk()関数buddyでbatch個の単一ページを取得してpcpチェーンテーブルに配置し、これらのページをbuddyから削除し、zoneのvm_を更新します.stat統計.
3.gfp_によるflagsはGPFがありますかCOLDフラグは、コールドページを割り当てる必要がある場合はpcpチェーンテーブルの末尾からページを取り、ホットページが必要であればチェーンテーブルの先頭からページを取り、得られたページはpageに付与すると判断する.
4.pageをpcpチェーンテーブルから削除する:list_del(&page->lru)、同時にpcp->count--.
複数のページを申請し、buddyに割り当てる:
1.設定が__の場合GFP_NOFAILタグ、order>1の場合、警告が表示されます.
2.使用_rmqueue()は2^orderページを割り当てます.
3.zoneのvm_を更新するstat統計.
buddyで2^orderを申請するページはすべて_を通過しますrmqueue()関数は、主に3つのステップに分けて動作します.
1.呼び出し_rmqueue_smallest()指定zoneのfree_area[order]上の特定migratetypeチェーンテーブルに2^order個のページを割り当ててみます.order段に十分なメモリがない場合は、order+1段の特定migratetypeチェーンテーブルに2^order個のページを割り当ててみます.希望するページに割り当てるまで順番に類推します.
2.申請されたページをbuddyシステムから消去します.また、申請後にbuddyシステムを再調整する必要がある場合があります.例えば、2^1=2ページを割り当てるつもりでしたが、buddyには2ページのパートナーがいないので、2^2=4ページのパートナーで申請すると、残りの2ページをfree_から申請する必要があります.area[2]を削除しfree_に配置area[1]チェーンテーブルでは、この作業はexpand()によって行われる.
3.現在のzoneのbuddy上の特定のmigratetypeのチェーンテーブルに割り当てが成功せず、migratetype!=MIGRATE_RESERVE、使用_rmqueue_fallback()は、代替リストに割り当てられます.
ここではstruct pageの3つのメンバーについて説明する必要があります.
lru:チェーンテーブルノード、struct pageが格納されているチェーンテーブルでは、buddyやpcpのチェーンテーブルなど、lruをノードとしています.
private:このメンバーはどんなに意味があるか、ここで見てみましょう.pageがbuddyシステムにある場合、privateはこのページのfreeです.areaの次数.pageがpcp冷熱ページチェーンテーブルにある場合、privateはmigratetypeです.
flags:buddyシステムにページがある場合、PG_buddyタグは設定されます.そうしないと消去されます.
4.リリースページ
リリースページのインタフェースは__です.free_pages()は、最初のページのポインタpageとorderのパラメータです.
void __free_pages(structpage *page, unsigned int order)
{
   if (put_page_testzero(page)) {
       if (order == 0)
          free_hot_page(page);
       else
          __free_pages_ok(page,order);
   }
}

ページを解放する場合は、pcpに追加し、pcpの制限を超えてbuddyシステムに追加します.複数のページを解放する場合は、free_pages_OK()リリース.
buddyシステムにページを回収するインタフェースは__です.free_one_page().page idxを検索し、既存のbuddyをマージするプロセスです.
static inline void__free_one_page(struct page *page,
       struct zone *zone, unsigned int order,
       int migratetype)
{
   unsigned long page_idx;
 
   if (unlikely(PageCompound(page)))
       if (unlikely(destroy_compound_page(page, order)))
          return;
 
   VM_BUG_ON(migratetype == -1);
 
   /*  mem_map    index */
   page_idx = page_to_pfn(page) & ((1 << MAX_ORDER) - 1);
 
   /*         page_idx   order   ? */
   VM_BUG_ON(page_idx & ((1 << order) - 1));
   VM_BUG_ON(bad_range(zone, page));
 
   /*  order        */
   while (order < MAX_ORDER-1) {
       unsigned long combined_idx;
       struct page *buddy;
 
       /*      page   buddy      */
       buddy = __page_find_buddy(page, page_idx, order);
      
       /*  page buddy    ,       */
       if (!page_is_buddy(page, buddy, order))
          break;
      
       /*     ,  buddy   */
       /* Our buddy is free, merge with it and move up one order. */
       list_del(&buddy->lru);
       zone->free_area[order].nr_free--;
       rmv_page_order(buddy);
 
       /* page buddy  ,  index  page buddy index */
       combined_idx = __find_combined_index(page_idx, order);
       page = page + (combined_idx - page_idx);
       page_idx = combined_idx;
 
       /*             */
       order++;
   }
 
/*     ,    buddy order      order      。 */
   set_page_order(page, order);
   list_add(&page->lru,
       &zone->free_area[order].free_list[migratetype]);
   zone->free_area[order].nr_free++;
}

このコードの論理ははっきりしている.ただし、隣接するアドレスのbuddyのみがマージされるため、実際にはpageのbuddyを解放するページのindexは計算できます.
/*
 * Locate the struct page for both the matchingbuddy in our
 * pair (buddy1) and the combined O(n+1) pagethey form (page).
 *
 * 1) Any buddy B1 will have an order O twin B2which satisfies
 * the following equation:
 *     B2= B1 ^ (1 << O)
 * For example, if the starting buddy (buddy2)is #8 its order
 * 1 buddy is #10:
 *     B2= 8 ^ (1 << 1) = 8 ^ 2 = 10
 *
 * 2) Any buddy B will have an order O+1 parentP which
 * satisfies the following equation:
 *     P= B & ~(1 << O)
 *
 * Assumption: *_mem_map is contiguous at leastup to MAX_ORDER
 */
static inline struct page *
__page_find_buddy(structpage *page, unsigned long page_idx, unsigned int order)
{
   unsigned long buddy_idx = page_idx ^ (1<< order);
 
   return page + (buddy_idx - page_idx);
}

2つのbuddyをマージすると、マージ後のbuddyの開始ページindexの関数が得られます.
static inline unsigned long
__find_combined_index(unsignedlong page_idx, unsigned int order)
{
   return (page_idx & ~(1 <

結合できるかどうかを判断する関数:
/*
 * This function checks whether a page is free&& is the buddy
 * we can do coalesce a page and its buddy if
 * (a) the buddy is not in a hole &&
 * (b) the buddy is in the buddy system&&
 * (c) a page and its buddy have the same order&&
 * (d) a page and its buddy are in the samezone.
 *
 * For recording whether a page is in the buddysystem, we use PG_buddy.
 * Setting, clearing, and testing PG_buddy isserialized by zone->lock.
 *
 * For recording page's order, we usepage_private(page).
 */
static inline intpage_is_buddy(struct page *page, struct page *buddy,
                            int order)
{
   /* buddy   index    。 */
   if (!pfn_valid_within(page_to_pfn(buddy)))
       return 0;
 
   /*        zone。 */
   if (page_zone_id(page) != page_zone_id(buddy))
       return 0;
 
   /*   buddy     PG_buddy  。   page  order 。 */
   if (PageBuddy(buddy) && page_order(buddy) == order) {
       VM_BUG_ON(page_count(buddy) != 0);
       return 1;
   }
   return 0;
}