page_ext_init_flatmem()

page extension

Flat memory model에서 page 구조체를 변경하지 않고 page_ext라는 별도의 구조체를 만들어 page 확장을 하여 디버깅에 도움을 줄 수 있게 하였다. Flat memory model이 아닌 경우 사용할 수 없다.

 

page_ext_init_flatmem-1

page_ext_init_flatmem-2

 

page_ext_init_flatmem()

mm/page_ext.c

void __init page_ext_init_flatmem(void)
{

        int nid, fail;

        if (!invoke_need_callbacks())
                return;

        for_each_online_node(nid)  {
                fail = alloc_node_page_ext(nid);
                if (fail)
                        goto fail;
        }
        pr_info("allocated %ld bytes of page_ext\n", total_usage);
        invoke_init_callbacks();
        return;

fail:
        pr_crit("allocation of page_ext failed.\n");
        panic("Out of memory");
}

page_ext가 필요한 경우 각 노드에 page_ext를 할당 받고 초기화한다.

 

invoke_need_callbacks()

mm/page_ext.c

static bool __init invoke_need_callbacks(void)
{
        int i;
        int entries = ARRAY_SIZE(page_ext_ops);

        for (i = 0; i < entries; i++) {
                if (page_ext_ops[i]->need && page_ext_ops[i]->need())
                        return true;
        }

        return false;
}

page_ext_ops 엔트리 수 만큼 루프를 돌며 엔트리에 등록된 need 함수를 수행시켜 엔트리 하나라도 초기화가 필요한 경우 true를 반환한다.

 

alloc_node_page_ext()

mm/page_ext.c

static int __init alloc_node_page_ext(int nid)
{
        struct page_ext *base;
        unsigned long table_size;
        unsigned long nr_pages;

        nr_pages = NODE_DATA(nid)->node_spanned_pages;
        if (!nr_pages)
                return 0;

        /*
         * Need extra space if node range is not aligned with
         * MAX_ORDER_NR_PAGES. When page allocator's buddy algorithm
         * checks buddy's status, range could be out of exact node range.
         */
        if (!IS_ALIGNED(node_start_pfn(nid), MAX_ORDER_NR_PAGES) ||
                !IS_ALIGNED(node_end_pfn(nid), MAX_ORDER_NR_PAGES))
                nr_pages += MAX_ORDER_NR_PAGES;

        table_size = sizeof(struct page_ext) * nr_pages;

        base = memblock_virt_alloc_try_nid_nopanic(
                        table_size, PAGE_SIZE, __pa(MAX_DMA_ADDRESS),
                        BOOTMEM_ALLOC_ACCESSIBLE, nid);
        if (!base)
                return -ENOMEM;
        NODE_DATA(nid)->node_page_ext = base;
        total_usage += table_size;
        return 0;
}

지정된 노드에 MAX_ORDER_NR_PAGES 단위로 align된 존재하는 페이지 수 만큼의 page_ext 구조체 영역을 memblock에 할당한다.

  • nr_pages = NODE_DATA(nid)->node_spanned_pages;
    • 해당 노드에서 absent page(hole)를 제외한 페이지
      • Sparsemem에서는 hole을 제외한 노드에 대한 전체 페이지 수
      • Flatmem에서는 노드에 대한 전체 페이지 수
  • if (!IS_ALIGNED(node_start_pfn(nid), MAX_ORDER_NR_PAGES) ||
    !IS_ALIGNED(node_end_pfn(nid), MAX_ORDER_NR_PAGES))
    nr_pages += MAX_ORDER_NR_PAGES;

    • 해당 노드의 시작과 끝 페이지가 1K page 단위로 align 되어 있지 않은 경우 할당할 페이지 수에 1024 pages를 더한다.
  • table_size = sizeof(struct page_ext) * nr_pages;
    • 할당할 크기를 구한다.
  • base = memblock_virt_alloc_try_nid_nopanic(table_size, PAGE_SIZE, __pa(MAX_DMA_ADDRESS), BOOTMEM_ALLOC_ACCESSIBLE, nid)
    • memblock의 DMA 주소 ~ lowmem 범위에 해당 노드에 대한 page_ext 공간을 reserve 한다.
      • 가능하면 지정된 노드의 memblock에 할당을 시도하고 메모리 공간이 부족한 경우 전체 노드를 대상으로 검색하여 할당한다.
  • NODE_DATA(nid)->node_page_ext = base;
    • 노드이 멤버 node_page_ext에 할당된 주소를 지정한다.
  • total_usage += table_size;
    • 전역 사용량(바이트)에 할당 사이즈 만큼 추가한다.

 

invoke_init_callbacks()

mm/page_ext.c

static void __init invoke_init_callbacks(void)
{
        int i;
        int entries = ARRAY_SIZE(page_ext_ops);

        for (i = 0; i < entries; i++) {
                if (page_ext_ops[i]->init)
                        page_ext_ops[i]->init();
        }
}

page_ext_ops 엔트리 수 만큼 루프를 돌며 초기화 핸들러 함수를 호출한다.

 

초기화 핸들러

need_debug_guardpage()

mm/page_alloc.c

static bool need_debug_guardpage(void)
{
        /* If we don't use debug_pagealloc, we don't need guard page */
        if (!debug_pagealloc_enabled())
                return false; 

        return true;
}

가드 페이지 디버깅이 필요한지 여부를 반환한다.

 

debug_pagealloc_enabled()

include/linux/mm.h

static inline bool debug_pagealloc_enabled(void) 
{       
        return _debug_pagealloc_enabled;
}

페이지 할당 디버깅 기능 사용 여부를 반환한다.

 

mm/page_alloc.c

bool _debug_pagealloc_enabled __read_mostly;

 

init_debug_guardpage()

mm/page_alloc.c

static void init_debug_guardpage(void)
{                           
        if (!debug_pagealloc_enabled())
                return;

        _debug_guardpage_enabled = true; 
}

가드 페이지 디버깅 기능을 enable 한다.

 

need_page_poisoning()

mm/debug-pagealloc.c

static bool need_page_poisoning(void)
{       
        if (!debug_pagealloc_enabled())
                return false;

        return true;
}

페이지 poisoning이 필요한지 여부를 반환한다.

init_page_poisoning()

mm/debug-pagealloc.c

static void init_page_poisoning(void)
{
        if (!debug_pagealloc_enabled())
                return;

        page_poisoning_enabled = true;
}

페이지 poisoning 기능을 enable한다.

 

mm/debug-pagealloc.c

static bool page_poisoning_enabled __read_mostly;

 

need_page_owner()

mm/page_owner.c

static bool need_page_owner(void)
{
        if (page_owner_disabled)
                return false;

        return true;
}

page owner 추적 기능이 필요한지 여부를 반환한다.

 

mm/page_owner.c

static bool page_owner_disabled = true;

 

init_page_owner()

mm/page_owner.c

static void init_page_owner(void)
{
        if (page_owner_disabled)
                return;

        page_owner_inited = true;
        init_early_allocated_pages();
}

page owner 추적 기능이 사용되는 경우 할당 페이지를 초기화한다.

 

mm/page_owner.c

bool page_owner_inited __read_mostly;

 

init_early_allocated_pages()

mm/page_owner.c

static void init_early_allocated_pages(void)
{
        pg_data_t *pgdat;

        drain_all_pages(NULL);
        for_each_online_pgdat(pgdat)
                init_zones_in_node(pgdat);
}

Per-CPU Page Frame Cache를 buddy allocator로 다시 돌려보내고 노드안에 있는 zone을 초기화한다.

 

init_zones_in_node()

mm/page_owner.c

static void init_zones_in_node(pg_data_t *pgdat)
{
        struct zone *zone;
        struct zone *node_zones = pgdat->node_zones;
        unsigned long flags;
        
        for (zone = node_zones; zone - node_zones < MAX_NR_ZONES; ++zone) {
                if (!populated_zone(zone))
                        continue;

                spin_lock_irqsave(&zone->lock, flags);
                init_pages_in_zone(pgdat, zone);
                spin_unlock_irqrestore(&zone->lock, flags);
        }       
}

해당 노드에 대해 populate zone에 속한 페이지를 초기화한다.

 

init_pages_in_zone()

mm/page_owner.c

static void init_pages_in_zone(pg_data_t *pgdat, struct zone *zone)
{
        struct page *page;
        struct page_ext *page_ext;
        unsigned long pfn = zone->zone_start_pfn, block_end_pfn;
        unsigned long end_pfn = pfn + zone->spanned_pages;
        unsigned long count = 0;

        /* Scan block by block. First and last block may be incomplete */
        pfn = zone->zone_start_pfn;

        /*
         * Walk the zone in pageblock_nr_pages steps. If a page block spans
         * a zone boundary, it will be double counted between zones. This does
         * not matter as the mixed block count will still be correct
         */
        for (; pfn < end_pfn; ) {
                if (!pfn_valid(pfn)) {
                        pfn = ALIGN(pfn + 1, MAX_ORDER_NR_PAGES);
                        continue;
                }

                block_end_pfn = ALIGN(pfn + 1, pageblock_nr_pages);
                block_end_pfn = min(block_end_pfn, end_pfn);

                page = pfn_to_page(pfn);

                for (; pfn < block_end_pfn; pfn++) {
                        if (!pfn_valid_within(pfn))
                                continue;

                        page = pfn_to_page(pfn);

                        /*
                         * We are safe to check buddy flag and order, because
                         * this is init stage and only single thread runs.
                         */
                        if (PageBuddy(page)) {
                                pfn += (1UL << page_order(page)) - 1;
                                continue;
                        }

                        if (PageReserved(page))
                                continue;

                        page_ext = lookup_page_ext(page);

                        /* Maybe overraping zone */
                        if (test_bit(PAGE_EXT_OWNER, &page_ext->flags))
                                continue;

                        /* Found early allocated page */
                        set_page_owner(page, 0, 0);
                        count++;
                }
        }

        pr_info("Node %d, zone %8s: page owner found early allocated %lu pages\n",
                pgdat->node_id, zone->name, count);
}

zone에 구성된 페이지 중 onwer 설정이 필요한 경우 page_ext[]에서 해당 페이지를 찾아 owner 비트를 설정한다.

 

set_page_owner()

include/linux/page_owner.h

static inline void set_page_owner(struct page *page,
                        unsigned int order, gfp_t gfp_mask)
{
        if (likely(!page_owner_inited))
                return; 

        __set_page_owner(page, order, gfp_mask);
}

page_ext[]의 해당 페이지에 owner 비트를 설정한다.

 

__set_page_owner()

mm/page_owner.c

void __set_page_owner(struct page *page, unsigned int order, gfp_t gfp_mask)
{
        struct page_ext *page_ext = lookup_page_ext(page);
        struct stack_trace trace = {
                .nr_entries = 0,
                .max_entries = ARRAY_SIZE(page_ext->trace_entries),
                .entries = &page_ext->trace_entries[0],
                .skip = 3,
        };

        save_stack_trace(&trace);

        page_ext->order = order;
        page_ext->gfp_mask = gfp_mask;
        page_ext->nr_entries = trace.nr_entries;

        __set_bit(PAGE_EXT_OWNER, &page_ext->flags);
}

page_ext에 owner를 설정한다.

 

reset_page_owner()

include/linux/page_owner.h

static inline void reset_page_owner(struct page *page, unsigned int order)
{
        if (likely(!page_owner_inited))
                return;

        __reset_page_owner(page, order);
}

2^order 만큼의 페이지들에 대항하는 page_ext의 owner 비트를 clear한다.

 

__reset_page_owner()

mm/page_owner.c

void __reset_page_owner(struct page *page, unsigned int order)
{
        int i;
        struct page_ext *page_ext;

        for (i = 0; i < (1 << order); i++) {
                page_ext = lookup_page_ext(page + i);
                __clear_bit(PAGE_EXT_OWNER, &page_ext->flags);
        }
}

2^order 만큼의 페이지들에 대항하는 page_ext의 owner 비트를 clear한다.

 

page_is_guard()

include/linux/mm.h

#ifdef CONFIG_DEBUG_PAGEALLOC
static inline bool page_is_guard(struct page *page)
{       
        struct page_ext *page_ext;

        if (!debug_guardpage_enabled())
                return false;

        page_ext = lookup_page_ext(page); 
        return test_bit(PAGE_EXT_DEBUG_GUARD, &page_ext->flags);
}
#endif

페이지에 대항하는 page_ext의 guard 플래그 비트가 설정되었는지 여부를 반환한다.

 

clear_page_guard()

mm/page_alloc.c

static inline void clear_page_guard(struct zone *zone, struct page *page,
                                unsigned int order, int migratetype)
{
        struct page_ext *page_ext;

        if (!debug_guardpage_enabled())
                return;

        page_ext = lookup_page_ext(page);
        __clear_bit(PAGE_EXT_DEBUG_GUARD, &page_ext->flags);

        set_page_private(page, 0);
        if (!is_migrate_isolate(migratetype))
                __mod_zone_freepage_state(zone, (1 << order), migratetype);
}

페이지에 대항하는 page_ext의 guard 플래그 비트를 clear 하고 _private에 0을 대입한다. zone에 대한 free page stat도 2^order 만큼 증가시킨다.

 

lookup_page_ext()

mm/page_ext.c

struct page_ext *lookup_page_ext(struct page *page)
{
        unsigned long pfn = page_to_pfn(page);
        unsigned long offset;
        struct page_ext *base;

        base = NODE_DATA(page_to_nid(page))->node_page_ext;
#ifdef CONFIG_DEBUG_VM
        /*
         * The sanity checks the page allocator does upon freeing a
         * page can reach here before the page_ext arrays are
         * allocated when feeding a range of pages to the allocator
         * for the first time during bootup or memory hotplug.
         */
        if (unlikely(!base))
                return NULL;
#endif
        offset = pfn - round_down(node_start_pfn(page_to_nid(page)),
                                        MAX_ORDER_NR_PAGES);
        return base + offset;
}

page로 page_ext 정보츨 찾아 반환한다.

 

Early 커널 파라메터

early_debug_pagealloc()

mm/page_alloc.c

static int __init early_debug_pagealloc(char *buf)
{
        if (!buf)
                return -EINVAL;

        if (strcmp(buf, "on") == 0)
                _debug_pagealloc_enabled = true;
        
        return 0;
}       
early_param("debug_pagealloc", early_debug_pagealloc);

“debug_pagealloc=on” early 커널 파라메터를 사용하는 경우 페이지 할당 디버깅 기능을 사용한다.

 

early_page_owner_param()

mm/page_owner.c

static int early_page_owner_param(char *buf)
{
        if (!buf)
                return -EINVAL;

        if (strcmp(buf, "on") == 0)
                page_owner_disabled = false;

        return 0;
}               
early_param("page_owner", early_page_owner_param);

“page_owner=on” early 커널 파라메터를 사용하는 경우 페이지 owner 추적 기능을 사용한다.

 

구조체

page_ext_operations 구조체

include/linux/page_ext.h

struct page_ext_operations {
        bool (*need)(void);       
        void (*init)(void);
};
  • need
    • 필요 여부 조회 핸들러 함수 등록
  • init
    • 초기화 핸들러 함수 등록

 

page_ext_ops[] 전역 객체

mm/page_ext.c

/*
 * struct page extension
 *
 * This is the feature to manage memory for extended data per page.
 *
 * Until now, we must modify struct page itself to store extra data per page.
 * This requires rebuilding the kernel and it is really time consuming process.
 * And, sometimes, rebuild is impossible due to third party module dependency.
 * At last, enlarging struct page could cause un-wanted system behaviour change.
 *
 * This feature is intended to overcome above mentioned problems. This feature
 * allocates memory for extended data per page in certain place rather than
 * the struct page itself. This memory can be accessed by the accessor
 * functions provided by this code. During the boot process, it checks whether
 * allocation of huge chunk of memory is needed or not. If not, it avoids
 * allocating memory at all. With this advantage, we can include this feature
 * into the kernel in default and can avoid rebuild and solve related problems.
 *
 * To help these things to work well, there are two callbacks for clients. One
 * is the need callback which is mandatory if user wants to avoid useless
 * memory allocation at boot-time. The other is optional, init callback, which
 * is used to do proper initialization after memory is allocated.
 *
 * The need callback is used to decide whether extended memory allocation is
 * needed or not. Sometimes users want to deactivate some features in this
 * boot and extra memory would be unneccessary. In this case, to avoid
 * allocating huge chunk of memory, each clients represent their need of
 * extra memory through the need callback. If one of the need callbacks
 * returns true, it means that someone needs extra memory so that
 * page extension core should allocates memory for page extension. If
 * none of need callbacks return true, memory isn't needed at all in this boot
 * and page extension core can skip to allocate memory. As result,
 * none of memory is wasted.
 *
 * The init callback is used to do proper initialization after page extension
 * is completely initialized. In sparse memory system, extra memory is
 * allocated some time later than memmap is allocated. In other words, lifetime
 * of memory for page extension isn't same with memmap for struct page.
 * Therefore, clients can't store extra data until page extension is
 * initialized, even if pages are allocated and used freely. This could
 * cause inadequate state of extra data per page, so, to prevent it, client
 * can utilize this callback to initialize the state of it correctly.
 */
static struct page_ext_operations *page_ext_ops[] = {
        &debug_guardpage_ops,
#ifdef CONFIG_PAGE_POISONING
        &page_poisoning_ops,
#endif
#ifdef CONFIG_PAGE_OWNER
        &page_owner_ops,
#endif
};

커널 옵션에 따라 현재 최대 3개의 핸들러 객체들이 등록되어 있다.

 

debug_guardpage_ops 전역 객체

mm/page_alloc.c

#ifdef CONFIG_DEBUG_PAGEALLOC
struct page_ext_operations debug_guardpage_ops = {
        .need = need_debug_guardpage,
        .init = init_debug_guardpage,
};
#else
struct page_ext_operations debug_guardpage_ops = { NULL, };
#endif

가드 페이지 디버깅에 관련된 핸들러 함수들이 등록되어 있다.

 

page_poisoning_ops 전역 객체

mm/debug-pagealloc.c

struct page_ext_operations page_poisoning_ops = {
        .need = need_page_poisoning, 
        .init = init_page_poisoning,
};

페이지 poisoning 디버깅에 관련된 핸들러 함수들이 등록되어 있다.

  • 디버그용으로 CONFIG_PAGE_POISONING 커널 옵션이 동작하는 경우에만 사용된다.

 

page_owner_ops 전역 객체

mm/page_owner.c

struct page_ext_operations page_owner_ops = {
        .need = need_page_owner,
        .init = init_page_owner,
};

페이지 owner 트래킹에 관련된 핸들러 함수들이 등록되어 있다.

  • 디버그용으로 CONFIG_PAGE_OWNER 커널 옵션이 동작하는 경우에만 사용된다.

 

참고

답글 남기기

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