#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "avm_sammel.h" #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 9, 0) static inline bool pgdat_is_empty(pg_data_t *pgdat) { return !pgdat->node_start_pfn && !pgdat->node_spanned_pages; } #endif/*--- #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 6, 0) ---*/ /*--- #define DEBUG_PAGE_STATISTIC ---*/ #if defined(DEBUG_PAGE_STATISTIC) #define DBG_TRC(args...) pr_info(args) #else/*--- #if defined(DEBUG_PAGE_STATISTIC) ---*/ #define DBG_TRC(args...) #endif/*--- #else ---*//*--- #if defined(DEBUG_PAGE_STATISTIC) ---*/ static atomic_t lock_memstat; #define MAX_MEMORY_FREE_STATISTIC (1 << (MAX_ORDER + 1)) #if defined(CONFIG_AVM_PAGE_TRACE) #if defined(CONFIG_PAGEFLAGS_EXTENDED) # define PGFLAGS_IGNORE_MASK BIT(PG_head) #else # define PGFLAGS_IGNORE_MASK 0 #endif #define MAX_SCORELIST_ENRIES 32 #define MAX_SCORETABLE 2 #else/*--- #if defined(CONFIG_AVM_PAGE_TRACE) ---*/ #define MAX_SCORELIST_ENRIES 1 #define MAX_SCORETABLE 1 #endif/*--- #else ---*//*--- #if defined(CONFIG_AVM_PAGE_TRACE) ---*/ struct _avm_pagealloc_score { unsigned long current_pc; unsigned short sum; unsigned short reserved; }; struct _avm_pagealloc_score_table { struct _avm_pagealloc_score pagealloc_score[MAX_ORDER + 1][MAX_SCORELIST_ENRIES + 1]; }; static struct _avm_pagealloc_score_table pagealloc_score_table[MAX_SCORETABLE]; #define NON_CLASSIFIED_PAGEMASK ((BIT(NR_PAGEFLAGS) - 1) & ~(PGFLAGS_IGNORE_MASK)) /*--- kein PG_... gesetzt, auszer HEAD ---*/ static struct _scorelist { const char *prefix; struct _avm_pagealloc_score_table *table; unsigned int mask; unsigned int result; unsigned int follow; } scorelist[MAX_SCORETABLE + 1] = { #if defined(CONFIG_AVM_PAGE_TRACE) {.prefix = "non-classified ", .mask = NON_CLASSIFIED_PAGEMASK, .result = 0, .follow = 0 }, #define SCORELISTENTRY(a) .prefix = #a" ", .mask = BIT(PG_##a), .result = BIT(PG_##a), .follow = 1 /*--- { SCORELISTENTRY(mlocked) }, ---*/ /*--- { SCORELISTENTRY(slab) }, ---*/ /*--- { SCORELISTENTRY(writeback) }, ---*/ /*--- { SCORELISTENTRY(swapbacked) }, ---*/ /*--- { SCORELISTENTRY(uptodate) }, ---*/ /*--- { SCORELISTENTRY(mappedtodisk) }, ---*/ /*--- { SCORELISTENTRY(active) }, ---*/ { SCORELISTENTRY(dirty) }, /*--- { SCORELISTENTRY(lru) }, ---*/ #endif/*--- #if defined(CONFIG_AVM_PAGE_TRACE) ---*/ {.prefix = NULL, }, /*--- END-Marker ---*/ }; #if defined(CONFIG_AVM_PAGE_TRACE) struct _page_extension { unsigned long pc; }; static struct _node_extension_table { struct pglist_data *pgdat; struct _page_extension *page_ext; } node_extension_table[2]; /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ int __init avm_alloc_page_extension(void) { pg_data_t *pgdat; for (pgdat = first_online_pgdat(); pgdat ; pgdat = next_online_pgdat(pgdat)) { unsigned int alloc_size; if(pgdat_is_empty(pgdat)) { continue; } if(pgdat->node_id >= (int)ARRAY_SIZE(node_extension_table)) { pr_err("%s error node_id %u exceed node_extension_table \n", __func__, pgdat->node_id); continue; } alloc_size = sizeof(struct _page_extension) * pgdat->node_spanned_pages; if(node_extension_table[pgdat->node_id].page_ext == NULL) { struct _page_extension *pg_ext = alloc_pages_exact(alloc_size, GFP_KERNEL | __GFP_ZERO); if(pg_ext) { node_extension_table[pgdat->node_id].page_ext = pg_ext; node_extension_table[pgdat->node_id].pgdat = pgdat; mb(); } pr_err("%s node_extension_table[%u] entries=%lu (size=%u) %s alloced\n", __func__, pgdat->node_id, pgdat->node_spanned_pages, alloc_size, pg_ext ? "" : "not"); } } return 0; } arch_initcall(avm_alloc_page_extension); /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ static struct _page_extension *get_page_extension(struct page *page) { struct _page_extension *pg_exttab; pg_data_t *pgdat; unsigned long pfn; unsigned int nid = page_to_nid(page); if(unlikely(nid >= ARRAY_SIZE(node_extension_table))) { /*--- pr_err("%s: error1 nid=%u pfn=%lu\n", __func__, nid, page_to_pfn(page)); ---*/ return NULL; } pgdat = node_extension_table[nid].pgdat; if(unlikely(pgdat == NULL)) { return NULL; } pfn = page_to_pfn(page) - pgdat->node_start_pfn; if(unlikely(pfn >= pgdat->node_spanned_pages)) { /*--- pr_err("%s: error2 nid=%u pfn=%lu\n", __func__, nid, page_to_pfn(page)); ---*/ return NULL; } pg_exttab = node_extension_table[nid].page_ext; return &pg_exttab[pfn]; } /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ void avm_set_page_current_pc(struct page *page, unsigned long pc) { struct _page_extension *pg_ext = get_page_extension(page); if(pg_ext) { pg_ext->pc = pc; } } /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ unsigned long avm_get_page_current_pc(struct page *page) { struct _page_extension *pg_ext = get_page_extension(page); if(pg_ext) { return pg_ext->pc; } return 0; } #else/*--- #if defined(CONFIG_AVM_PAGE_TRACE) ---*/ #define avm_get_page_current_pc(page) 0 #endif/*--- #if defined(CONFIG_AVM_PAGE_TRACE) ---*/ #if defined(CONFIG_MIPS) && !defined(CONFIG_SOC_GRX500) /*--- unsere MIPS-Boxen auszer GRX haben max. 256 MByte (max. 65 k Pages) ---*/ typedef unsigned short mem_count_t; #else/*--- #endif ---*//*--- #if defined(CONFIG_MIPS) ---*/ typedef unsigned int mem_count_t; #endif/*--- #if defined(CONFIG_MIPS) ---*/ struct _avm_memory_statistic { mem_count_t free_statistic[MAX_MEMORY_FREE_STATISTIC]; mem_count_t lru_free_statistic[MAX_MEMORY_FREE_STATISTIC]; mem_count_t buddy_free_statistic[MAX_ORDER + 1]; mem_count_t flag_statistic[32]; }; static struct _avm_memory_statistic avm_memory_statistic; /*--------------------------------------------------------------------------------*\ * Eintrag 0 fuer current_pc = 0; \*--------------------------------------------------------------------------------*/ static void add_to_page_scorelist(struct _avm_pagealloc_score_table *score_table, unsigned long current_pc, unsigned int order) { unsigned long i; if(order > MAX_ORDER) { pr_err("%s: warning: invalid order=%u\n", __func__, order); return; } if((current_pc == 0)) { /*--- pr_err("warning: no current_pc order=%u\n", order); ---*/ score_table->pagealloc_score[order][0].sum++; return; } for(i = 1; i < ARRAY_SIZE(score_table->pagealloc_score[0]); i++) { if(score_table->pagealloc_score[order][i].current_pc == 0) { score_table->pagealloc_score[order][i].current_pc = current_pc; score_table->pagealloc_score[order][i].sum++; return; } if(score_table->pagealloc_score[order][i].current_pc == current_pc) { score_table->pagealloc_score[order][i].sum++; return; } } printk_ratelimited(KERN_ERR"warning: no alloc %pS order =%u -> add to [0]\n", (void *)current_pc, order); score_table->pagealloc_score[order][0].sum++; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static void add_to_page_all_scorelist(unsigned long current_pc, unsigned int order, unsigned int page_flag) { struct _scorelist *pscore = scorelist; while(pscore->table) { if((page_flag & pscore->mask) == pscore->result) { add_to_page_scorelist(pscore->table, current_pc, order); if(pscore->follow == 0) { return; } } pscore++; } } #if 1 /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static char *name_pgflag(char *txt, unsigned int txt_len, unsigned int pg_flag) { snprintf(txt, txt_len, "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s" #if (LINUX_VERSION_CODE < KERNEL_VERSION(4,4,0)) #ifdef CONFIG_PAGEFLAGS_EXTENDED "%s%s" #else "%s" #endif #else "%s" #endif #ifdef CONFIG_MMU "%s" #endif #ifdef CONFIG_ARCH_USES_PG_UNCACHED "%s" #endif , pg_flag & ( 1 << PG_locked) ? " locked" : "", /* Page is locked. Don't touch. */ pg_flag & ( 1 << PG_error) ? " error" : "", pg_flag & ( 1 << PG_referenced) ? " referenced" : "", pg_flag & ( 1 << PG_uptodate) ? " uptodate" : "", pg_flag & ( 1 << PG_dirty) ? " dirty" : "", pg_flag & ( 1 << PG_lru) ? " lru" : "", pg_flag & ( 1 << PG_active) ? " active" : "", pg_flag & ( 1 << PG_slab) ? " slab" : "", pg_flag & ( 1 << PG_owner_priv_1) ? " owner_priv_1" : "", /* Owner use. If pagecache, fs may use*/ pg_flag & ( 1 << PG_arch_1) ? " arch_1" : "", pg_flag & ( 1 << PG_reserved) ? " reserved" : "", pg_flag & ( 1 << PG_private) ? " private" : "", /* If pagecache, has fs-private data */ pg_flag & ( 1 << PG_private_2) ? " private_2" : "", /* If pagecache, has fs aux data */ pg_flag & ( 1 << PG_writeback) ? " writeback" : "", /* Page is under writeback */ #if (LINUX_VERSION_CODE < KERNEL_VERSION(4,4,0)) #ifdef CONFIG_PAGEFLAGS_EXTENDED pg_flag & ( 1 << PG_head) ? " head" : "", /* A head page */ pg_flag & ( 1 << PG_tail) ? " tail" : "", /* A tail page */ #else pg_flag & ( 1 << PG_compound) ? " compound" : "", /* A compound page */ #endif #else pg_flag & ( 1 << PG_head) ? " head" : "", /* A head page */ #endif pg_flag & ( 1 << PG_swapcache) ? " swapcache" : "", /* Swap page: swp_entry_t in private */ pg_flag & ( 1 << PG_mappedtodisk) ? " mappedtodisk" : "", /* Has blocks allocated on-disk */ pg_flag & ( 1 << PG_reclaim) ? " reclaim" : "", /* To be reclaimed asap */ pg_flag & ( 1 << PG_swapbacked) ? " swapbacked" : "", /* Page is backed by RAM/swap */ pg_flag & ( 1 << PG_unevictable) ? " unevictable" : "", /* Page is "unevictable" */ #ifdef CONFIG_MMU pg_flag & ( 1 << PG_mlocked) ? " mlocked" : "", /* Page is vma mlocked */ #endif #ifdef CONFIG_ARCH_USES_PG_UNCACHED pg_flag & ( 1<< PG_uncached) ? " uncached" : "", /* Page has been mapped as uncached */ #endif "" ); return txt; } #endif #define local_print(info, args ...) info->printf(info, args) /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static inline unsigned int __mark_continues_free(unsigned int free, mem_count_t table[], unsigned int max_entries) { if(free == 0) { return free; } if(free >= max_entries) { free = max_entries - 1; } table[free]++; return 0; } #define mark_continues_free(free, table) __mark_continues_free(free, table, ARRAY_SIZE(table)) /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static void dislay_page_scorelist(struct avm_oom_info_data *info, const char *prefix, struct _avm_pagealloc_score_table *score_table) { unsigned int i, order, sum = 0; for(order = 0; order < (MAX_ORDER + 1); order++) { if((score_table->pagealloc_score[order][0].sum == 0) && (score_table->pagealloc_score[order][1].sum == 0)) { continue; } local_print(info, "%s%s-Pages order=%u (%u KiB):\n", prefix, order ? "Head" : "Single", order, 4 << order); for(i = 0; i < ARRAY_SIZE(score_table->pagealloc_score[0]); i++) { if(score_table->pagealloc_score[order][i].sum == 0) { if(i == 0) { continue; } break; } sum += (unsigned int)score_table->pagealloc_score[order][i].sum << order; if(order == 0) { local_print(info, "[%3u] %5u (%6u KiB) %pS\n", i, score_table->pagealloc_score[order][i].sum, (unsigned int)score_table->pagealloc_score[order][i].sum * 4, (void *)score_table->pagealloc_score[order][i].current_pc); } else { local_print(info, "[%3u] %5u - pages=%5u (%6u KiB) %pS\n", i, score_table->pagealloc_score[order][i].sum, (unsigned int)score_table->pagealloc_score[order][i].sum << order, ((unsigned int)score_table->pagealloc_score[order][i].sum << order) * 4, (void *)score_table->pagealloc_score[order][i].current_pc); } } } if(sum) { local_print(info, "Sum of %sPages: %u (%6u KiB)\n", prefix, sum, sum * 4); } } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static void dislay_page_all_scorelist(struct avm_oom_info_data *info) { struct _scorelist *pscore = scorelist; while(pscore->table) { dislay_page_scorelist(info, pscore->prefix, pscore->table); pscore++; } } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static inline int is_free_page(struct page *page) { if ((page_mapcount(page) | (page->mapping != NULL) | (atomic_read(&page->_count) != 0) | (page->flags & PAGE_FLAGS_CHECK_AT_FREE))) { return 0; } return 1; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static void add_pagetype_field(unsigned int page_flags, mem_count_t flag_statistic[]) { unsigned int i = 0; while(page_flags) { if(page_flags & 0x01) { flag_statistic[i]++; } i++; page_flags >>= 1; } } /**--------------------------------------------------------------------------------**\ * ret: 0 in relevanten parts identisch \**--------------------------------------------------------------------------------**/ int save_page_cmp(struct page *dst __maybe_unused, unsigned long *last_pc __maybe_unused, struct page *act __maybe_unused) { int ret = 1; #if defined(DEBUG_PAGE_STATISTIC) unsigned long act_pc = avm_get_page_current_pc(act); if((dst->flags == act->flags) && (compound_order(dst) == compound_order(act)) && (atomic_read(&dst->_count) == atomic_read(&act->_count)) && (*last_pc == act_pc)) { ret = 0; } memcpy(dst, act, sizeof(*dst)); *last_pc = act_pc; #endif/*--- #if defined(DEBUG_PAGE_STATISTIC) ---*/ return ret; } #if defined(DEBUG_PAGE_STATISTIC) /**--------------------------------------------------------------------------------**\ * die pages fuer den memmap-Bereich muessen reserviert sein! \**--------------------------------------------------------------------------------**/ static int check_memmap_page_reserved(void) { int gaps = 0; pg_data_t *pgdat; void *memmap_start, *memmap_end; struct page *pagememmap_start, *pagememmap_end; unsigned long pfnmemmap_start, pfnmemmap_end; for (pgdat = first_online_pgdat(); pgdat ; pgdat = next_online_pgdat(pgdat)) { if(pgdat_is_empty(pgdat)) { pr_err("[%s] pgdat 0x%lx is empty\n", __func__, (unsigned long)pgdat); continue; } /*--- die virtuellen Addressen der memmap ermitteln .. ---*/ memmap_start = pgdat_page_nr(pgdat, 0); memmap_end = pgdat_page_nr(pgdat, pgdat->node_spanned_pages - 1) + sizeof(struct page) -1; /*--- ... das zugehoerige page-struct-Area in mem_map ---*/ pagememmap_start = virt_to_page(memmap_start); pagememmap_end = virt_to_page(memmap_end); pfnmemmap_start = page_to_pfn(pagememmap_start); pfnmemmap_end = page_to_pfn(pagememmap_end); pr_info("[%s] pfn %lu - %lu (mem_map= 0x%p - 0x%p) should be reserved - because contain page-structs", __func__, pfnmemmap_start, pfnmemmap_end, pagememmap_start, pagememmap_end); while(pagememmap_start && (pagememmap_start < pagememmap_end)) { if(pfn_valid((unsigned long)page_address(pagememmap_start))) { if(!PageReserved(pagememmap_start)) { pr_err("%s: error page 0x%p (pfn=%lu) flags=0x%08lx (addr=0x%p) should be reserved!", __func__, pagememmap_start, page_to_pfn(pagememmap_start), pagememmap_start->flags, page_address(pagememmap_start)); return -1; } } else { gaps++; } pagememmap_start++; } } pr_info("%s: mem_map-area: pfn %lu - %lu marked as reserved: ok %s\n", __func__, pfnmemmap_start, pfnmemmap_end, gaps ? "(include gaps)" : ""); return 0; } #endif/*--- #if defined(DEBUG_PAGE_STATISTIC) ---*/ /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static void avm_page_statistic_print(struct avm_oom_info_data *info, void *_complete){ struct _avm_memory_statistic *pmemstat = &avm_memory_statistic; char txt[256]; unsigned int page_flags, order; unsigned long flags; unsigned int buddy_pages, _count; struct page *page; struct zone *zone; pg_data_t *pgdat; unsigned int i = 0; unsigned int page_count, lru_free_pages, sum_pages, free_pages, n_order = 0; struct page last_page; unsigned long current_pc, last_pc = 0; int point_flag = 0, gap_flag = 0; if(atomic_add_return(1, &lock_memstat) > 1) { return; } memset(&last_page, -1, sizeof(last_page)); memset(pagealloc_score_table, 0, sizeof(pagealloc_score_table)); memset(pmemstat, 0, sizeof(struct _avm_memory_statistic)); lru_free_pages = 0; free_pages = 0; sum_pages = 0; for (pgdat = first_online_pgdat(); pgdat ; pgdat = next_online_pgdat(pgdat)) { unsigned int is_free = 0; unsigned int is_lru_free = 0; if(pgdat_is_empty(pgdat)) { pr_err("[%s] pgdat 0x%lx is empty\n", __func__, (unsigned long)pgdat); continue; } DBG_TRC("[%s] scan pgdat: start 0x%lx(mem_map=0x%p) present 0x%lx spanned 0x%lx\n", __func__, pgdat->node_start_pfn, pgdat_page_nr(pgdat, 0), pgdat->node_present_pages, pgdat->node_spanned_pages); local_print(info, "scan pgdat: start 0x%lx(mem_map=0x%p) present 0x%lx spanned 0x%lx\n", pgdat->node_start_pfn, pgdat_page_nr(pgdat, 0), pgdat->node_present_pages, pgdat->node_spanned_pages); for(page_count = 0; page_count < pgdat->node_spanned_pages; page_count++) { page = pgdat_page_nr(pgdat, page_count); if(!pfn_valid(page_to_pfn(page))) { /*--- es kann gaps in der page struct-Table geben, da auch der memory gaps enthalten kann ! ---*/ if(gap_flag == 0) { DBG_TRC("gaps page=0x%p pfn=%lu\n", page, page_to_pfn(page)); } else if(gap_flag == 1) { DBG_TRC("...\n"); } gap_flag++; continue; } gap_flag = 0; zone = page_zone(page); spin_lock_irqsave(&zone->lock, flags); page_flags = page->flags; if (PageBuddy(page)) { order = page_private(page); /*--- PageBuddy must check ! ---*/ spin_unlock_irqrestore(&zone->lock, flags); is_free = mark_continues_free(is_free, pmemstat->free_statistic); is_lru_free = mark_continues_free(is_lru_free, pmemstat->lru_free_statistic); if(order <= MAX_ORDER) { pmemstat->buddy_free_statistic[order]++; DBG_TRC("Buddy: page=0x%p pfn=%lu-%lu (addr=0x%p) order %2u %s (0x%08x)\n", page, page_to_pfn(page), page_to_pfn(page) + (1 << order) - 1, page_address(page), order, name_pgflag(txt, sizeof(txt), page_flags), page_flags); page_count += (1 << order) - 1; } else { pr_info("obscure buddy: page=0x%p pfn=%lu (addr=0x%p) order %2u %s (0x%08x)\n", page, page_to_pfn(page), page_address(page), order, name_pgflag(txt, sizeof(txt), page_flags), page_flags); } memset(&last_page, -1, sizeof(last_page)); last_pc = 0; continue; } current_pc = avm_get_page_current_pc(page); if(save_page_cmp(&last_page, &last_pc, page)) { point_flag = 0; } else { point_flag++; } if(PageHead(page)) { order = compound_order(page); if(order > MAX_ORDER) { spin_unlock_irqrestore(&zone->lock, flags); pr_info("obscure head: page=0x%p pfn=%lu (addr=0x%p) invalid order %2u %s (0x%08x)\n", page, page_to_pfn(page), page_address(page), order, name_pgflag(txt, sizeof(txt), page_flags), page_flags); continue; } if((_count = atomic_read(&page->_count)) == 0) { pr_info("obscure head: page=0x%p pfn=%lu (addr=0x%p) order %2u invalid _count=%u %s (0x%08x) %pS\n", page, page_to_pfn(page), page_address(page), compound_order(page), _count, name_pgflag(txt, sizeof(txt), page_flags), page_flags, (void *)current_pc); } } if((_count = atomic_read(&page->_count))) { order = compound_order(page); /*--- hier alle nicht klassifizierten Pages tracen: ---*/ if(point_flag == 0) { DBG_TRC("non classified: page=0x%p pfn=%lu (addr=0x%p) order=%2u _count=%u %s (0x%08x) %pS\n", page, page_to_pfn(page), page_address(page), order, _count, name_pgflag(txt, sizeof(txt), page_flags), page_flags, (void *)current_pc); } else if(point_flag == 1) { DBG_TRC("...\n"); point_flag++; } add_to_page_all_scorelist(current_pc, order, page_flags); } if(PageReserved(page)) { order = compound_order(page); _count = atomic_read(&page->_count); spin_unlock_irqrestore(&zone->lock, flags); if(point_flag == 0) { DBG_TRC("reserved: page=0x%p pfn=%lu (addr=0x%p) order=%2u _count=%u %s (0x%08x) %pS\n", page, page_to_pfn(page), page_address(page), order, _count, name_pgflag(txt, sizeof(txt), page_flags), page_flags, (void *)current_pc); } else if(point_flag == 1) { DBG_TRC("...\n"); } is_free = mark_continues_free(is_free, pmemstat->free_statistic); is_lru_free = mark_continues_free(is_lru_free, pmemstat->lru_free_statistic); pmemstat->flag_statistic[PG_reserved]++; continue; } add_pagetype_field(page_flags, &pmemstat->flag_statistic[0]); if(PageHead(page)) { order = compound_order(page); _count = atomic_read(&page->_count); spin_unlock_irqrestore(&zone->lock, flags); DBG_TRC("head: page=%p pfn=%lu (addr=0x%p) order=%2u _count=%u %s (0x%08x) %pS\n", page, page_to_pfn(page), page_address(page), order, _count, name_pgflag(txt, sizeof(txt), page_flags), page_flags, (void *)current_pc); page_count += (1 << order) - 1; is_free = mark_continues_free(is_free, pmemstat->free_statistic); is_lru_free = mark_continues_free(is_lru_free, pmemstat->lru_free_statistic); continue; } if(is_free_page(page)) { _count = atomic_read(&page->_count); spin_unlock_irqrestore(&zone->lock, flags); if(point_flag == 0) { DBG_TRC("free-page: page=0x%p pfn=%lu (addr=0x%p) _count=%u %s (0x%08x) %pS\n", page, page_to_pfn(page), page_address(page), _count, name_pgflag(txt, sizeof(txt), page_flags), page_flags, (void *)current_pc); } else if(point_flag == 1) { DBG_TRC("...\n"); } free_pages++; is_free++; continue; } spin_unlock_irqrestore(&zone->lock, flags); is_free = mark_continues_free(is_free, pmemstat->free_statistic); if(page_flags & (1 << PG_lru)) { if(!(page_flags & (1 << PG_dirty))) { lru_free_pages++; is_lru_free++; continue; } } is_lru_free = mark_continues_free(is_lru_free, pmemstat->lru_free_statistic); } is_free = mark_continues_free(is_free, pmemstat->free_statistic); is_lru_free = mark_continues_free(is_lru_free, pmemstat->lru_free_statistic); sum_pages += pgdat->node_spanned_pages; } /*------------ Ausgabe -------------*/ if(_complete) { local_print(info, "Free pages statistic\n"); } if(_complete) { local_print(info, "Count([KiB]): on-LRU free Buddy Sum( max) \n"); } buddy_pages = 0; for(i = 1 ; i < MAX_MEMORY_FREE_STATISTIC ; i++) { n_order = (i & ~((1 << ffs(i)) - 1)) == 0 ? ffs(i) : 0; if(pmemstat->lru_free_statistic[i] || pmemstat->free_statistic[i] || (n_order)) { unsigned int buddy = n_order ? pmemstat->buddy_free_statistic[n_order - 1] : 0; if(_complete) { local_print(info, "%4u (%5u): %5u %5u %5u %5u%s\n", i, i * 4, pmemstat->lru_free_statistic[i], pmemstat->free_statistic[i], buddy, (unsigned int)pmemstat->lru_free_statistic[i] + (unsigned int)pmemstat->free_statistic[i] + buddy, n_order ? " =======" : ""); } buddy_pages += buddy * i; } } dislay_page_all_scorelist(info); #if 0 local_print(info, "Free Buddy-List statistic\n"); local_print(info, "Count([KiB]):\n"); for(i = 0; i < ARRAY_SIZE(pmemstat->buddy_free_statistic); i++) { if(pmemstat->buddy_free_statistic[i]) { local_print(info, "%4u (%5u): %5u\n", (1U << i), (1U << i) * 4, pmemstat->buddy_free_statistic[i]); } } #endif local_print(info, "%5u (%6u KiB) total pages\n", sum_pages, sum_pages * 4); local_print(info, "%5u (%6u KiB) free pages\n", free_pages , free_pages * 4); local_print(info, "%5u (%6u KiB) free pages on Buddy\n", buddy_pages, buddy_pages * 4); local_print(info, "%5u (%6u KiB) pages on LRU (%u freeable pages)\n", pmemstat->flag_statistic[PG_lru], pmemstat->flag_statistic[PG_lru] * 4, lru_free_pages); local_print(info, "%5u (%6u KiB) pages allocated on-disc\n", pmemstat->flag_statistic[PG_mappedtodisk], pmemstat->flag_statistic[PG_mappedtodisk] * 4); local_print(info, "%5u (%6u KiB) pages for slab\n", pmemstat->flag_statistic[PG_slab], pmemstat->flag_statistic[PG_slab] * 4); local_print(info, "%5u (%6u KiB) pages to be reclaimed asap\n", pmemstat->flag_statistic[PG_reclaim], pmemstat->flag_statistic[PG_reclaim] * 4); local_print(info, "%5u (%6u KiB) pages are reserved\n", pmemstat->flag_statistic[PG_reserved], pmemstat->flag_statistic[PG_reserved] * 4); #ifdef CONFIG_PAGEFLAGS_EXTENDED local_print(info, "%5u (%6u KiB) pages as tail\n", pmemstat->flag_statistic[PG_tail], pmemstat->flag_statistic[PG_tail] * 4); local_print(info, "%5u (%6u KiB) pages as head\n", pmemstat->flag_statistic[PG_head], pmemstat->flag_statistic[PG_head] * 4); #endif if(pmemstat->flag_statistic[PG_mlocked]) { local_print(info, "%5u (%6u KiB) pages are mlocked\n", pmemstat->flag_statistic[PG_mlocked], pmemstat->flag_statistic[PG_mlocked] * 4); } if(pmemstat->flag_statistic[PG_swapcache]) { local_print(info, "%5u (%6u KiB) pages are swap pages\n", pmemstat->flag_statistic[PG_swapcache], pmemstat->flag_statistic[PG_swapcache] * 4); } #if 0 if(pmemstat->flag_statistic[PG_swapbacked]) { local_print(info, "%5u (%6u KiB) pages are swapbacked\n", pmemstat->flag_statistic[PG_swapbacked], pmemstat->flag_statistic[PG_swapbacked] * 4); } #endif atomic_set(&lock_memstat, 0); } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static int oom_printf(struct avm_oom_info_data *data, const char *f, ...) { int ret; va_list args; struct va_format fmt; va_start(args, f); fmt.fmt = f; fmt.va = &args; ret = pr_err("%pV", &fmt); va_end(args); return ret; } void show_avm_page_statistic(int complete) { struct avm_oom_info_data data = { .printf = oom_printf }; avm_page_statistic_print(&data, (void *)complete); } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static void __init init_scorelist(void) { int i = 0; struct _scorelist *pscore = scorelist; while(pscore->prefix) { if(i >= (int)ARRAY_SIZE(pagealloc_score_table)) { pr_err("%s: can't fill complete score-list\n", __func__); break; } pscore->table = &pagealloc_score_table[i++]; DBG_TRC("%s: complete score-list[%u] %s: table=%p mask=%x result=%x\n", __func__, i - 1, pscore->prefix, pscore->table, pscore->mask, pscore->result); pscore++; } } struct avm_oom_file_info_data { struct avm_oom_info_data data; struct seq_file *file; }; static int file_printf(struct avm_oom_info_data *_data, const char *f, ...) { struct avm_oom_file_info_data *data = (struct avm_oom_file_info_data*)_data; va_list args; struct va_format fmt; va_start(args, f); fmt.fmt = f; fmt.va = &args; seq_printf(data->file, "%pV", &fmt); va_end(args); return 0; } static void avm_proc_page_statistic(struct seq_file *file, void *priv) { struct avm_oom_file_info_data data = { .data.printf = file_printf, .file = file }; avm_page_statistic_print(&data.data, (void*)1); } static int dump_task_memory(struct notifier_block *block, unsigned long event, void *_data) { struct avm_oom_info_data *oom_data = _data; avm_page_statistic_print(oom_data, (void*)0); return NOTIFY_DONE; } static struct notifier_block page_statistic_block = { .notifier_call = dump_task_memory }; /*--------------------------------------------------------------------------------*\ registrieren: \*--------------------------------------------------------------------------------*/ int __init init_avm_page_statistic(void) { DBG_TRC("%s: memory-sizes: avm_memory_statistic=%u pagealloc_score_table=%u page-size=%u\n", __func__, sizeof(struct _avm_memory_statistic), sizeof(pagealloc_score_table), sizeof(struct page)); init_scorelist(); add_simple_proc_file( "avm/page_statistic", NULL, avm_proc_page_statistic, NULL); avm_oom_info_chain_register(&page_statistic_block); #if defined(DEBUG_PAGE_STATISTIC) check_memmap_page_reserved(); show_avm_page_statistic(1); #endif/*--- #if defined(DEBUG_PAGE_STATISTIC) ---*/ return 0; } late_initcall(init_avm_page_statistic);