<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
