mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [RFC PATCH 0/1] mm: page_frag: account page_frag pages in vmstat and meminfo
@ 2026-10-10  2:59 Yifan Li
  2026-10-10  2:59 ` [RFC PATCH 1/1] " Yifan Li
  2026-10-12 10:36 ` [RFC PATCH 0/1] " David Hildenbrand (Arm)
  0 siblings, 2 replies; 5+ messages in thread
From: Yifan Li @ 2026-10-10  2:59 UTC (permalink / raw)
  To: akpm, david; +Cc: linux-mm, linux-fsdevel, linux-kernel, Yifan Li

This is an RFC.

Pages held by the page_frag cache are not accounted by any memory
statistics counter, so on systems that derive "LostRAM" as MemTotal
minus the sum of accounted memory they inflate LostRAM and lead to
misleading "leak" conclusions, even though the memory is legitimately
used by the network path.

The page_frag allocator (page_frag_alloc / napi_alloc_frag /
netdev_alloc_frag) backs sk_buff->head and the frags portion of
skb_shared_info.  Unlike slab (NR_SLAB_*) or page_pool, page_frag
pages have no counter.  A preliminary measurement on a 4K-page
arm64 Android device shows 14432 kB (3608 pages) of page_frag
pages already in use at boot, and the memory footprint grows
larger under heavy load (e.g. sustained traffic through the
virtio_net / tun receive paths).

Patch 1/1 adds an NR_PAGE_FRAG node counter and exposes it as
nr_page_frag in /proc/vmstat and PageFrag in /proc/meminfo.  Pages
enter the cache through a single entry point
__page_frag_cache_refill(), so the increment side is complete; the
decrements sit on the paths that free the cache page.

The remaining gap is on the release side, which is open: a cache page
handed to an skb frags array (skb_add_rx_frag() /
skb_fill_page_desc(), without an extra get_page) is freed 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.

Closing that needs the page to carry a marker checked at the common
free path, e.g. either

  1. a page flag: PAGEFLAG(PageFrag, page_frag, PF_HEAD), accounted
     and cleared in __free_pages_prepare(); or

  2. a page type: PGTY_page_frag reusing _mapcount, accounted in
     free_frozen_pages() (requires that page_frag pages are never
     mapped into userspace; true for the current RX paths).

With either variant, the per-path decrements of patch 1/1 would go
away and only the increment would remain.  Which variant fits
mainline better?

Yifan Li (1):
  mm: page_frag: account page_frag pages in vmstat and meminfo

 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(-)

--
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!******/#

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [RFC PATCH 1/1] mm: page_frag: account page_frag pages in vmstat and meminfo
  2026-10-10  2:59 [RFC PATCH 0/1] mm: page_frag: account page_frag pages in vmstat and meminfo Yifan Li
@ 2026-10-10  2:59 ` Yifan Li
  2026-10-12 10:36 ` [RFC PATCH 0/1] " David Hildenbrand (Arm)
  1 sibling, 0 replies; 5+ messages in thread
From: Yifan Li @ 2026-10-10  2:59 UTC (permalink / raw)
  To: akpm, david; +Cc: linux-mm, linux-fsdevel, linux-kernel, Yifan Li

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@xiaomi.com>
---
 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!******/#

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [RFC PATCH 0/1] mm: page_frag: account page_frag pages in vmstat and meminfo
  2026-10-10  2:59 [RFC PATCH 0/1] mm: page_frag: account page_frag pages in vmstat and meminfo Yifan Li
  2026-10-10  2:59 ` [RFC PATCH 1/1] " Yifan Li
@ 2026-10-12 10:36 ` David Hildenbrand (Arm)
  2026-10-12 11:27   ` Pedro Falcato
  1 sibling, 1 reply; 5+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-12 10:36 UTC (permalink / raw)
  To: Yifan Li, akpm
  Cc: linux-mm, linux-fsdevel, linux-kernel, Matthew Wilcox,
	Vlastimil Babka, Pedro Falcato

On 10/10/26 04:59, Yifan Li wrote:
> This is an RFC.
> 
> Pages held by the page_frag cache are not accounted by any memory
> statistics counter, so on systems that derive "LostRAM" as MemTotal
> minus the sum of accounted memory they inflate LostRAM and lead to

Sorry, what exactly is subtracted from MemTotal?

How would any other kernel allocations be treated?

> misleading "leak" conclusions, even though the memory is legitimately
> used by the network path.

Assuming "leak" in such scenarios is questionable, sorry.

Aren't most page_frag allocations short-lived? Or is this more about the
page_frag cache?

> 
> The page_frag allocator (page_frag_alloc / napi_alloc_frag /
> netdev_alloc_frag) backs sk_buff->head and the frags portion of
> skb_shared_info.  Unlike slab (NR_SLAB_*) or page_pool, page_frag
> pages have no counter.  A preliminary measurement on a 4K-page
> arm64 Android device shows 14432 kB (3608 pages) of page_frag
> pages already in use at boot, and the memory footprint grows
> larger under heavy load (e.g. sustained traffic through the
> virtio_net / tun receive paths).
> 
> Patch 1/1 adds an NR_PAGE_FRAG node counter and exposes it as
> nr_page_frag in /proc/vmstat and PageFrag in /proc/meminfo.  Pages
> enter the cache through a single entry point
> __page_frag_cache_refill(), so the increment side is complete; the
> decrements sit on the paths that free the cache page.
> 
> The remaining gap is on the release side, which is open: a cache page
> handed to an skb frags array (skb_add_rx_frag() /
> skb_fill_page_desc(), without an extra get_page) is freed 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.
> 
> Closing that needs the page to carry a marker checked at the common
> free path, e.g. either
> 
>   1. a page flag: PAGEFLAG(PageFrag, page_frag, PF_HEAD), accounted
>      and cleared in __free_pages_prepare(); or
> 
>   2. a page type: PGTY_page_frag reusing _mapcount, accounted in
>      free_frozen_pages() (requires that page_frag pages are never
>      mapped into userspace; true for the current RX paths).
> 

Certainly no page flag.

A page type might be the right choice. I don't think Willy planned for that [1],
IIUC, the bump allocator was something different, but another page type
shouldn't hurt at this point.

[1] https://kernelnewbies.org/MatthewWilcox/Memdescs

-- 
Cheers,

David

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [RFC PATCH 0/1] mm: page_frag: account page_frag pages in vmstat and meminfo
  2026-10-12 10:36 ` [RFC PATCH 0/1] " David Hildenbrand (Arm)
@ 2026-10-12 11:27   ` Pedro Falcato
  2026-10-12 13:34     ` David Hildenbrand (Arm)
  0 siblings, 1 reply; 5+ messages in thread
From: Pedro Falcato @ 2026-10-12 11:27 UTC (permalink / raw)
  To: David Hildenbrand (Arm)
  Cc: Yifan Li, akpm, linux-mm, linux-fsdevel, linux-kernel,
	Matthew Wilcox, Vlastimil Babka, mhocko, netdev

On Mon, Oct 12, 2026 at 12:36:35PM +0200, David Hildenbrand (Arm) wrote:
> On 10/10/26 04:59, Yifan Li wrote:
> > This is an RFC.
> > 
> > Pages held by the page_frag cache are not accounted by any memory
> > statistics counter, so on systems that derive "LostRAM" as MemTotal
> > minus the sum of accounted memory they inflate LostRAM and lead to
> 
> Sorry, what exactly is subtracted from MemTotal?
> 
> How would any other kernel allocations be treated?

I suppose those would mostly show up as Slab:. But yes, I agree the metric
is a little dubious.

> 
> > misleading "leak" conclusions, even though the memory is legitimately
> > used by the network path.
> 
> Assuming "leak" in such scenarios is questionable, sorry.
> 
> Aren't most page_frag allocations short-lived? Or is this more about the
> page_frag cache?

page_frag allocations are kinda arbitrarily lived. If you queue (send) some
data to a socket to send over the wire, they are more likely than not
short-lived, unless the protocol/qdisc says no (for example, you're hitting
the TCP congestion window and as such can't send more data over the wire).
If you send it over loopback/veth you get arbitrarily livetimes as sockets
can keep the page frag alive if the application never reads them.

On the regular RX side you can also (AIUI) keep page frags randomly pinned
as long as you don't read them.

(note that all of these allocations are bounded by each protocol's memory
limits)


In any case, I remember an off-list discussion with mhocko regarding a
downstream bug, where I suggested adding a counter for this, and he said
networking is somewhat sensitive to extra accounting on the fast paths.
Added netdev in case they have comments.

> 
> > 
> > The page_frag allocator (page_frag_alloc / napi_alloc_frag /
> > netdev_alloc_frag) backs sk_buff->head and the frags portion of
> > skb_shared_info.  Unlike slab (NR_SLAB_*) or page_pool, page_frag
> > pages have no counter.  A preliminary measurement on a 4K-page
> > arm64 Android device shows 14432 kB (3608 pages) of page_frag
> > pages already in use at boot, and the memory footprint grows
> > larger under heavy load (e.g. sustained traffic through the
> > virtio_net / tun receive paths).
> > 
> > Patch 1/1 adds an NR_PAGE_FRAG node counter and exposes it as
> > nr_page_frag in /proc/vmstat and PageFrag in /proc/meminfo.  Pages
> > enter the cache through a single entry point
> > __page_frag_cache_refill(), so the increment side is complete; the
> > decrements sit on the paths that free the cache page.
> > 
> > The remaining gap is on the release side, which is open: a cache page
> > handed to an skb frags array (skb_add_rx_frag() /
> > skb_fill_page_desc(), without an extra get_page) is freed 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.
> > 
> > Closing that needs the page to carry a marker checked at the common
> > free path, e.g. either
> > 
> >   1. a page flag: PAGEFLAG(PageFrag, page_frag, PF_HEAD), accounted
> >      and cleared in __free_pages_prepare(); or
> > 
> >   2. a page type: PGTY_page_frag reusing _mapcount, accounted in
> >      free_frozen_pages() (requires that page_frag pages are never
> >      mapped into userspace; true for the current RX paths).
> > 
> 
> Certainly no page flag.
> 
> A page type might be the right choice. I don't think Willy planned for that [1],
> IIUC, the bump allocator was something different, but another page type
> shouldn't hurt at this point.

A page type might not work, these pages _may_ need to be mappable. I know
that was an issue for page_pool (Yet Another Networking Page Allocator) + TCP
zero-copy RX using mmap.

Of course, if you have a page frag under PAGE_SIZE or misaligned then you can't map that.

-- 
Pedro

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [RFC PATCH 0/1] mm: page_frag: account page_frag pages in vmstat and meminfo
  2026-10-12 11:27   ` Pedro Falcato
@ 2026-10-12 13:34     ` David Hildenbrand (Arm)
  0 siblings, 0 replies; 5+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-12 13:34 UTC (permalink / raw)
  To: Pedro Falcato
  Cc: Yifan Li, akpm, linux-mm, linux-fsdevel, linux-kernel,
	Matthew Wilcox, Vlastimil Babka, mhocko, netdev

On 10/12/26 13:27, Pedro Falcato wrote:
> On Mon, Oct 12, 2026 at 12:36:35PM +0200, David Hildenbrand (Arm) wrote:
>> On 10/10/26 04:59, Yifan Li wrote:
>>> This is an RFC.
>>>
>>> Pages held by the page_frag cache are not accounted by any memory
>>> statistics counter, so on systems that derive "LostRAM" as MemTotal
>>> minus the sum of accounted memory they inflate LostRAM and lead to
>>
>> Sorry, what exactly is subtracted from MemTotal?
>>
>> How would any other kernel allocations be treated?
> 
> I suppose those would mostly show up as Slab:. But yes, I agree the metric
> is a little dubious.
> 
>>
>>> misleading "leak" conclusions, even though the memory is legitimately
>>> used by the network path.
>>
>> Assuming "leak" in such scenarios is questionable, sorry.
>>
>> Aren't most page_frag allocations short-lived? Or is this more about the
>> page_frag cache?
> 
> page_frag allocations are kinda arbitrarily lived. If you queue (send) some
> data to a socket to send over the wire, they are more likely than not
> short-lived, unless the protocol/qdisc says no (for example, you're hitting
> the TCP congestion window and as such can't send more data over the wire).
> If you send it over loopback/veth you get arbitrarily livetimes as sockets
> can keep the page frag alive if the application never reads them.
> 
> On the regular RX side you can also (AIUI) keep page frags randomly pinned
> as long as you don't read them.
> 
> (note that all of these allocations are bounded by each protocol's memory
> limits)
> 
> 
> In any case, I remember an off-list discussion with mhocko regarding a
> downstream bug, where I suggested adding a counter for this, and he said
> networking is somewhat sensitive to extra accounting on the fast paths.
> Added netdev in case they have comments.

That makes sense.

>> Certainly no page flag.
>>
>> A page type might be the right choice. I don't think Willy planned for that [1],
>> IIUC, the bump allocator was something different, but another page type
>> shouldn't hurt at this point.
> 
> A page type might not work, these pages _may_ need to be mappable. I know
> that was an issue for page_pool (Yet Another Networking Page Allocator) + TCP
> zero-copy RX using mmap.

I'll have to trust on networking experts, I was reading:

	 (requires that page_frag pages are never
     mapped into userspace; true for the current RX paths).

> 
> Of course, if you have a page frag under PAGE_SIZE or misaligned then you can't map that.

It would be best if we could disallow that entirely :)

(of if required, map them through a PFNMAP or similar that does not care about
page metadata ... but the interactions with GUP are tricky)

-- 
Cheers,

David

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-10-12 13:34 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-10  2:59 [RFC PATCH 0/1] mm: page_frag: account page_frag pages in vmstat and meminfo Yifan Li
2026-10-10  2:59 ` [RFC PATCH 1/1] " Yifan Li
2026-10-12 10:36 ` [RFC PATCH 0/1] " David Hildenbrand (Arm)
2026-10-12 11:27   ` Pedro Falcato
2026-10-12 13:34     ` David Hildenbrand (Arm)

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®