Per-CPU Page Frame Cache (zone->pageset)

 

Per-CPU Page Frame Cache

per-cpu-page-frame-cache-1c

  • 커널에서 메모리 할당은 주로 큰 페이지보다 single 페이지(0-order page) 프레임을 요청하는 경우가 많다.
  • single 페이지 요청인 경우에만 할당 처리 성능을 높이기 위해 각각의 zone에 per-cpu page frame cache를 준비하고 미리 여러 개의 페이지를 준비한 후 요청한 single 페이지에 대해 buddy를 사용하지 않고 곧바로 캐시된 페이지를 요청자에게 전달한다.
  • 기존 커널에서 각 zone에는 hot 캐시와 cold 캐시를  사용해왔었는데 그 용도가 하나로 통합되었고 hot 요청인 경우 준비된 캐시 페이지 중 앞쪽을 사용하게 하고 cold 요청인 경우 뒷 쪽 페이지를 사용하게 한다.
  • 커널 2.6.25-rc1 이후 부터 각 zone 마다 migratetype 수 만큼 캐시 배열로 관리한다.
  • 커널 모니터가 캐시 페이지가 low 워터마크 수 이하로 떨어지는 것을 커널 모니터가 detect하면 미리 batch 수 만큼 페이지를 캐시에 할당해 놓는다.
  • single 페이지 요청 시 캐시된 페이지가 없는 경우에는 batch 수 만큼 페이지를 캐시에 할당 받은 후 그 중 한 페이지를 요청자에게 전달한다.
  • single 페이지 해제 시 캐시된 페이지가 high 이상인 경우 batch 수 만큼 버디 시스템에 되돌린다.

 

drain_all_pages()

/*
 * Spill all the per-cpu pages from all CPUs back into the buddy allocator.
 *      
 * When zone parameter is non-NULL, spill just the single zone's pages.
 *
 * Note that this code is protected against sending an IPI to an offline
 * CPU but does not guarantee sending an IPI to newly hotplugged CPUs:
 * on_each_cpu_mask() blocks hotplug and won't talk to offlined CPUs but
 * nothing keeps CPUs from showing up after we populated the cpumask and
 * before the call to on_each_cpu_mask().
 */
void drain_all_pages(struct zone *zone)
{
        int cpu;

        /*
         * Allocate in the BSS so we wont require allocation in
         * direct reclaim path for CONFIG_CPUMASK_OFFSTACK=y
         */
        static cpumask_t cpus_with_pcps;

        /*
         * We don't care about racing with CPU hotplug event
         * as offline notification will cause the notified
         * cpu to drain that CPU pcps and on_each_cpu_mask
         * disables preemption as part of its processing
         */ 
        for_each_online_cpu(cpu) {
                struct per_cpu_pageset *pcp;
                struct zone *z;
                bool has_pcps = false;

                if (zone) {
                        pcp = per_cpu_ptr(zone->pageset, cpu);
                        if (pcp->pcp.count)
                                has_pcps = true;
                } else {
                        for_each_populated_zone(z) {
                                pcp = per_cpu_ptr(z->pageset, cpu);
                                if (pcp->pcp.count) {
                                        has_pcps = true;
                                        break;
                                }
                        }
                }

                if (has_pcps)
                        cpumask_set_cpu(cpu, &cpus_with_pcps);
                else
                        cpumask_clear_cpu(cpu, &cpus_with_pcps);
        }
        on_each_cpu_mask(&cpus_with_pcps, (smp_call_func_t) drain_local_pages,
                                                                zone, 1);
}

지정된 zone의 모든 online cpu에 있는 Per-CPU Page Frame Cache를 버디 메모리 할당자로 옮긴다. zone을 지정하지 않은 경우는 모든 populated zone에 대해 수행한다.

 

drain_local_pages()

mm/page_alloc.c

void drain_local_pages(struct zone *zone)
{
        int cpu = smp_processor_id();

        if (zone)
                drain_pages_zone(cpu, zone);
        else
                drain_pages(cpu);
}

 

 


페이지 할당자의 버디 시스템용 PCP(Per-Cpu Page frame cache)의 Drain

drain_pages()

mm/page_alloc.c

/*
 * Drain pcplists of all zones on the indicated processor.
 *
 * The processor must either be the current processor and the
 * thread pinned to the current processor or a processor that
 * is not online.
 */
static void drain_pages(unsigned int cpu)
{
        struct zone *zone;

        for_each_populated_zone(zone) {
                drain_pages_zone(cpu, zone);
        }
}

활성화된 zone 모두에 대해 Per-Cpu Page Fram Cache를 비운다.

 

drain_pages_zone()

mm/page_alloc.c

/*
 * Drain pcplists of the indicated processor and zone.
 *
 * The processor must either be the current processor and the
 * thread pinned to the current processor or a processor that
 * is not online.
 */
static void drain_pages_zone(unsigned int cpu, struct zone *zone)
{
        unsigned long flags;
        struct per_cpu_pageset *pset;
        struct per_cpu_pages *pcp;

        local_irq_save(flags);
        pset = per_cpu_ptr(zone->pageset, cpu);

        pcp = &pset->pcp;
        if (pcp->count) {
                free_pcppages_bulk(zone, pcp->count, pcp);
                pcp->count = 0;
        }
        local_irq_restore(flags);
}

요청 zone에 대한 Per-Cpu Page Fram Cache에 등록된 페이지들 모두 buddy 시스템으로 이주시킨다.

 


pcp -> 버디시스템으로 이주

free_pcppages_bulk()

mm/page_alloc.c

/*
 * Frees a number of pages from the PCP lists
 * Assumes all pages on list are in same zone, and of same order.
 * count is the number of pages to free.
 *
 * If the zone was previously in an "all pages pinned" state then look to
 * see if this freeing clears that state.
 *
 * And clear the zone's pages_scanned counter, to hold off the "all pages are
 * pinned" detection logic.
 */
static void free_pcppages_bulk(struct zone *zone, int count,
                                        struct per_cpu_pages *pcp)
{
        int migratetype = 0;
        int batch_free = 0;
        int to_free = count;
        unsigned long nr_scanned;

        spin_lock(&zone->lock);
        nr_scanned = zone_page_state(zone, NR_PAGES_SCANNED);
        if (nr_scanned)
                __mod_zone_page_state(zone, NR_PAGES_SCANNED, -nr_scanned);

        while (to_free) {
                struct page *page;
                struct list_head *list;

                /*
                 * Remove pages from lists in a round-robin fashion. A
                 * batch_free count is maintained that is incremented when an
                 * empty list is encountered.  This is so more pages are freed
                 * off fuller lists instead of spinning excessively around empty
                 * lists
                 */
                do {
                        batch_free++;
                        if (++migratetype == MIGRATE_PCPTYPES)
                                migratetype = 0;
                        list = &pcp->lists[migratetype];
                } while (list_empty(list));

                /* This is the only non-empty list. Free them all. */
                if (batch_free == MIGRATE_PCPTYPES)
                        batch_free = to_free;

                do {
                        int mt; /* migratetype of the to-be-freed page */

                        page = list_entry(list->prev, struct page, lru);
                        /* must delete as __free_one_page list manipulates */
                        list_del(&page->lru);
                        mt = get_freepage_migratetype(page);
                        if (unlikely(has_isolate_pageblock(zone)))
                                mt = get_pageblock_migratetype(page);

                        /* MIGRATE_MOVABLE list may include MIGRATE_RESERVEs */
                        __free_one_page(page, page_to_pfn(page), zone, 0, mt);
                        trace_mm_page_pcpu_drain(page, 0, mt);
                } while (--to_free && --batch_free && !list_empty(list));
        }
        spin_unlock(&zone->lock);
}

요청 zone에 대한 Per-Cpu Page Fram Cache를 비운다. 그리고 비운 페이지 수 만큼 vm_stat에서 감소시킨다.

  • nr_scanned = zone_page_state(zone, NR_PAGES_SCANNED);
    • zone->vm_stat[NR_PAGES_SCANNED]를 알아온다.
  • if (nr_scanned) __mod_zone_page_state(zone, NR_PAGES_SCANNED, -nr_scanned);
    • 읽어온 nr_scanned 만큼 zone->vm_stat[NR_PAGES_SCANNED]를 감소시킨다.
  • while (to_free) {
    • free할 페이지 수 만큼
  • do { batch_free++;
    • batch_free 카운터는 리스트가 empty될 때 마다 증가되게 하였다.
      • pcp의 3개 리스트에서 로드밸런싱을 위해 1개씩 처리하게한다.
      • 단 list가 비게 되는 경우 다음 list로 이동하게 되면 batch_free를 증가시킨다.
        • 리스트가 비게 되면 너무 spin 되는 것을 억제하게 하기 위해 batch_free를 추가 증가시킨다.
        • 처음에는 1, 리스트가 비면 2, 리스트가 두 개 비면 3이 된다.
  • if (++migratetype == MIGRATE_PCPTYPES) migratetype = 0;
    • migratetype를 1 -> 2 -> 0이 되도록 카운터를 조정한다.
      • MIGRATE_RECLAIMABLE(1)  -> MIGRATE_MOVABLE(2) -> MIGRATE_UNMOVABLE(0)
  • } while (list_empty(list));
    • 리스트가 비어 있는 경우 다른 migrate type의 리스트를 선택하도록 계속한다.
  • if (batch_free == MIGRATE_PCPTYPES) batch_free = to_free;
    • batch_free 카운터가 3이면 batch_free = to_free(free할 남은 페이지 수)를 대입한다.
      • 2개의 list[]가 빈 후 마지막 lists[]만 항목이 남아 있는 경우 batch_free 카운터가 3이될 수 있다.  이러한 경우 아래 루프에서 전체를 삭제할 수 있도록 batch_free 카운터를 to_free 카운터와 동일하게 한다.
  • page = list_entry(list->prev, struct page, lru);
    • 양방향 리스트의 마지막 페이지
  • list_del(&page->lru);
    • 리스트에서 연결을 삭제한다.
  • mt = get_freepage_migratetype(page);
    • page->index에 저장된 migratetype을 가져온다.
    • 버디 시스템에 보내기 위해 pcp의 list[]에서 사용된 migratetype을 사용하지 않고 페이지에 저장된 migratetype을 사용한다.
  • if (unlikely(has_isolate_pageblock(zone))) mt = get_pageblock_migratetype(page);
    • 드문 확률로 지정된 zone에 isolate page가 존재하는 경우 해당 페이지 블럭이 다시 갱신되었을 가능성이 있기 때문에 페이지 블럭에서 migratetype을 가져온다.
  • __free_one_page(page, page_to_pfn(page), zone, 0, mt);
    • pcp에서 제거된 페이지를 buddy 메모리 할당자로 이주한다.
      • 버디 시스템의 free_area[0].free_list[mt]에 페이지를 hot 방향으로 추가 한다.
  • } while (–to_free && –batch_free && !list_empty(list));
    • 감소시킨 to_free 카운터가 남아있으면서 역시 감소시킨 batch_free 카운터도 남아 있으면서 리스트가 비어 있지 않은 경우 계속 루프를 돈다.

 

다음 그림은 free_pcppages_bulk()의 순서도이다.

 

아래 그림은 pcp가 overflow되어 batch 수 만큼 buddy로 이주하는 과정과 순서를 보여준다.

free_pcppages_bulk-2b

  • free_list[0] 슬롯으로 페이지가 이주될 때 free_list[0]에 buddy 페이지가 존재하는 경우 buddy 페이지를 제거하고 다음 order인 free_list[1]으로 합쳐서 추가한다. 동일하게 free_list[1]에서도 buddy 페이지가 발견되면 다음 order로 통합하면서 buddy 페이지가 발견되지 않을 때까지 통합한다.

 

다음 그림은 pcp에서 버디로 옮겨지는 페이지의 순서를 보여준다.

free_pcppages_bulk-1a

 

다음과 같이 zone별 pagesets에 대한 카운터 정보를 확인할 수 있다.

pi@pi /proc $ cat zoneinfo
Node 0, zone   Normal
  pages free     190861
        min      2048
        low      2560
        high     3072
        scanned  0
        spanned  241664
        present  241664
        managed  233403
    nr_free_pages 190861
    nr_alloc_batch 222
    nr_inactive_anon 43
    nr_active_anon 1709
    nr_inactive_file 3525
    nr_active_file 28293
    nr_unevictable 0
    nr_mlock     0
    nr_anon_pages 1698
    nr_mapped    1722
    nr_file_pages 31872
    nr_dirty     0
    nr_writeback 0
    nr_slab_reclaimable 4469
    nr_slab_unreclaimable 1884
    nr_page_table_pages 128
    nr_kernel_stack 88
    nr_unstable  0
    nr_bounce    0
    nr_vmscan_write 0
    nr_vmscan_immediate_reclaim 0
    nr_writeback_temp 0
    nr_isolated_anon 0
    nr_isolated_file 0
    nr_shmem     54
    nr_dirtied   47369
    nr_written   41212
    nr_pages_scanned 0
    workingset_refault 0
    workingset_activate 0
    workingset_nodereclaim 0
    nr_anon_transparent_hugepages 0
    nr_free_cma  935
        protection: (0, 0)
  pagesets
    cpu: 0
              count: 50
              high:  186
              batch: 31
  vm stats threshold: 24
    cpu: 1
              count: 106
              high:  186
              batch: 31
  vm stats threshold: 24
    cpu: 2
              count: 153
              high:  186
              batch: 31
  vm stats threshold: 24
    cpu: 3
              count: 156
              high:  186
              batch: 31
  vm stats threshold: 24
  all_unreclaimable: 0
  start_pfn:         0
  inactive_ratio:    1

 

참고

 

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다