디버그 메모리 -3- (Page Owner 추적)

<kernel v5.0>

디버그 메모리 -3- (Page Owner 추적)

사용 조건

  • CONFIG_PAGE_EXTENSION  커널 옵션
  • CONFIG_PAGE_OWNER 커널 옵션
  • “page_owner=on” 커널 파라미터

 

  • “/sys/kernel/debug/page_owner” 파일을 출력하여 모든 페이지들의 onwer 추적을 수행할 수 있다.
  • 커널 v3.19-rc1에서 추가되었다.

 

page_ext_init_flatmem-2

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 추적 기능을 사용한다.

 

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를 버디 할당자로 다시 돌려보내고 노드안에 있는 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_owner 분석을 위한 덤프

$ cat /sys/kernel/debug/page_owner
Page allocated via order 0, mask 0x6212ca(GFP_HIGHUSER_MOVABLE|__GFP_NOWARN|__GFP_NORETRY)
PFN 262144 type Movable Block 512 type Movable Flags 0xfffc00000020836(referenced|uptodate|lru|activ
e|arch_1|mappedtodisk)
 get_page_from_freelist+0x1050/0x1e68
 __alloc_pages_nodemask+0x220/0xffc
 alloc_pages_current+0xac/0xe8
 __page_cache_alloc+0x130/0x138
 __do_page_cache_readahead+0x118/0x2c4
 filemap_fault+0x3d4/0x9a0
 ext4_filemap_fault+0x44/0x60
 __do_fault+0x7c/0x33c
 __handle_mm_fault+0x112c/0x1cd0
 handle_mm_fault+0x1b0/0x27c
 do_page_fault+0x290/0x4d8
 do_translation_fault+0x88/0x8c
 do_mem_abort+0x58/0xe8
 el0_da+0x20/0x24

Page allocated via order 0, mask 0x6212ca(GFP_HIGHUSER_MOVABLE|__GFP_NOWARN|__GFP_NORETRY)
PFN 262148 type Movable Block 512 type Movable Flags 0xfffc00000020836(referenced|uptodate|lru|activ
e|arch_1|mappedtodisk)
 get_page_from_freelist+0x1050/0x1e68
 __alloc_pages_nodemask+0x220/0xffc
 alloc_pages_current+0xac/0xe8
 __page_cache_alloc+0x130/0x138
 __do_page_cache_readahead+0x118/0x2c4
 filemap_fault+0x3d4/0x9a0
 ext4_filemap_fault+0x44/0x60
 __do_fault+0x7c/0x33c
 __handle_mm_fault+0x112c/0x1cd0
 handle_mm_fault+0x1b0/0x27c
 do_page_fault+0x290/0x4d8
 do_translation_fault+0x88/0x8c
 do_mem_abort+0x58/0xe8
 el0_da+0x20/0x24

...(생략)...

 

참고

 

댓글 남기기

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