[RFC] mm/page_alloc: Enumerate bad page reasons

From: Anshuman Khandual
Date: Mon Mar 30 2020 - 02:52:02 EST


Enumerate all existing bad page reasons which can be used in bad_page() for
reporting via __dump_page(). Unfortunately __dump_page() cannot be changed.
__dump_page() is called from dump_page() that accepts a raw string and is
also an exported symbol that is currently being used from various generic
memory functions and other drivers. This reduces code duplication while
reporting bad pages.

Cc: Andrew Morton <akpm@xxxxxxxxxxxxxxxxxxxx>
Cc: David Hildenbrand <david@xxxxxxxxxx>
Cc: Michal Hocko <mhocko@xxxxxxxx>
Cc: Dan Williams <dan.j.williams@xxxxxxxxx>
Cc: Pavel Tatashin <pasha.tatashin@xxxxxxxxxx>
Cc: linux-mm@xxxxxxxxx
Cc: linux-kernel@xxxxxxxxxxxxxxx
Signed-off-by: Anshuman Khandual <anshuman.khandual@xxxxxxx>
---
Boot tested on arm64 and x86 platforms (next-20200327) but has been built
tested on many other platforms.

include/linux/memory.h | 36 ++++++++++++++++++++++++++++++++++++
mm/page_alloc.c | 40 ++++++++++++++++++++--------------------
2 files changed, 56 insertions(+), 20 deletions(-)

diff --git a/include/linux/memory.h b/include/linux/memory.h
index 7914b0dbd4bb..b2fb90fda537 100644
--- a/include/linux/memory.h
+++ b/include/linux/memory.h
@@ -82,6 +82,42 @@ struct memory_notify {
struct notifier_block;
struct mem_section;

+/*
+ * Bad page reasons
+ */
+enum page_bad_reason {
+ PAGE_BAD_NONZERO_MAPCOUNT,
+ PAGE_BAD_NONZERO_REFCOUNT,
+ PAGE_BAD_NONNULL_MAPPING,
+ PAGE_BAD_FREE_FLAGS,
+ PAGE_BAD_PREP_FLAGS,
+ PAGE_BAD_CGROUP_CHARGED,
+ PAGE_BAD_HWPOISON,
+ PAGE_BAD_TAIL_CORRUPTED,
+ PAGE_BAD_TAIL_NOT_SET,
+ PAGE_BAD_COMPOUND_HEAD,
+ PAGE_BAD_COMPOUND_MAPCOUNT,
+};
+
+static const char *const page_bad_names[] = {
+ [PAGE_BAD_NONZERO_MAPCOUNT] = "non-zero mapcout",
+ [PAGE_BAD_NONZERO_REFCOUNT] = "non-zero _refcount",
+ [PAGE_BAD_NONNULL_MAPPING] = "non-NULL mapping",
+ [PAGE_BAD_FREE_FLAGS] = "PAGE_FLAGS_CHECK_AT_FREE flag(s) set",
+ [PAGE_BAD_PREP_FLAGS] = "PAGE_FLAGS_CHECK_AT_PREP flag(s) set",
+ [PAGE_BAD_CGROUP_CHARGED] = "page still charged to cgroup",
+ [PAGE_BAD_HWPOISON] = "HWPoisoned (hardware-corrupted)",
+ [PAGE_BAD_TAIL_CORRUPTED] = "corrupted mapping in tail page",
+ [PAGE_BAD_TAIL_NOT_SET] = "PageTail not set",
+ [PAGE_BAD_COMPOUND_HEAD] = "compound_head not consistent",
+ [PAGE_BAD_COMPOUND_MAPCOUNT] = "non-zero compound_mapcount",
+};
+
+static inline const char *get_page_bad(int reason)
+{
+ return page_bad_names[reason];
+}
+
/*
* Priorities for the hotplug memory callback routines (stored in decreasing
* order in the callback chain)
diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index ca1453204e66..0f05da0fdc9a 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -68,6 +68,7 @@
#include <linux/lockdep.h>
#include <linux/nmi.h>
#include <linux/psi.h>
+#include <linux/memory.h>

#include <asm/sections.h>
#include <asm/tlbflush.h>
@@ -609,7 +610,7 @@ static inline int __maybe_unused bad_range(struct zone *zone, struct page *page)
}
#endif

-static void bad_page(struct page *page, const char *reason,
+static void bad_page(struct page *page, int reason,
unsigned long bad_flags)
{
static unsigned long resume;
@@ -638,7 +639,7 @@ static void bad_page(struct page *page, const char *reason,

pr_alert("BUG: Bad page state in process %s pfn:%05lx\n",
current->comm, page_to_pfn(page));
- __dump_page(page, reason);
+ __dump_page(page, get_page_bad(reason));
bad_flags &= page->flags;
if (bad_flags)
pr_alert("bad because of flags: %#lx(%pGp)\n",
@@ -1079,25 +1080,24 @@ static inline bool page_expected_state(struct page *page,

static void free_pages_check_bad(struct page *page)
{
- const char *bad_reason;
+ int bad_reason;
unsigned long bad_flags;

- bad_reason = NULL;
bad_flags = 0;

if (unlikely(atomic_read(&page->_mapcount) != -1))
- bad_reason = "nonzero mapcount";
+ bad_reason = PAGE_BAD_NONZERO_MAPCOUNT;
if (unlikely(page->mapping != NULL))
- bad_reason = "non-NULL mapping";
+ bad_reason = PAGE_BAD_NONNULL_MAPPING;
if (unlikely(page_ref_count(page) != 0))
- bad_reason = "nonzero _refcount";
+ bad_reason = PAGE_BAD_NONZERO_REFCOUNT;
if (unlikely(page->flags & PAGE_FLAGS_CHECK_AT_FREE)) {
- bad_reason = "PAGE_FLAGS_CHECK_AT_FREE flag(s) set";
+ bad_reason = PAGE_BAD_FREE_FLAGS;
bad_flags = PAGE_FLAGS_CHECK_AT_FREE;
}
#ifdef CONFIG_MEMCG
if (unlikely(page->mem_cgroup))
- bad_reason = "page still charged to cgroup";
+ bad_reason = PAGE_BAD_CGROUP_CHARGED;
#endif
bad_page(page, bad_reason, bad_flags);
}
@@ -1130,7 +1130,7 @@ static int free_tail_pages_check(struct page *head_page, struct page *page)
case 1:
/* the first tail page: ->mapping may be compound_mapcount() */
if (unlikely(compound_mapcount(page))) {
- bad_page(page, "nonzero compound_mapcount", 0);
+ bad_page(page, PAGE_BAD_COMPOUND_MAPCOUNT, 0);
goto out;
}
break;
@@ -1142,17 +1142,17 @@ static int free_tail_pages_check(struct page *head_page, struct page *page)
break;
default:
if (page->mapping != TAIL_MAPPING) {
- bad_page(page, "corrupted mapping in tail page", 0);
+ bad_page(page, PAGE_BAD_TAIL_CORRUPTED, 0);
goto out;
}
break;
}
if (unlikely(!PageTail(page))) {
- bad_page(page, "PageTail not set", 0);
+ bad_page(page, PAGE_BAD_TAIL_NOT_SET, 0);
goto out;
}
if (unlikely(compound_head(page) != head_page)) {
- bad_page(page, "compound_head not consistent", 0);
+ bad_page(page, PAGE_BAD_COMPOUND_HEAD, 0);
goto out;
}
ret = 0;
@@ -2110,29 +2110,29 @@ static inline void expand(struct zone *zone, struct page *page,

static void check_new_page_bad(struct page *page)
{
- const char *bad_reason = NULL;
+ int bad_reason;
unsigned long bad_flags = 0;

if (unlikely(atomic_read(&page->_mapcount) != -1))
- bad_reason = "nonzero mapcount";
+ bad_reason = PAGE_BAD_NONZERO_MAPCOUNT;
if (unlikely(page->mapping != NULL))
- bad_reason = "non-NULL mapping";
+ bad_reason = PAGE_BAD_NONNULL_MAPPING;
if (unlikely(page_ref_count(page) != 0))
- bad_reason = "nonzero _refcount";
+ bad_reason = PAGE_BAD_NONZERO_REFCOUNT;
if (unlikely(page->flags & __PG_HWPOISON)) {
- bad_reason = "HWPoisoned (hardware-corrupted)";
+ bad_reason = PAGE_BAD_HWPOISON;
bad_flags = __PG_HWPOISON;
/* Don't complain about hwpoisoned pages */
page_mapcount_reset(page); /* remove PageBuddy */
return;
}
if (unlikely(page->flags & PAGE_FLAGS_CHECK_AT_PREP)) {
- bad_reason = "PAGE_FLAGS_CHECK_AT_PREP flag set";
+ bad_reason = PAGE_BAD_PREP_FLAGS;
bad_flags = PAGE_FLAGS_CHECK_AT_PREP;
}
#ifdef CONFIG_MEMCG
if (unlikely(page->mem_cgroup))
- bad_reason = "page still charged to cgroup";
+ bad_reason = PAGE_BAD_CGROUP_CHARGED;
#endif
bad_page(page, bad_reason, bad_flags);
}
--
2.20.1