[RFC PATCH 1/1] mm: page_frag: account page_frag pages in vmstat and meminfo
From: Yifan Li
Date: Fri Oct 09 2026 - 23:02:47 EST
The page_frag allocator backs sk_buff->head and the frags portion of
skb_shared_info for the network stack and NIC drivers, but pages
obtained from it are not accounted by any memory statistics counter.
Unlike slab (NR_SLAB_*) or page_pool, page_frag pages are invisible
to tools that derive "LostRAM = MemTotal - (sum of accounted memory)",
which inflates LostRAM and makes memory diagnosis misleading.
Pages enter the cache through a single entry point,
__page_frag_cache_refill(), so account them there. Decrement when the
cache page is freed via __page_frag_cache_drain() and page_frag_free().
Expose the counter, in pages, as "nr_page_frag" in /proc/vmstat and
"PageFrag" in /proc/meminfo so that page_frag memory is classified
instead of being counted as LostRAM.
Known limitation: a cache page handed over to an skb frags array is
released via put_page() (kfree_skb -> __skb_frag_unref ->
put_netmem -> put_page), bypassing page_frag_free(), so the counter
can over-count under network load. Covering that open release path
requires marking the page and accounting at the common free path;
that will be posted separately for discussion.
Verified on an arm64 Android device with 4K pages: at boot, PageFrag
in /proc/meminfo reports 14432 kB, consistent with nr_page_frag
(3608 pages) in /proc/vmstat.
Signed-off-by: Yifan Li <liyifan21@xxxxxxxxxx>
---
fs/proc/meminfo.c | 1 +
include/linux/mmzone.h | 1 +
mm/page_frag_cache.c | 14 ++++++++++++--
mm/vmstat.c | 1 +
4 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/fs/proc/meminfo.c b/fs/proc/meminfo.c
index b2813ff13..73241c3a3 100644
--- a/fs/proc/meminfo.c
+++ b/fs/proc/meminfo.c
@@ -108,6 +108,7 @@ static int meminfo_proc_show(struct seq_file *m, void *v)
show_val_kb(m, "Slab: ", sreclaimable + sunreclaim);
show_val_kb(m, "SReclaimable: ", sreclaimable);
show_val_kb(m, "SUnreclaim: ", sunreclaim);
+ show_val_kb(m, "PageFrag: ", global_node_page_state(NR_PAGE_FRAG));
seq_printf(m, "KernelStack: %8lu kB\n",
global_node_page_state(NR_KERNEL_STACK_KB));
#ifdef CONFIG_SHADOW_CALL_STACK
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index 94f9c3ff5..636f6ecb7 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -333,6 +333,7 @@ enum node_stat_item {
NR_KERNEL_FILE_PAGES,
NR_GPU_ACTIVE, /* Pages assigned to GPU objects */
NR_GPU_RECLAIM, /* Pages in shrinkable GPU pools */
+ NR_PAGE_FRAG,
NR_VM_NODE_STAT_ITEMS
};
diff --git a/mm/page_frag_cache.c b/mm/page_frag_cache.c
index e63efe78b..4f768093c 100644
--- a/mm/page_frag_cache.c
+++ b/mm/page_frag_cache.c
@@ -18,6 +18,7 @@
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/page_frag_cache.h>
+#include <linux/vmstat.h>
#include "page_alloc.h"
static unsigned long encoded_page_create(struct page *page, unsigned int order,
@@ -67,6 +68,9 @@ static struct page *__page_frag_cache_refill(struct page_frag_cache *nc,
nc->encoded_page = page ?
encoded_page_create(page, order, page_is_pfmemalloc(page)) : 0;
+ if (page)
+ mod_node_page_state(page_pgdat(page), NR_PAGE_FRAG,
+ 1L << order);
return page;
}
@@ -85,8 +89,11 @@ void __page_frag_cache_drain(struct page *page, unsigned int count)
{
VM_BUG_ON_PAGE(page_ref_count(page) == 0, page);
- if (page_ref_sub_and_test(page, count))
+ if (page_ref_sub_and_test(page, count)) {
+ mod_node_page_state(page_pgdat(page), NR_PAGE_FRAG,
+ -(1L << compound_order(page)));
free_frozen_pages(page, compound_order(page));
+ }
}
EXPORT_SYMBOL(__page_frag_cache_drain);
@@ -165,7 +172,10 @@ void page_frag_free(void *addr)
{
struct page *page = virt_to_head_page(addr);
- if (unlikely(put_page_testzero(page)))
+ if (unlikely(put_page_testzero(page))) {
+ mod_node_page_state(page_pgdat(page), NR_PAGE_FRAG,
+ -(1L << compound_order(page)));
free_frozen_pages(page, compound_order(page));
+ }
}
EXPORT_SYMBOL(page_frag_free);
diff --git a/mm/vmstat.c b/mm/vmstat.c
index cb5771453..883137cb6 100644
--- a/mm/vmstat.c
+++ b/mm/vmstat.c
@@ -1311,6 +1311,7 @@ const char * const vmstat_text[] = {
[I(NR_KERNEL_FILE_PAGES)] = "nr_kernel_file_pages",
[I(NR_GPU_ACTIVE)] = "nr_gpu_active",
[I(NR_GPU_RECLAIM)] = "nr_gpu_reclaim",
+ [I(NR_PAGE_FRAG)] = "nr_page_frag",
#undef I
/* system-wide enum vm_stat_item counters */
--
2.46.0.windows.1
#/******±¾Óʼþ¼°Æä¸½¼þº¬ÓÐСÃ×¹«Ë¾µÄ±£ÃÜÐÅÏ¢£¬½öÏÞÓÚ·¢Ë͸øÉÏÃæµØÖ·ÖÐÁгöµÄ¸öÈË»òȺ×é¡£½ûÖ¹ÈÎºÎÆäËûÈËÒÔÈκÎÐÎʽʹÓ㨰üÀ¨µ«²»ÏÞÓÚÈ«²¿»ò²¿·ÖµØÐ¹Â¶¡¢¸´ÖÆ¡¢»òÉ¢·¢£©±¾ÓʼþÖеÄÐÅÏ¢¡£Èç¹ûÄú´íÊÕÁ˱¾Óʼþ£¬ÇëÄúÁ¢¼´µç»°»òÓʼþ֪ͨ·¢¼þÈ˲¢É¾³ý±¾Óʼþ£¡ This e-mail and its attachments contain confidential information from XIAOMI, which is intended only for the person or entity whose address is listed above. Any use of the information contained herein in any way (including, but not limited to, total or partial disclosure, reproduction, or dissemination) by persons other than the intended recipient(s) is prohibited. If you receive this e-mail in error, please notify the sender by phone or email immediately and delete it!******/#