<kernel v5.0>
디버그 메모리 -3- (Page Owner 추적)
사용 조건
- CONFIG_PAGE_EXTENSION 커널 옵션
- CONFIG_PAGE_OWNER 커널 옵션
- “page_owner=on” 커널 파라미터
- “/sys/kernel/debug/page_owner” 파일을 출력하여 모든 페이지들의 onwer 추적을 수행할 수 있다.
- 커널 v3.19-rc1에서 추가되었다.
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 ...(생략)...
참고
- 디버그 메모리 -1- (Page Alloc) | 문c
- 디버그 메모리 -2- (Page Poisoning) | 문c
- 디버그 메모리 -3- (Page Owner 추적) | 문c
- 디버그 메모리 -4- (Idle Page 추적) | 문c
- page_ext_init_flatmem() | 문c
- page_ext_init() | 문c
- Useful gadget: /proc/page_owner | LWN.net
- page owner: Tracking about who allocated each page | Kernel.org
- PAGE_OWNER | barrios kernel story