Zoned Allocator -3- (Watermark)

Zone 워터마크 관련

zone_watermark_ok()

mm/page_alloc.c

bool zone_watermark_ok(struct zone *z, unsigned int order, unsigned long mark,
                      int classzone_idx, int alloc_flags)
{
        return __zone_watermark_ok(z, order, mark, classzone_idx, alloc_flags,
                                        zone_page_state(z, NR_FREE_PAGES));
}

해당 zone에서 order 페이지를 처리하기 위해 free page가 워터마크 값의 기준 보다 충분한지 확인한다.

 

__zone_watermark_ok()

mm/page_alloc.c

static bool __zone_watermark_ok(struct zone *z, unsigned int order,
                        unsigned long mark, int classzone_idx, int alloc_flags,
                        long free_pages)
{
        /* free_pages may go negative - that's OK */
        long min = mark;
        int o;
        long free_cma = 0;

        free_pages -= (1 << order) - 1;
        if (alloc_flags & ALLOC_HIGH)
                min -= min / 2;
        if (alloc_flags & ALLOC_HARDER)
                min -= min / 4;
#ifdef CONFIG_CMA
        /* If allocation can't use CMA areas don't use free CMA pages */
        if (!(alloc_flags & ALLOC_CMA))
                free_cma = zone_page_state(z, NR_FREE_CMA_PAGES);
#endif

        if (free_pages - free_cma <= min + z->lowmem_reserve[classzone_idx])
                return false;
        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 false;
        }
        return true;
}

해당 zone에서 order 페이지를 처리하기 위해 free page가 워터마크 값의 기준 보다 충분한지 확인한다.

  • free_pages -= (1 << order) – 1;
    • free_pages를 2^order-1 페이지 만큼 감소시킨다.
  • if (alloc_flags & ALLOC_HIGH) min -= min / 2;
    • ALLOC_HIGH 플래그가 설정된 경우 min 값을 절반으로 줄인다.
  • if (alloc_flags & ALLOC_HARDER) min -= min / 4;
    • ALLOC_HARDER 플래그가 설정된 경우 min 값을 1/4로 줄인다.
  • if (!(alloc_flags & ALLOC_CMA)) free_cma = zone_page_state(z, NR_FREE_CMA_PAGES);
    • ALLOC_CMA 플래그가 설정되지 않은 경우 free_cma 카운터를 알아온다.
  • if (free_pages – free_cma <= min + z->lowmem_reserve[classzone_idx]) return false;
    • free_cma 페이지를 제외한 free_pages가 classzone_idx로 지정한 lwmem_reserve를 포함한 min 값을 초과하는 경우 false를 반환한다.
  • for (o = 0; o < order; o++) { free_pages -= z->free_area[o].nr_free << o; min >>= 1; if (free_pages <= min) return false; }
    • 0부터 요청 order까지 루프를 돌며 등록된 페이지들의 수를 free_pages에서 감소시키고, min 값을 절반으로 감소시킨 후 free_pages가 min 값 이하인 경우 compaction 또는 reclaim에 문제가 발생할 가능성이 있어 false를 반환한다.
      • 이 알고리즘은 루프 수행 중 특정 order의 nr_free가 0일 때에도 min을 절반씩 줄이는 등 계산이 정확하지 않다.
      •  high order 페이지 할당 요청 시 워터마크 이하의 영역을 이용하여 compaction이 가능한지 여부를 정확히 계산하기 위해 이 부분의 알고리즘이 커널 v4.4-rc1에서 수정되었다.

 

다음 그림은 zone 워터마크 설정에 따른 할당과 페이지 회수에 따른 관계를 보여준다.

zone-watermark-1b

 

다음 그림은 요청한 워터마크와 free size를 order 루프만큼 비교하여 free size가 충분한 경우 ok가 되는 모습을 보여준다.

  • 위에서 소개하였듯이 이 루틴의 정확도가 떨어져 커널 4.4-rc1에서 수정되었다.

zone_watermark_ok-1a

 

다음 그림은 zone 워터마크 설정에 따른 할당과 페이지 회수에 따른 관계를 보여준다. 1) ~4)번 까지의 수식 값을 감소시킨 남은 free_pages 수가 워터마크 기준-5) 제한 완화 포함- 이하일 때 요청한 order의 페이지가 버디시스템에 있는지 체크하는 과정을 보여준다 (커널 4.6 기준)

 

워터마크 초기값 설정

init_per_zone_wmark_min()

mm/page_alloc.c

/*
 * Initialise min_free_kbytes.
 *
 * For small machines we want it small (128k min).  For large machines
 * we want it large (64MB max).  But it is not linear, because network
 * bandwidth does not increase linearly with machine size.  We use
 *
 *      min_free_kbytes = 4 * sqrt(lowmem_kbytes), for better accuracy:
 *      min_free_kbytes = sqrt(lowmem_kbytes * 16)
 *
 * which yields
 *
 * 16MB:        512k
 * 32MB:        724k
 * 64MB:        1024k
 * 128MB:       1448k
 * 256MB:       2048k
 * 512MB:       2896k
 * 1024MB:      4096k
 * 2048MB:      5792k
 * 4096MB:      8192k
 * 8192MB:      11584k
 * 16384MB:     16384k
 */
int __meminit init_per_zone_wmark_min(void)
{
        unsigned long lowmem_kbytes;
        int new_min_free_kbytes;

        lowmem_kbytes = nr_free_buffer_pages() * (PAGE_SIZE >> 10);
        new_min_free_kbytes = int_sqrt(lowmem_kbytes * 16);

        if (new_min_free_kbytes > user_min_free_kbytes) {
                min_free_kbytes = new_min_free_kbytes;
                if (min_free_kbytes < 128)
                        min_free_kbytes = 128;
                if (min_free_kbytes > 65536)
                        min_free_kbytes = 65536;
        } else {
                pr_warn("min_free_kbytes is not updated to %d because user defined value %d is preferred\n",
                                new_min_free_kbytes, user_min_free_kbytes);
        }
        setup_per_zone_wmarks();
        refresh_zone_stat_thresholds();
        setup_per_zone_lowmem_reserve();
        setup_per_zone_inactive_ratio();
        return 0;
}
module_init(init_per_zone_wmark_min)

메타 데이터(lowmem에서의 mem_map 및 lowmem_reserve)를 제외한 lowmem 크기에 따른 전역 min_free_kbytes 값을 초기화한다. 그 외에 각 zone의 워터마크, lowmem_reserve와 inactive_ratio 등을 설정한다.

  • lowmem_kbytes = nr_free_buffer_pages() * (PAGE_SIZE >> 10);
    • 현재 노드의 zonelist에서 ZONE_NORMAL 이하의 zone들을 대상으로 high 워터마크 페이지를 제외한 페이지 수를 kbyte 단위로 바꾼다.
      • 커널이 초기화될 때 이외에도 hotplug 메모리를 사용하여 메모리가 hot add/del 될 때마다 이 초기화 루틴이 수행된다.
      • 커널이 처음 초기화될 때에는 각 zone의 워터마크 초기값은 0이다.
  •  new_min_free_kbytes = int_sqrt(lowmem_kbytes * 16);
    • 메타 데이터(lowmem에서의 mem_map 및 lowmem_reserve)를 제외한 lowmem 크기 * 16을 한 후 제곱근(루트)한 수를 얻어온다.
      • 예) sqrt(512M * 16) -> 2M
  • if (new_min_free_kbytes > user_min_free_kbytes) { min_free_kbytes = new_min_free_kbytes;
    • 산출한 값이 user_min_free_kbytes(초기값: -1)를 초과한 경우 전역 min_free_kbytes를 갱신한다.
  • if (min_free_kbytes < 128) min_free_kbytes = 128; if (min_free_kbytes > 65536) min_free_kbytes = 65536; }
    • 전역 min_free_kbytes 값을 128 ~ 65536 범위로 clamp한다.
  • setup_per_zone_wmarks();
    • 각 zone에 대한 threshold들을 갱신한다.
      • 각 cpu의 zone->pageset->stat_threshold 갱신
      • zone->percpu_drift_mark 갱신
  • setup_per_zone_lowmem_reserve();
    • zone->lowmem_reserve 갱신
    • 전역 dirty_balance_reserve, totalreserve_pages 갱신
  • setup_per_zone_inactive_ratio();
    • zone별 inactive anon 비율을 산출한다.
      • zone->inactive_ratio 갱신

 

다음 그림은 전역 min_free_kbytes 값이 산출되는 과정을 보여준다.

init_per_zone_wmark_min-1

 

nr_free_buffer_pages()

mm/page_alloc.c

/**
 * nr_free_buffer_pages - count number of pages beyond high watermark
 *
 * nr_free_buffer_pages() counts the number of pages which are beyond the high
 * watermark within ZONE_DMA and ZONE_NORMAL.
 */
unsigned long nr_free_buffer_pages(void)
{
        return nr_free_zone_pages(gfp_zone(GFP_USER));
}
EXPORT_SYMBOL_GPL(nr_free_buffer_pages);

현재 노드의 zonelist에서 ZONE_NORMAL 이하의 zone들을 대상으로 high 워터마크 페이지를 제외한 페이지 수를 반환한다.

  • 참고로 처음 커널 설정을 위해 호출될 때에는 high 워터마크가 0이다.

 

nr_free_zone_pages()

mm/page_alloc.c

/**
 * nr_free_zone_pages - count number of pages beyond high watermark
 * @offset: The zone index of the highest zone
 *
 * nr_free_zone_pages() counts the number of counts pages which are beyond the
 * high watermark within all zones at or below a given zone index.  For each
 * zone, the number of pages is calculated as:
 *     managed_pages - high_pages
 */
static unsigned long nr_free_zone_pages(int offset)
{
        struct zoneref *z;
        struct zone *zone;

        /* Just pick one node, since fallback list is circular */
        unsigned long sum = 0;

        struct zonelist *zonelist = node_zonelist(numa_node_id(), GFP_KERNEL);

        for_each_zone_zonelist(zone, z, zonelist, offset) {
                unsigned long size = zone->managed_pages;
                unsigned long high = high_wmark_pages(zone);
                if (size > high)
                        sum += size - high;
        }

        return sum;
}

현재 노드의 zonelist에서 offset이하의 zone들을 대상으로 managed_pages – high 워터마크 페이지를 모두 더한 수를 반환한다.

 

setup_per_zone_wmarks()

mm/page_alloc.c

/**
 * setup_per_zone_wmarks - called when min_free_kbytes changes
 * or when memory is hot-{added|removed}
 *
 * Ensures that the watermark[min,low,high] values for each zone are set
 * correctly with respect to min_free_kbytes.
 */
void setup_per_zone_wmarks(void)
{
        mutex_lock(&zonelists_mutex);
        __setup_per_zone_wmarks();
        mutex_unlock(&zonelists_mutex);
}

zone별 워터마크 값과 totalreserve 값을 산출한다.

 

__setup_per_zone_wmarks()

mm/page_alloc.c

static void __setup_per_zone_wmarks(void)
{
        unsigned long pages_min = min_free_kbytes >> (PAGE_SHIFT - 10);
        unsigned long lowmem_pages = 0;
        struct zone *zone;
        unsigned long flags;

        /* Calculate total number of !ZONE_HIGHMEM pages */
        for_each_zone(zone) {
                if (!is_highmem(zone))
                        lowmem_pages += zone->managed_pages;
        }

        for_each_zone(zone) {
                u64 tmp;

                spin_lock_irqsave(&zone->lock, flags);
                tmp = (u64)pages_min * zone->managed_pages;
                do_div(tmp, lowmem_pages);
                if (is_highmem(zone)) {
                        /*
                         * __GFP_HIGH and PF_MEMALLOC allocations usually don't
                         * need highmem pages, so cap pages_min to a small
                         * value here.
                         *
                         * The WMARK_HIGH-WMARK_LOW and (WMARK_LOW-WMARK_MIN)
                         * deltas controls asynch page reclaim, and so should
                         * not be capped for highmem.
                         */
                        unsigned long min_pages;

                        min_pages = zone->managed_pages / 1024;
                        min_pages = clamp(min_pages, SWAP_CLUSTER_MAX, 128UL);
                        zone->watermark[WMARK_MIN] = min_pages;
                } else {
                        /*
                         * If it's a lowmem zone, reserve a number of pages
                         * proportionate to the zone's size.
                         */
                        zone->watermark[WMARK_MIN] = tmp;
                }

                zone->watermark[WMARK_LOW]  = min_wmark_pages(zone) + (tmp >> 2);
                zone->watermark[WMARK_HIGH] = min_wmark_pages(zone) + (tmp >> 1);

                __mod_zone_page_state(zone, NR_ALLOC_BATCH,
                        high_wmark_pages(zone) - low_wmark_pages(zone) -
                        atomic_long_read(&zone->vm_stat[NR_ALLOC_BATCH]));

                setup_zone_migrate_reserve(zone);
                spin_unlock_irqrestore(&zone->lock, flags);
        }

        /* update totalreserve_pages */
        calculate_totalreserve_pages();
}

zone별 워터마크 값과 totalreserve 값을 산출한다.

  • unsigned long pages_min = min_free_kbytes >> (PAGE_SHIFT – 10);
    • 전역 min_free_kbytes를 읽어서 page 수 단위로 바꾼다.
  • for_each_zone(zone) { if (!is_highmem(zone)) lowmem_pages += zone->managed_pages; }
    • ZONE_HIGHMEM이 아닌 경우 managed_pages를 합산하여 lowmem_pages에 대입한다.
      • managed_pages
        • highmem 미만의 zone의 present_pages 에서 해당 zone이 사용하는 크기 만큼의 mem_map을 뺀 값이며 dma zone에서는 dma_reserve 값도 추가로 뺀다.
        • highmem zone에서는 highmem zone의 present page와 동일하다.
        • 참고: free_area_init_node() | 문c
  • for_each_zone(zone) { tmp = (u64)pages_min * zone->managed_pages; do_div(tmp, lowmem_pages);
    • 각 zone을 루프를 돌며 tmp에 pages_min(min_free_kbytes)을 현재 zone의 비율만큼 나눈 페이지 수
  • if (is_highmem(zone)) { min_pages = zone->managed_pages / 1024; min_pages = clamp(min_pages, SWAP_CLUSTER_MAX, 128UL); zone->watermark[WMARK_MIN] = min_pages;
    • highmem zone인 경우 실제 present page를 1024로 나눈 값을 32 ~ 128까지의 범위로 clamp한 후 min 워터마크에 저장한다.
  • } else { zone->watermark[WMARK_MIN] = tmp; }
    • highmem이 아닌 경우 min 워터마크에 tmp 값을 저장한다.
  • zone->watermark[WMARK_LOW] = min_wmark_pages(zone) + (tmp >> 2);
    • low 워터마크에 min 워터마크의 125%를 대입한다.
  • zone->watermark[WMARK_HIGH] = min_wmark_pages(zone) + (tmp >> 1);
    • high 워터마크에 min 워터마크의 150%를 대입한다.
  • __mod_zone_page_state(zone, NR_ALLOC_BATCH, high_wmark_pages(zone) – low_wmark_pages(zone) – atomic_long_read(&zone->vm_stat[NR_ALLOC_BATCH]));
    • NR_ALLOC_BATCH에 high-low간 워터마크 값의 변경된 차이 값만 추가한다.
  • setup_zone_migrate_reserve(zone);
    • min_free_kbytes: 0xdac (3500) ->

 

다음 그림은 min_free_kbytes 값과 각 zone의 managed_pages를 가지고 워터마크 값을 산출해내는 모습을 보여준다.

__setup_per_zone_wmarks-1

 

setup_zone_migrate_reserve()

mm/page_alloc.c

/*
 * Mark a number of pageblocks as MIGRATE_RESERVE. The number
 * of blocks reserved is based on min_wmark_pages(zone). The memory within
 * the reserve will tend to store contiguous free pages. Setting min_free_kbytes
 * higher will lead to a bigger reserve which will get freed as contiguous
 * blocks as reclaim kicks in
 */
static void setup_zone_migrate_reserve(struct zone *zone)
{
        unsigned long start_pfn, pfn, end_pfn, block_end_pfn;
        struct page *page;
        unsigned long block_migratetype;
        int reserve;
        int old_reserve;

        /*
         * Get the start pfn, end pfn and the number of blocks to reserve
         * We have to be careful to be aligned to pageblock_nr_pages to
         * make sure that we always check pfn_valid for the first page in
         * the block.
         */
        start_pfn = zone->zone_start_pfn;
        end_pfn = zone_end_pfn(zone);
        start_pfn = roundup(start_pfn, pageblock_nr_pages);
        reserve = roundup(min_wmark_pages(zone), pageblock_nr_pages) >>
                                                        pageblock_order;

        /*
         * Reserve blocks are generally in place to help high-order atomic
         * allocations that are short-lived. A min_free_kbytes value that
         * would result in more than 2 reserve blocks for atomic allocations
         * is assumed to be in place to help anti-fragmentation for the
         * future allocation of hugepages at runtime.
         */
        reserve = min(2, reserve);
        old_reserve = zone->nr_migrate_reserve_block;

        /* When memory hot-add, we almost always need to do nothing */
        if (reserve == old_reserve)
                return;
        zone->nr_migrate_reserve_block = reserve;

워터마크 min 이하의 페이지 블럭들에 대해 버디 시스템의 free page들과 pageblock의 migrate 타입을 reserve 타입으로 구성한다.

  • 워터마크 min 이하의 페이지 블럭의 movable 타입과 버디 시스템의 free page들은 모두 reserve 타입으로 변경
  • 워타마크 min을 초과한 페이지 블럭의 reserve 타입과 버디 시스템의 free page들은 movable 타입으로 변경.

 

  • reserve = roundup(min_wmark_pages(zone), pageblock_nr_pages) >> pageblock_order;
    • min 워터마크 페이지가 필요한 페이지 블럭의 수를 산출한다.
      • min 워터마크 페이지 수를 페이지 블럭 단위로 round up한 후 pageblock_order 만큼 나눈다.
      • 예) min_free_kbytes: 0xdac -> 1개의 페이지 블럭
  • reserve = min(2, reserve); old_reserve = zone->nr_migrate_reserve_block;
    • 2와 reserve 값 중 작은 값을 선택하고, 기존 reserve 값을 백업한다.
  • if (reserve == old_reserve) return;
    • reserve 값이 변동이 없는 경우 처리를 중지하고 빠져나간다.
  • zone->nr_migrate_reserve_block = reserve;
    • reserve 값을 저장한다.

 

        for (pfn = start_pfn; pfn < end_pfn; pfn += pageblock_nr_pages) {
                if (!pfn_valid(pfn))
                        continue;
                page = pfn_to_page(pfn);

                /* Watch out for overlapping nodes */
                if (page_to_nid(page) != zone_to_nid(zone))
                        continue;

                block_migratetype = get_pageblock_migratetype(page);

                /* Only test what is necessary when the reserves are not met */
                if (reserve > 0) {
                        /*
                         * Blocks with reserved pages will never free, skip
                         * them.
                         */
                        block_end_pfn = min(pfn + pageblock_nr_pages, end_pfn);
                        if (pageblock_is_reserved(pfn, block_end_pfn))
                                continue;

                        /* If this block is reserved, account for it */
                        if (block_migratetype == MIGRATE_RESERVE) {
                                reserve--;
                                continue;
                        }

                        /* Suitable for reserving if this block is movable */
                        if (block_migratetype == MIGRATE_MOVABLE) {
                                set_pageblock_migratetype(page,
                                                        MIGRATE_RESERVE);
                                move_freepages_block(zone, page,
                                                        MIGRATE_RESERVE);
                                reserve--;
                                continue;
                        }
                } else if (!old_reserve) {
                        /*
                         * At boot time we don't need to scan the whole zone
                         * for turning off MIGRATE_RESERVE.
                         */
                        break;
                }

                /*
                 * If the reserve is met and this is a previous reserved block,
                 * take it back
                 */
                if (block_migratetype == MIGRATE_RESERVE) {
                        set_pageblock_migratetype(page, MIGRATE_MOVABLE);
                        move_freepages_block(zone, page, MIGRATE_MOVABLE);
                }
        }
}
  • for (pfn = start_pfn; pfn < end_pfn; pfn += pageblock_nr_pages) {
    • start_pfn ~ end_pfn 까지 페이지 블럭 단위로 증가시키며 루프를 돈다.
      • start_pfn은 페이지 블럭단위로 정렬되어 있다.
  • if (!pfn_valid(pfn)) continue;
    • 유효 메모리 페이지가 아니면 skip
  • if (page_to_nid(page) != zone_to_nid(zone)) continue;
    • 페이지의 노드와 zone의 노드가 다른 경우 skip
  • block_migratetype = get_pageblock_migratetype(page);
    • 지정된 페이지에 해당하는 페이지 블럭의 migrate 타입을 알아온다.
  • if (reserve > 0) { block_end_pfn = min(pfn + pageblock_nr_pages, end_pfn);
    • reserve가 0보다 크면 블럭의 끝 pfn을 알아온다.
  • if (pageblock_is_reserved(pfn, block_end_pfn)) continue;
    • pfn ~ block_end_pfn 범위내의 페이지에 유효 페이지가 없거나 reserved 페이지가 존재하는 경우 skip한다.
  • if (block_migratetype == MIGRATE_RESERVE) { reserve–; continue; }
    • 블럭의 migrate 타입이 MIGRATE_RESERVE인 경우 reserve를 감소시키고 skip한다.
  • } else if (!old_reserve) { break; }
    • reserve가 0이하 이면서 기존 reserve가 0인 경우 break 한다.
      • 커널 초기화 시에는 MIGRATE_RESERVE를 끄기 위해 전체 zone을 스캔할 필요 없다.
  • if (block_migratetype == MIGRATE_RESERVE) { set_pageblock_migratetype(page, MIGRATE_MOVABLE); move_freepages_block(zone, page, MIGRATE_MOVABLE); }
    • 블럭 migrate 타입이 MIGRATE_RESERVE인 경우 MIGRATE_MOVABLE로 변경하고 블럭의 free page들을 모두 MIGRATE_MOVABLE로 이동시킨다.

 

다음 그림은 워터마크 min 을 기준으로 하는 페이지 블럭 단위로 버디 시스템의 free page들을 movable 과 reserve 타입으로 변경하는 모습을 보여준다.

setup_zone_migrate_reserve-1a

 

pageblock_is_reserved()

mm/page_alloc.c

/*
 * Check if a pageblock contains reserved pages
 */
static int pageblock_is_reserved(unsigned long start_pfn, unsigned long end_pfn)
{
        unsigned long pfn;

        for (pfn = start_pfn; pfn < end_pfn; pfn++) {
                if (!pfn_valid_within(pfn) || PageReserved(pfn_to_page(pfn)))
                        return 1;
        }
        return 0;
}

시작 pfn ~ 끝 pfn에 유효 메모리가 없거나 reserved 페이지가 포함된 경우 true를 반환한다.

 

move_freepages_block()

mm/page_alloc.c

int move_freepages_block(struct zone *zone, struct page *page,
                                int migratetype)
{
        unsigned long start_pfn, end_pfn;
        struct page *start_page, *end_page;

        start_pfn = page_to_pfn(page);
        start_pfn = start_pfn & ~(pageblock_nr_pages-1);
        start_page = pfn_to_page(start_pfn);
        end_page = start_page + pageblock_nr_pages - 1;
        end_pfn = start_pfn + pageblock_nr_pages - 1;

        /* Do not cross zone boundaries */
        if (!zone_spans_pfn(zone, start_pfn))
                start_page = page;
        if (!zone_spans_pfn(zone, end_pfn))
                return 0;

        return move_freepages(zone, start_page, end_page, migratetype);
}

지정된 zone의 요청 page가 있는 pageblock내의 모든 free page들을 요청 migrate 타입으로 변경하고 버디 시스템에서 요청 migrate 타입으로 이동하고 이동된 페이지 수를 반환한다.

  • 요청 zone의 시작 페이지가 pageblock 단위로 정렬되지 않은 경우에도 zone의 시작 주소부터 move 적용가능 (partial)
  • 요청 zone의 끝 페이지가 pageblock 단위로 정렬되지 않은 경우 그 pageblock은 사용할 수 없음

 

아래 그림은 지정된 zone의 페이지블럭에 있는 free page들의 migrate type을 요청한 타입으로 변경하는 모습을 보여준다.

move_freepages_block-1

 

zone_spans_pfn()

include/linux/mmzone.h

static inline bool zone_spans_pfn(const struct zone *zone, unsigned long pfn)
{
        return zone->zone_start_pfn <= pfn && pfn < zone_end_pfn(zone);
}

pfn이 zone의 영역에 들어있는지 여부를 반환한다.

 

move_freepages()

mm/page_alloc.c

/*
 * Move the free pages in a range to the free lists of the requested type.
 * Note that start_page and end_pages are not aligned on a pageblock
 * boundary. If alignment is required, use move_freepages_block()
 */
int move_freepages(struct zone *zone,
                          struct page *start_page, struct page *end_page,
                          int migratetype)
{
        struct page *page;
        unsigned long order;
        int pages_moved = 0;

#ifndef CONFIG_HOLES_IN_ZONE
        /*
         * page_zone is not safe to call in this context when
         * CONFIG_HOLES_IN_ZONE is set. This bug check is probably redundant
         * anyway as we check zone boundaries in move_freepages_block().
         * Remove at a later date when no bug reports exist related to
         * grouping pages by mobility
         */
        VM_BUG_ON(page_zone(start_page) != page_zone(end_page));
#endif

        for (page = start_page; page <= end_page;) {
                /* Make sure we are not inadvertently changing nodes */
                VM_BUG_ON_PAGE(page_to_nid(page) != zone_to_nid(zone), page);

                if (!pfn_valid_within(page_to_pfn(page))) {
                        page++;
                        continue;
                }

                if (!PageBuddy(page)) {
                        page++;
                        continue;
                }

                order = page_order(page);
                list_move(&page->lru,
                          &zone->free_area[order].free_list[migratetype]);
                set_freepage_migratetype(page, migratetype);
                page += 1 << order;
                pages_moved += 1 << order;
        }

        return pages_moved;
}

요청 zone의 시작 페이지부터 끝 페이지까지 모든 free page들에 대해 migrate 타입을 변경하고 버디 시스템의 migrate 타입도 변경하고 변경된 페이지 수를 반환한다.

 

다음 그림은 요청한 범위에서 버디 시스템의 free page들을 찾아 MIGRATE_UNMOVABLE 타입으로 이동시키는 모습을 보여준다.

move_freepages-1

 

calculate_totalreserve_pages()

mm/page_alloc.c

/*
 * calculate_totalreserve_pages - called when sysctl_lower_zone_reserve_ratio
 *      or min_free_kbytes changes.
 */
static void calculate_totalreserve_pages(void)
{
        struct pglist_data *pgdat;
        unsigned long reserve_pages = 0;
        enum zone_type i, j;

        for_each_online_pgdat(pgdat) {
                for (i = 0; i < MAX_NR_ZONES; i++) {
                        struct zone *zone = pgdat->node_zones + i;
                        long max = 0;

                        /* Find valid and maximum lowmem_reserve in the zone */
                        for (j = i; j < MAX_NR_ZONES; j++) {
                                if (zone->lowmem_reserve[j] > max)
                                        max = zone->lowmem_reserve[j];
                        }

                        /* we treat the high watermark as reserved pages. */
                        max += high_wmark_pages(zone);

                        if (max > zone->managed_pages)
                                max = zone->managed_pages;
                        reserve_pages += max;
                        /*
                         * Lowmem reserves are not available to
                         * GFP_HIGHUSER page cache allocations and
                         * kswapd tries to balance zones to their high
                         * watermark.  As a result, neither should be
                         * regarded as dirtyable memory, to prevent a
                         * situation where reclaim has to clean pages
                         * in order to balance the zones.
                         */
                        zone->dirty_balance_reserve = max;
                }
        }
        dirty_balance_reserve = reserve_pages;
        totalreserve_pages = reserve_pages;
}

zone별 dirty_balance_reserve 값과 전역 dirty_balance_reserve 및 totalreserve_pages 값을 산출한다.

  • for_each_online_pgdat(pgdat) {
    • 모든 online 노드에 대하여 루프를 돈다.
  • for (i = 0; i < MAX_NR_ZONES; i++) {
    • 모든 zone에 대하여 루프를 돈다.
  • for (j = i; j < MAX_NR_ZONES; j++) { if (zone->lowmem_reserve[j] > max) max = zone->lowmem_reserve[j]; }
    • 지정된 zone을 포함한 상위 z->lowmem_reserve[] 배열에서 최대 값 max를 알아온다.
  • max += high_wmark_pages(zone);
    • max 값에 high 워터마크 값을 더한다.
  • if (max > zone->managed_pages) max = zone->managed_pages;
    • max 값이 zone의 managed_pages 값보다 큰 경우 max 값을 managed_pages로 제한한다.
  • reserve_pages += max;
    • 최종 전역 변수 갱신을 위해 모든 reserve_pages 값을 더한다.
  • zone->dirty_balance_reserve = max;
    • 각 zone의 dirty_balance_reserve 값에 max 값을 대입한다.
  • dirty_balance_reserve = reserve_pages; totalreserve_pages = reserve_pages;
  • 마지막으로 전역 dirty_balance_reserve 및 totalreserve_pages 값에 전체 reserve_pages 값을 대입한다.

 

다음 그림은 zone별 dirty_balance_reserve 값과 전역 dirty_balance_reserve 및 totalreserve_pages 값을 산출하는 과정을 보여준다.

calculate_totalreserve_pages-1a

 

refresh_zone_stat_thresholds()

mm/vmstat.c

/*
 * Refresh the thresholds for each zone.
 */
void refresh_zone_stat_thresholds(void)
{
        struct zone *zone;
        int cpu;
        int threshold;

        for_each_populated_zone(zone) {
                unsigned long max_drift, tolerate_drift;

                threshold = calculate_normal_threshold(zone);

                for_each_online_cpu(cpu) 
                        per_cpu_ptr(zone->pageset, cpu)->stat_threshold
                                                        = threshold;

                /* 
                 * Only set percpu_drift_mark if there is a danger that
                 * NR_FREE_PAGES reports the low watermark is ok when in fact
                 * the min watermark could be breached by an allocation
                 */
                tolerate_drift = low_wmark_pages(zone) - min_wmark_pages(zone);
                max_drift = num_online_cpus() * threshold;
                if (max_drift > tolerate_drift)
                        zone->percpu_drift_mark = high_wmark_pages(zone) +
                                        max_drift;
        }
}

모든 zone의 zone 통계 스레졸드 값을 구해 업데이트한다.

  • zone 카운터들은 zone에 대략의 카운터와 각 per-cpu에 구성된 카운터를 더해야 정확한 값을 알아낼 수 있다. 따라서 어느 일정 free 페이지 미만에 대해 정확히 검사하려고 하는 경우에는 각 cpu의 zone 통계 값들을 다 더해야하는데, 이렇게 매번 계산을 하는 경우 성능을 떨어뜨리므로 일정 스레졸드 이상인 경우는 zone에 있는 카운터 값만으로 판단하고, 스레졸드 기준 이하가 되면 각 cpu의 zone 통계를 구하도록 유도하여 성능을 유지할 수 있다.
  • percpu_drift_mark
    • compaction 등의 상황에서 워터마크 비교에 대해 더 정확한 판단이 필요할 때 free 페이지가 이 스레졸드 값보다 떨어지는 경우 zone 카운터에 per-cpu 카운터까지 합하여 정밀한 카운터를 구해 비교한다.

 

  • for_each_populated_zone(zone) { unsigned long max_drift, tolerate_drift; threshold = calculate_normal_threshold(zone);
    • 전체 zone에 대해 루프를 돌며 해당 zone에 대한 stat 스레졸드 값을 구해온다
  • for_each_online_cpu(cpu)  per_cpu_ptr(zone->pageset, cpu)->stat_threshold = threshold;
    • 해당 zone에 있는 per-cpu 페이지 프레임 캐시의 모든 cpu에 대해 산출한 스레졸드 값을 저장한다
  • tolerate_drift = low_wmark_pages(zone) – min_wmark_pages(zone);
    • tolerate_drift에 low 워터마크 – min 워터마크 값을 대입한다.
  • max_drift = num_online_cpus() * threshold;
    • max_drift에 스레졸드 * online cpu 수를 대입한다
  • if (max_drift > tolerate_drift) zone->percpu_drift_mark = high_wmark_pages(zone) + max_drift;
    • tolerate_drift보다 max_drift 값이 더 큰 경우 해당 zone의 percpu_drift_mark 값에 high 워터마크에 max_drift 값을 더한 값을 저장한다

 

아래는 zone stat 스레졸드를 이용한 zone_watermark_ok_safe() 함수의 사용예를 보여준다.

zone_watermark_ok_safe-1

 

 

calculate_normal_threshold()

mm/vmstat.c

int calculate_normal_threshold(struct zone *zone)
{
        int threshold;          
        int mem;        /* memory in 128 MB units */

        /*
         * The threshold scales with the number of processors and the amount
         * of memory per zone. More memory means that we can defer updates for
         * longer, more processors could lead to more contention.
         * fls() is used to have a cheap way of logarithmic scaling.
         *
         * Some sample thresholds:
         *
         * Threshold    Processors      (fls)   Zonesize        fls(mem+1)
         * ------------------------------------------------------------------
         * 8            1               1       0.9-1 GB        4
         * 16           2               2       0.9-1 GB        4
         * 20           2               2       1-2 GB          5
         * 24           2               2       2-4 GB          6
         * 28           2               2       4-8 GB          7
         * 32           2               2       8-16 GB         8
         * 4            2               2       <128M           1
         * 30           4               3       2-4 GB          5
         * 48           4               3       8-16 GB         8
         * 32           8               4       1-2 GB          4
         * 32           8               4       0.9-1GB         4
         * 10           16              5       <128M           1
         * 40           16              5       900M            4
         * 70           64              7       2-4 GB          5
         * 84           64              7       4-8 GB          6
         * 108          512             9       4-8 GB          6
         * 125          1024            10      8-16 GB         8
         * 125          1024            10      16-32 GB        9
         */

        mem = zone->managed_pages >> (27 - PAGE_SHIFT);

        threshold = 2 * fls(num_online_cpus()) * (1 + fls(mem));

        /*
         * Maximum threshold is 125
         */
        threshold = min(125, threshold);

        return threshold;
}

요청 zone의 managed_pages 크기에 비례하여 스레졸드 값을 구하는데 최소 125값부터 시작한다.

  • 요청 zone의 managed_pages에 대해 128M 단위로 round down 한 값을 mem에 대입한다.
  • 2 x (온라인cpu 수를 2의 차수 단위로 round down) x (mem을 2의 차수 단위로 round down + 1) 한 값으로 스레졸드를 구하고 이 값은 최대 125로 제한한다.
    • 예) 4 cpu이고, zone이 600M인 경우
      • threshold=24(2 x 3 x 4)

 

setup_per_zone_lowmem_reserve()

mm/page_alloc.c

/*
 * setup_per_zone_lowmem_reserve - called whenever
 *      sysctl_lower_zone_reserve_ratio changes.  Ensures that each zone
 *      has a correct pages reserved value, so an adequate number of
 *      pages are left in the zone after a successful __alloc_pages().
 */
static void setup_per_zone_lowmem_reserve(void)
{
        struct pglist_data *pgdat;
        enum zone_type j, idx;

        for_each_online_pgdat(pgdat) {
                for (j = 0; j < MAX_NR_ZONES; j++) {
                        struct zone *zone = pgdat->node_zones + j;
                        unsigned long managed_pages = zone->managed_pages;

                        zone->lowmem_reserve[j] = 0;

                        idx = j;
                        while (idx) {
                                struct zone *lower_zone;

                                idx--;

                                if (sysctl_lowmem_reserve_ratio[idx] < 1)
                                        sysctl_lowmem_reserve_ratio[idx] = 1;

                                lower_zone = pgdat->node_zones + idx;
                                lower_zone->lowmem_reserve[j] = managed_pages /
                                        sysctl_lowmem_reserve_ratio[idx];
                                managed_pages += lower_zone->managed_pages;
                        }
                }
        }

        /* update totalreserve_pages */
        calculate_totalreserve_pages();
}

zone별 lowmem_reserve[] 값을 산출한다.

  • for_each_online_pgdat(pgdat) {
    • 전체 online 노드만큼 루프를 돈다.
  • for (j = 0; j < MAX_NR_ZONES; j++) {
    • 전체 zone 갯수만큼 루프를 돈다.
  • idx = j; while (idx) { struct zone *lower_zone; idx–;
    • 가장 처음 zone을 제외하고 루프 카운터로 지정된 zone부터 밑으로 루프를 돈다.
  • if (sysctl_lowmem_reserve_ratio[idx] < 1) sysctl_lowmem_reserve_ratio[idx] = 1;
    • sysctl_lowmem_reserve_ratio[idx] 값이 1보다 작으면 1로 대입한다.
  • lower_zone = pgdat->node_zones + idx;
    • 변경하고자 하는 하위 zone을 선택한다.
  • lower_zone->lowmem_reserve[j] = managed_pages / sysctl_lowmem_reserve_ratio[idx];
    • 선택된 하위 zone의 lowmem_reserve[j] 값에 managed_pages / 비율로 나누어 대입한다.
      • sysctl_lowmem_reserve_ratio 비율
        • CONFIG_DMA 및 CONFIG_DMA32의 경우에만 0x100, 그 외 0x20
        • rpi2: CONFIG_NORMAL만 사용하므로 0x20 (32)로 설정되어 있다.
    • 참고: lowmem_reserve_ratio | barrios
  •  managed_pages += lower_zone->managed_pages;
    • 가장 밑에 있는 zone에 managed_pages를 중첩시키기 위해 보관한다.
  •  calculate_totalreserve_pages();
    • zone별 lowmem_reserve[] 값이 산출 완료되었으므로 totalreserve_pages 값을 산출한다.

 

다음 그림은 zone별 lowmem_reserve[]를 산출하는 과정을 보여준다.

setup_per_zone_lowmem_reserve-1

 

setup_per_zone_inactive_ratio()

mm/page_alloc.c

static void __meminit setup_per_zone_inactive_ratio(void)
{
        struct zone *zone;

        for_each_zone(zone)
                calculate_zone_inactive_ratio(zone);
}

모든 zone의 inactive anon 비율을 산출한다.

  • zone->managed_pages가 256k pages (1GB) 미만인 경우 zone->inactive_ratio가 1이되어 active anon과 inactive anon의 비율을 1:1로 설정한다. 만일 managed_pages가 256k pages (1GB)를 초과한 경우 inactive_ratio 값은 3 이상이 되면서 inactive anon의 비율이 1/3 이하로 줄어든다.

 

다음 그림은 각 zone의 managed_pages  크기에 따라 inactive_ratio 값이 산출되는 과정을 보여준다.

setup_per_zone_inactive_ratio-1

 

calculate_zone_inactive_ratio()

mm/page_alloc.c

/*
 * The inactive anon list should be small enough that the VM never has to
 * do too much work, but large enough that each inactive page has a chance
 * to be referenced again before it is swapped out.
 *
 * The inactive_anon ratio is the target ratio of ACTIVE_ANON to
 * INACTIVE_ANON pages on this zone's LRU, maintained by the
 * pageout code. A zone->inactive_ratio of 3 means 3:1 or 25% of
 * the anonymous pages are kept on the inactive list.
 *
 * total     target    max
 * memory    ratio     inactive anon
 * -------------------------------------
 *   10MB       1         5MB
 *  100MB       1        50MB
 *    1GB       3       250MB
 *   10GB      10       0.9GB
 *  100GB      31         3GB
 *    1TB     101        10GB
 *   10TB     320        32GB
 */
static void __meminit calculate_zone_inactive_ratio(struct zone *zone)
{
        unsigned int gb, ratio;

        /* Zone size in gigabytes */
        gb = zone->managed_pages >> (30 - PAGE_SHIFT);
        if (gb)
                ratio = int_sqrt(10 * gb);
        else
                ratio = 1;

        zone->inactive_ratio = ratio;
}

지정된 zone의 inactive anon 비율을 산출한다.

  • managed 페이지가 1G 미만인 경우 ratio는 1이된다.
    • inactive anon=1 : active anon=1
  • managed 페이지가 1G 이상인 경우 * 10한 후 제곱근(루트)을 하여 ratio를 설정한다. (inactive = 1 / ratio)
    • 예) managed pages=1G
      • inactive_ratio=3
        • inactive anon=1 : active anon=3
          • inactive anon=250M : active anon=750M

 

워터마크 관련 값

  • min_free_kbytes
    • free page가 부족해지면 kswapd가 페이지 회수에 나서는데 최소 free page 이상을 확보한다.
      • 이 페이지 수를 만족시키지 못하는 경우 OOM이 발생하게 된다.
  • zone->watermark[WMARK_MIN]
    • free pages가 이 값 미만이 되는 경우 페이지 할당 시 필요 시마다 페이지 회수 작업을 한다.
  • zone->watermark[WMARK_LOW]
    • free pages가 이 값 미만이 되는 경우 kswapd가 깨어나 페이지 회수 작업을 한다.
    • zone->watermark[WMARK_MIN] * 125%
  • zone->watermark[WMARK_HIGH]
    • free pages가 이 값을 초과하는 경우 kswapd가 슬립한다.
    • zone->watermark[WMARK_MIN] * 150%

 

참고

답글 남기기

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