* [PATCH] mm/thp: expose deferred split folio memory usage in meminfo @ 2026-07-17 6:30 Ye Liu 2026-07-17 8:08 ` Lorenzo Stoakes (ARM) 2026-07-17 8:37 ` David Hildenbrand (Arm) 0 siblings, 2 replies; 14+ messages in thread From: Ye Liu @ 2026-07-17 6:30 UTC (permalink / raw) To: Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich, Andrew Morton, David Hildenbrand, Lorenzo Stoakes Cc: Ye Liu, Liam R. Howlett, Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Kairui Song, Qi Zheng, Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts, Dev Jain, Lance Yang, Usama Arif, driver-core, linux-kernel, linux-mm, linux-fsdevel From: Ye Liu <liuye@kylinos.cn> Folios on the deferred split list hold physical memory that is invisible in meminfo. When a THP becomes partially mapped, the unmapped pages are removed from AnonPages but remain physically allocated until the shrinker splits the folio. This creates a memory accounting gap where used memory cannot be attributed to any meminfo field. Add NR_DEFERRED_SPLIT_PAGES to track the total memory consumed by folios currently on the deferred_split_lru, updated via mod_node_page_state() at all enqueue/dequeue points. The new field DeferredSplitPages is visible in /proc/meminfo, /proc/vmstat, and per-node /sys/devices/system/node/node*/meminfo. Signed-off-by: Ye Liu <liuye@kylinos.cn> --- drivers/base/node.c | 4 +++- fs/proc/meminfo.c | 2 ++ include/linux/mmzone.h | 1 + mm/huge_memory.c | 24 +++++++++++++++++++++--- mm/vmstat.c | 1 + 5 files changed, 28 insertions(+), 4 deletions(-) diff --git a/drivers/base/node.c b/drivers/base/node.c index 3da91929ad4e..69463438300e 100644 --- a/drivers/base/node.c +++ b/drivers/base/node.c @@ -519,6 +519,7 @@ static ssize_t node_read_meminfo(struct device *dev, "Node %d ShmemPmdMapped: %8lu kB\n" "Node %d FileHugePages: %8lu kB\n" "Node %d FilePmdMapped: %8lu kB\n" + "Node %d DeferredSplitPages: %8lu kB\n" #endif #ifdef CONFIG_UNACCEPTED_MEMORY "Node %d Unaccepted: %8lu kB\n" @@ -553,7 +554,8 @@ static ssize_t node_read_meminfo(struct device *dev, nid, K(node_page_state(pgdat, NR_SHMEM_THPS)), nid, K(node_page_state(pgdat, NR_SHMEM_PMDMAPPED)), nid, K(node_page_state(pgdat, NR_FILE_THPS)), - nid, K(node_page_state(pgdat, NR_FILE_PMDMAPPED)) + nid, K(node_page_state(pgdat, NR_FILE_PMDMAPPED)), + nid, K(node_page_state(pgdat, NR_DEFERRED_SPLIT_PAGES)) #endif #ifdef CONFIG_UNACCEPTED_MEMORY , diff --git a/fs/proc/meminfo.c b/fs/proc/meminfo.c index b2813ff13cb2..76dbf51ad1e0 100644 --- a/fs/proc/meminfo.c +++ b/fs/proc/meminfo.c @@ -149,6 +149,8 @@ static int meminfo_proc_show(struct seq_file *m, void *v) global_node_page_state(NR_FILE_THPS)); show_val_kb(m, "FilePmdMapped: ", global_node_page_state(NR_FILE_PMDMAPPED)); + show_val_kb(m, "DeferredSplitPages: ", + global_node_page_state(NR_DEFERRED_SPLIT_PAGES)); #endif #ifdef CONFIG_CMA diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h index 0507193b3ae3..1e75adb73d00 100644 --- a/include/linux/mmzone.h +++ b/include/linux/mmzone.h @@ -267,6 +267,7 @@ enum node_stat_item { NR_FILE_THPS, NR_FILE_PMDMAPPED, NR_ANON_THPS, + NR_DEFERRED_SPLIT_PAGES, /* THP/mTHP pages pending deferred split */ NR_VMSCAN_WRITE, NR_VMSCAN_IMMEDIATE, /* Prioritise for reclaim when writeback ends */ NR_DIRTIED, /* page dirtyings since bootup */ diff --git a/mm/huge_memory.c b/mm/huge_memory.c index c642bec967fa..1094b844675c 100644 --- a/mm/huge_memory.c +++ b/mm/huge_memory.c @@ -77,6 +77,14 @@ static unsigned long deferred_split_scan(struct shrinker *shrink, struct shrink_control *sc); static bool split_underused_thp = true; +#define deferred_split_pages_add(folio) \ + mod_node_page_state(NODE_DATA(folio_nid(folio)), \ + NR_DEFERRED_SPLIT_PAGES, folio_nr_pages(folio)) + +#define deferred_split_pages_del(folio) \ + mod_node_page_state(NODE_DATA(folio_nid(folio)), \ + NR_DEFERRED_SPLIT_PAGES, -folio_nr_pages(folio)) + static atomic_t huge_zero_refcount; struct folio *huge_zero_folio __read_mostly; unsigned long huge_zero_pfn __read_mostly = ~0UL; @@ -3913,8 +3921,10 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n struct lruvec *lruvec; if (dequeue_deferred) { - __list_lru_del(&deferred_split_lru, lru, - &folio->_deferred_list, folio_nid(folio)); + if (__list_lru_del(&deferred_split_lru, lru, + &folio->_deferred_list, folio_nid(folio)) && + folio_test_partially_mapped(folio)) + deferred_split_pages_del(folio); if (folio_test_partially_mapped(folio)) { folio_clear_partially_mapped(folio); mod_mthp_stat(old_order, @@ -4415,6 +4425,7 @@ bool __folio_unqueue_deferred_split(struct folio *folio) lru = list_lru_lock_irqsave(&deferred_split_lru, nid, &memcg, &flags); if (__list_lru_del(&deferred_split_lru, lru, &folio->_deferred_list, nid)) { if (folio_test_partially_mapped(folio)) { + deferred_split_pages_del(folio); folio_clear_partially_mapped(folio); mod_mthp_stat(folio_order(folio), MTHP_STAT_NR_ANON_PARTIALLY_MAPPED, -1); @@ -4473,6 +4484,8 @@ void deferred_split_folio(struct folio *folio, bool partially_mapped) VM_WARN_ON_FOLIO(folio_test_partially_mapped(folio), folio); } __list_lru_add(&deferred_split_lru, lru, &folio->_deferred_list, nid, memcg); + if (partially_mapped) + deferred_split_pages_add(folio); list_lru_unlock_irqrestore(lru, &flags); rcu_read_unlock(); } @@ -4524,8 +4537,11 @@ static enum lru_status deferred_split_isolate(struct list_head *item, { struct folio *folio = container_of(item, struct folio, _deferred_list); struct list_head *freeable = cb_arg; + bool partially_mapped = folio_test_partially_mapped(folio); if (folio_try_get(folio)) { + if (partially_mapped) + deferred_split_pages_del(folio); list_lru_isolate_move(lru, item, freeable); return LRU_REMOVED; } @@ -4535,7 +4551,8 @@ static enum lru_status deferred_split_isolate(struct list_head *item, * isolate: folio_unqueue_deferred_split() checks list_empty() * locklessly, so once removed the folio can be freed any time. */ - if (folio_test_partially_mapped(folio)) { + if (partially_mapped) { + deferred_split_pages_del(folio); folio_clear_partially_mapped(folio); mod_mthp_stat(folio_order(folio), MTHP_STAT_NR_ANON_PARTIALLY_MAPPED, -1); @@ -4596,6 +4613,7 @@ static unsigned long deferred_split_scan(struct shrinker *shrink, &folio->_deferred_list, folio_nid(folio), folio_memcg(folio)); + deferred_split_pages_add(folio); rcu_read_unlock(); } folio_put(folio); diff --git a/mm/vmstat.c b/mm/vmstat.c index 3b5cb1031f72..ba71cc3e4c30 100644 --- a/mm/vmstat.c +++ b/mm/vmstat.c @@ -1248,6 +1248,7 @@ const char * const vmstat_text[] = { [I(NR_FILE_THPS)] = "nr_file_hugepages", [I(NR_FILE_PMDMAPPED)] = "nr_file_pmdmapped", [I(NR_ANON_THPS)] = "nr_anon_transparent_hugepages", + [I(NR_DEFERRED_SPLIT_PAGES)] = "nr_deferred_split_pages", [I(NR_VMSCAN_WRITE)] = "nr_vmscan_write", [I(NR_VMSCAN_IMMEDIATE)] = "nr_vmscan_immediate_reclaim", [I(NR_DIRTIED)] = "nr_dirtied", -- 2.43.0 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH] mm/thp: expose deferred split folio memory usage in meminfo 2026-07-17 6:30 [PATCH] mm/thp: expose deferred split folio memory usage in meminfo Ye Liu @ 2026-07-17 8:08 ` Lorenzo Stoakes (ARM) 2026-07-17 9:24 ` Ye Liu 2026-07-17 8:37 ` David Hildenbrand (Arm) 1 sibling, 1 reply; 14+ messages in thread From: Lorenzo Stoakes (ARM) @ 2026-07-17 8:08 UTC (permalink / raw) To: Ye Liu Cc: Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich, Andrew Morton, David Hildenbrand, Ye Liu, Liam R. Howlett, Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Kairui Song, Qi Zheng, Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts, Dev Jain, Lance Yang, Usama Arif, driver-core, linux-kernel, linux-mm, linux-fsdevel, Johannes Weiner +cc Johannes On Fri, Jul 17, 2026 at 02:30:22PM +0800, Ye Liu wrote: > From: Ye Liu <liuye@kylinos.cn> > > Folios on the deferred split list hold physical memory that is > invisible in meminfo. When a THP becomes partially mapped, the > unmapped pages are removed from AnonPages but remain physically > allocated until the shrinker splits the folio. This creates a > memory accounting gap where used memory cannot be attributed to > any meminfo field. Is this really that much of an issue? You're not giving any use cases here. What real-world use case motivated this? > > Add NR_DEFERRED_SPLIT_PAGES to track the total memory consumed by > folios currently on the deferred_split_lru, updated via > mod_node_page_state() at all enqueue/dequeue points. The new field > DeferredSplitPages is visible in /proc/meminfo, /proc/vmstat, and > per-node /sys/devices/system/node/node*/meminfo. Again you're not justifying anything, why do you need a per-node breakdown? > > Signed-off-by: Ye Liu <liuye@kylinos.cn> You're updating these stats in a number of places and you've given zero explanataion or evidence that you're accounting this correctly. This is very subtle code and you're a very new contributor to this so I wouldn't want us to take someting like this even if it was justified without a very strong argument. You'd probably want to do some refactoring stuff first also to combine any such updates with moves. > --- > drivers/base/node.c | 4 +++- > fs/proc/meminfo.c | 2 ++ > include/linux/mmzone.h | 1 + > mm/huge_memory.c | 24 +++++++++++++++++++++--- > mm/vmstat.c | 1 + > 5 files changed, 28 insertions(+), 4 deletions(-) > > diff --git a/drivers/base/node.c b/drivers/base/node.c > index 3da91929ad4e..69463438300e 100644 > --- a/drivers/base/node.c > +++ b/drivers/base/node.c > @@ -519,6 +519,7 @@ static ssize_t node_read_meminfo(struct device *dev, > "Node %d ShmemPmdMapped: %8lu kB\n" > "Node %d FileHugePages: %8lu kB\n" > "Node %d FilePmdMapped: %8lu kB\n" > + "Node %d DeferredSplitPages: %8lu kB\n" > #endif > #ifdef CONFIG_UNACCEPTED_MEMORY > "Node %d Unaccepted: %8lu kB\n" > @@ -553,7 +554,8 @@ static ssize_t node_read_meminfo(struct device *dev, > nid, K(node_page_state(pgdat, NR_SHMEM_THPS)), > nid, K(node_page_state(pgdat, NR_SHMEM_PMDMAPPED)), > nid, K(node_page_state(pgdat, NR_FILE_THPS)), > - nid, K(node_page_state(pgdat, NR_FILE_PMDMAPPED)) > + nid, K(node_page_state(pgdat, NR_FILE_PMDMAPPED)), > + nid, K(node_page_state(pgdat, NR_DEFERRED_SPLIT_PAGES)) > #endif > #ifdef CONFIG_UNACCEPTED_MEMORY > , > diff --git a/fs/proc/meminfo.c b/fs/proc/meminfo.c > index b2813ff13cb2..76dbf51ad1e0 100644 > --- a/fs/proc/meminfo.c > +++ b/fs/proc/meminfo.c > @@ -149,6 +149,8 @@ static int meminfo_proc_show(struct seq_file *m, void *v) > global_node_page_state(NR_FILE_THPS)); > show_val_kb(m, "FilePmdMapped: ", > global_node_page_state(NR_FILE_PMDMAPPED)); > + show_val_kb(m, "DeferredSplitPages: ", > + global_node_page_state(NR_DEFERRED_SPLIT_PAGES)); > #endif > > #ifdef CONFIG_CMA > diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h > index 0507193b3ae3..1e75adb73d00 100644 > --- a/include/linux/mmzone.h > +++ b/include/linux/mmzone.h > @@ -267,6 +267,7 @@ enum node_stat_item { > NR_FILE_THPS, > NR_FILE_PMDMAPPED, > NR_ANON_THPS, > + NR_DEFERRED_SPLIT_PAGES, /* THP/mTHP pages pending deferred split */ > NR_VMSCAN_WRITE, > NR_VMSCAN_IMMEDIATE, /* Prioritise for reclaim when writeback ends */ > NR_DIRTIED, /* page dirtyings since bootup */ > diff --git a/mm/huge_memory.c b/mm/huge_memory.c > index c642bec967fa..1094b844675c 100644 > --- a/mm/huge_memory.c > +++ b/mm/huge_memory.c > @@ -77,6 +77,14 @@ static unsigned long deferred_split_scan(struct shrinker *shrink, > struct shrink_control *sc); > static bool split_underused_thp = true; > > +#define deferred_split_pages_add(folio) \ > + mod_node_page_state(NODE_DATA(folio_nid(folio)), \ > + NR_DEFERRED_SPLIT_PAGES, folio_nr_pages(folio)) > + > +#define deferred_split_pages_del(folio) \ > + mod_node_page_state(NODE_DATA(folio_nid(folio)), \ > + NR_DEFERRED_SPLIT_PAGES, -folio_nr_pages(folio)) Why are these macros? I really dislike the naming too, these read like they're actually adding/deleting pages. > + > static atomic_t huge_zero_refcount; > struct folio *huge_zero_folio __read_mostly; > unsigned long huge_zero_pfn __read_mostly = ~0UL; > @@ -3913,8 +3921,10 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n > struct lruvec *lruvec; > > if (dequeue_deferred) { > - __list_lru_del(&deferred_split_lru, lru, > - &folio->_deferred_list, folio_nid(folio)); > + if (__list_lru_del(&deferred_split_lru, lru, > + &folio->_deferred_list, folio_nid(folio)) && > + folio_test_partially_mapped(folio)) > + deferred_split_pages_del(folio); > if (folio_test_partially_mapped(folio)) { > folio_clear_partially_mapped(folio); > mod_mthp_stat(old_order, > @@ -4415,6 +4425,7 @@ bool __folio_unqueue_deferred_split(struct folio *folio) > lru = list_lru_lock_irqsave(&deferred_split_lru, nid, &memcg, &flags); > if (__list_lru_del(&deferred_split_lru, lru, &folio->_deferred_list, nid)) { > if (folio_test_partially_mapped(folio)) { > + deferred_split_pages_del(folio); > folio_clear_partially_mapped(folio); > mod_mthp_stat(folio_order(folio), > MTHP_STAT_NR_ANON_PARTIALLY_MAPPED, -1); > @@ -4473,6 +4484,8 @@ void deferred_split_folio(struct folio *folio, bool partially_mapped) > VM_WARN_ON_FOLIO(folio_test_partially_mapped(folio), folio); > } > __list_lru_add(&deferred_split_lru, lru, &folio->_deferred_list, nid, memcg); > + if (partially_mapped) > + deferred_split_pages_add(folio); > list_lru_unlock_irqrestore(lru, &flags); > rcu_read_unlock(); > } > @@ -4524,8 +4537,11 @@ static enum lru_status deferred_split_isolate(struct list_head *item, > { > struct folio *folio = container_of(item, struct folio, _deferred_list); > struct list_head *freeable = cb_arg; > + bool partially_mapped = folio_test_partially_mapped(folio); > > if (folio_try_get(folio)) { > + if (partially_mapped) > + deferred_split_pages_del(folio); > list_lru_isolate_move(lru, item, freeable); > return LRU_REMOVED; > } > @@ -4535,7 +4551,8 @@ static enum lru_status deferred_split_isolate(struct list_head *item, > * isolate: folio_unqueue_deferred_split() checks list_empty() > * locklessly, so once removed the folio can be freed any time. > */ > - if (folio_test_partially_mapped(folio)) { > + if (partially_mapped) { > + deferred_split_pages_del(folio); > folio_clear_partially_mapped(folio); > mod_mthp_stat(folio_order(folio), > MTHP_STAT_NR_ANON_PARTIALLY_MAPPED, -1); > @@ -4596,6 +4613,7 @@ static unsigned long deferred_split_scan(struct shrinker *shrink, > &folio->_deferred_list, > folio_nid(folio), > folio_memcg(folio)); > + deferred_split_pages_add(folio); > rcu_read_unlock(); > } > folio_put(folio); > diff --git a/mm/vmstat.c b/mm/vmstat.c > index 3b5cb1031f72..ba71cc3e4c30 100644 > --- a/mm/vmstat.c > +++ b/mm/vmstat.c > @@ -1248,6 +1248,7 @@ const char * const vmstat_text[] = { > [I(NR_FILE_THPS)] = "nr_file_hugepages", > [I(NR_FILE_PMDMAPPED)] = "nr_file_pmdmapped", > [I(NR_ANON_THPS)] = "nr_anon_transparent_hugepages", > + [I(NR_DEFERRED_SPLIT_PAGES)] = "nr_deferred_split_pages", > [I(NR_VMSCAN_WRITE)] = "nr_vmscan_write", > [I(NR_VMSCAN_IMMEDIATE)] = "nr_vmscan_immediate_reclaim", > [I(NR_DIRTIED)] = "nr_dirtied", > -- > 2.43.0 > Cheers, Lorenzo ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH] mm/thp: expose deferred split folio memory usage in meminfo 2026-07-17 8:08 ` Lorenzo Stoakes (ARM) @ 2026-07-17 9:24 ` Ye Liu 2026-07-17 9:38 ` liuye 0 siblings, 1 reply; 14+ messages in thread From: Ye Liu @ 2026-07-17 9:24 UTC (permalink / raw) To: Lorenzo Stoakes (ARM) Cc: Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich, Andrew Morton, David Hildenbrand, Ye Liu, Liam R. Howlett, Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Kairui Song, Qi Zheng, Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts, Dev Jain, Lance Yang, Usama Arif, driver-core, linux-kernel, linux-mm, linux-fsdevel, Johannes Weiner 在 2026/7/17 16:08, Lorenzo Stoakes (ARM) 写道: > +cc Johannes > > On Fri, Jul 17, 2026 at 02:30:22PM +0800, Ye Liu wrote: >> From: Ye Liu <liuye@kylinos.cn> >> >> Folios on the deferred split list hold physical memory that is >> invisible in meminfo. When a THP becomes partially mapped, the >> unmapped pages are removed from AnonPages but remain physically >> allocated until the shrinker splits the folio. This creates a >> memory accounting gap where used memory cannot be attributed to >> any meminfo field. > > Is this really that much of an issue? You're not giving any use cases here. > > What real-world use case motivated this? > I have indeed encountered this situation in a customer environment, but the environment is complex, and I cannot clearly explain why this memory black hole occurs in that scenario. However, I will provide an example to reproduce it below. Use case: The system has 100GB of memory. A user-space program requests 80GB of memory. For each 2MB block, only one page is retained, and the mapping of all other pages is unmapped, while the process continues to run. You'll find that in meminfo, MemFree: only 12GB remains, but the process actually uses very little memory. This creates a memory black hole situation. Below is the test program I used, from AI. >> >> Add NR_DEFERRED_SPLIT_PAGES to track the total memory consumed by >> folios currently on the deferred_split_lru, updated via >> mod_node_page_state() at all enqueue/dequeue points. The new field >> DeferredSplitPages is visible in /proc/meminfo, /proc/vmstat, and >> per-node /sys/devices/system/node/node*/meminfo. > > Again you're not justifying anything, why do you need a per-node breakdown? > Initially, the only plan was to add it to /proc/meminfo; the others were more of a transitional design. >> >> Signed-off-by: Ye Liu <liuye@kylinos.cn> > > You're updating these stats in a number of places and you've given zero > explanataion or evidence that you're accounting this correctly. > The counter tracks the current total pages of partially_mapped folios on deferred_split_lru. The six hook points: - Add: deferred_split_folio() when partially_mapped → the only first-enqueue path - Add: deferred_split_scan() requeue → net effect is zero paired with isolate's del - Del: deferred_split_isolate() → temporary removal for shrinker processing - Del: __folio_unqueue_deferred_split() → permanent removal when folio is freed - Del: split path → permanent removal when folio is split into sub-folios > This is very subtle code and you're a very new contributor to this so I > wouldn't want us to take someting like this even if it was justified > without a very strong argument. True – I’m new to the kernel code, so I’ll definitely benefit from checking in with you guys more regularly. > > You'd probably want to do some refactoring stuff first also to combine any > such updates with moves. > Indeed, it would be better to integrate it with folio updates. >> --- >> drivers/base/node.c | 4 +++- >> fs/proc/meminfo.c | 2 ++ >> include/linux/mmzone.h | 1 + >> mm/huge_memory.c | 24 +++++++++++++++++++++--- >> mm/vmstat.c | 1 + >> 5 files changed, 28 insertions(+), 4 deletions(-) >> >> diff --git a/drivers/base/node.c b/drivers/base/node.c >> index 3da91929ad4e..69463438300e 100644 >> --- a/drivers/base/node.c >> +++ b/drivers/base/node.c >> @@ -519,6 +519,7 @@ static ssize_t node_read_meminfo(struct device *dev, >> "Node %d ShmemPmdMapped: %8lu kB\n" >> "Node %d FileHugePages: %8lu kB\n" >> "Node %d FilePmdMapped: %8lu kB\n" >> + "Node %d DeferredSplitPages: %8lu kB\n" >> #endif >> #ifdef CONFIG_UNACCEPTED_MEMORY >> "Node %d Unaccepted: %8lu kB\n" >> @@ -553,7 +554,8 @@ static ssize_t node_read_meminfo(struct device *dev, >> nid, K(node_page_state(pgdat, NR_SHMEM_THPS)), >> nid, K(node_page_state(pgdat, NR_SHMEM_PMDMAPPED)), >> nid, K(node_page_state(pgdat, NR_FILE_THPS)), >> - nid, K(node_page_state(pgdat, NR_FILE_PMDMAPPED)) >> + nid, K(node_page_state(pgdat, NR_FILE_PMDMAPPED)), >> + nid, K(node_page_state(pgdat, NR_DEFERRED_SPLIT_PAGES)) >> #endif >> #ifdef CONFIG_UNACCEPTED_MEMORY >> , >> diff --git a/fs/proc/meminfo.c b/fs/proc/meminfo.c >> index b2813ff13cb2..76dbf51ad1e0 100644 >> --- a/fs/proc/meminfo.c >> +++ b/fs/proc/meminfo.c >> @@ -149,6 +149,8 @@ static int meminfo_proc_show(struct seq_file *m, void *v) >> global_node_page_state(NR_FILE_THPS)); >> show_val_kb(m, "FilePmdMapped: ", >> global_node_page_state(NR_FILE_PMDMAPPED)); >> + show_val_kb(m, "DeferredSplitPages: ", >> + global_node_page_state(NR_DEFERRED_SPLIT_PAGES)); >> #endif >> >> #ifdef CONFIG_CMA >> diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h >> index 0507193b3ae3..1e75adb73d00 100644 >> --- a/include/linux/mmzone.h >> +++ b/include/linux/mmzone.h >> @@ -267,6 +267,7 @@ enum node_stat_item { >> NR_FILE_THPS, >> NR_FILE_PMDMAPPED, >> NR_ANON_THPS, >> + NR_DEFERRED_SPLIT_PAGES, /* THP/mTHP pages pending deferred split */ >> NR_VMSCAN_WRITE, >> NR_VMSCAN_IMMEDIATE, /* Prioritise for reclaim when writeback ends */ >> NR_DIRTIED, /* page dirtyings since bootup */ >> diff --git a/mm/huge_memory.c b/mm/huge_memory.c >> index c642bec967fa..1094b844675c 100644 >> --- a/mm/huge_memory.c >> +++ b/mm/huge_memory.c >> @@ -77,6 +77,14 @@ static unsigned long deferred_split_scan(struct shrinker *shrink, >> struct shrink_control *sc); >> static bool split_underused_thp = true; >> >> +#define deferred_split_pages_add(folio) \ >> + mod_node_page_state(NODE_DATA(folio_nid(folio)), \ >> + NR_DEFERRED_SPLIT_PAGES, folio_nr_pages(folio)) >> + >> +#define deferred_split_pages_del(folio) \ >> + mod_node_page_state(NODE_DATA(folio_nid(folio)), \ >> + NR_DEFERRED_SPLIT_PAGES, -folio_nr_pages(folio)) > > Why are these macros? > > I really dislike the naming too, these read like they're actually > adding/deleting pages. > Rename the helpers to avoid the "add/del pages" confusion (e.g. mod_deferred_split_stat() or just inline mod_node_page_state). >> + >> static atomic_t huge_zero_refcount; >> struct folio *huge_zero_folio __read_mostly; >> unsigned long huge_zero_pfn __read_mostly = ~0UL; >> @@ -3913,8 +3921,10 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n >> struct lruvec *lruvec; >> >> if (dequeue_deferred) { >> - __list_lru_del(&deferred_split_lru, lru, >> - &folio->_deferred_list, folio_nid(folio)); >> + if (__list_lru_del(&deferred_split_lru, lru, >> + &folio->_deferred_list, folio_nid(folio)) && >> + folio_test_partially_mapped(folio)) >> + deferred_split_pages_del(folio); >> if (folio_test_partially_mapped(folio)) { >> folio_clear_partially_mapped(folio); >> mod_mthp_stat(old_order, >> @@ -4415,6 +4425,7 @@ bool __folio_unqueue_deferred_split(struct folio *folio) >> lru = list_lru_lock_irqsave(&deferred_split_lru, nid, &memcg, &flags); >> if (__list_lru_del(&deferred_split_lru, lru, &folio->_deferred_list, nid)) { >> if (folio_test_partially_mapped(folio)) { >> + deferred_split_pages_del(folio); >> folio_clear_partially_mapped(folio); >> mod_mthp_stat(folio_order(folio), >> MTHP_STAT_NR_ANON_PARTIALLY_MAPPED, -1); >> @@ -4473,6 +4484,8 @@ void deferred_split_folio(struct folio *folio, bool partially_mapped) >> VM_WARN_ON_FOLIO(folio_test_partially_mapped(folio), folio); >> } >> __list_lru_add(&deferred_split_lru, lru, &folio->_deferred_list, nid, memcg); >> + if (partially_mapped) >> + deferred_split_pages_add(folio); >> list_lru_unlock_irqrestore(lru, &flags); >> rcu_read_unlock(); >> } >> @@ -4524,8 +4537,11 @@ static enum lru_status deferred_split_isolate(struct list_head *item, >> { >> struct folio *folio = container_of(item, struct folio, _deferred_list); >> struct list_head *freeable = cb_arg; >> + bool partially_mapped = folio_test_partially_mapped(folio); >> >> if (folio_try_get(folio)) { >> + if (partially_mapped) >> + deferred_split_pages_del(folio); >> list_lru_isolate_move(lru, item, freeable); >> return LRU_REMOVED; >> } >> @@ -4535,7 +4551,8 @@ static enum lru_status deferred_split_isolate(struct list_head *item, >> * isolate: folio_unqueue_deferred_split() checks list_empty() >> * locklessly, so once removed the folio can be freed any time. >> */ >> - if (folio_test_partially_mapped(folio)) { >> + if (partially_mapped) { >> + deferred_split_pages_del(folio); >> folio_clear_partially_mapped(folio); >> mod_mthp_stat(folio_order(folio), >> MTHP_STAT_NR_ANON_PARTIALLY_MAPPED, -1); >> @@ -4596,6 +4613,7 @@ static unsigned long deferred_split_scan(struct shrinker *shrink, >> &folio->_deferred_list, >> folio_nid(folio), >> folio_memcg(folio)); >> + deferred_split_pages_add(folio); >> rcu_read_unlock(); >> } >> folio_put(folio); >> diff --git a/mm/vmstat.c b/mm/vmstat.c >> index 3b5cb1031f72..ba71cc3e4c30 100644 >> --- a/mm/vmstat.c >> +++ b/mm/vmstat.c >> @@ -1248,6 +1248,7 @@ const char * const vmstat_text[] = { >> [I(NR_FILE_THPS)] = "nr_file_hugepages", >> [I(NR_FILE_PMDMAPPED)] = "nr_file_pmdmapped", >> [I(NR_ANON_THPS)] = "nr_anon_transparent_hugepages", >> + [I(NR_DEFERRED_SPLIT_PAGES)] = "nr_deferred_split_pages", >> [I(NR_VMSCAN_WRITE)] = "nr_vmscan_write", >> [I(NR_VMSCAN_IMMEDIATE)] = "nr_vmscan_immediate_reclaim", >> [I(NR_DIRTIED)] = "nr_dirtied", >> -- >> 2.43.0 >> > > Cheers, Lorenzo -- Thanks, Ye Liu ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH] mm/thp: expose deferred split folio memory usage in meminfo 2026-07-17 9:24 ` Ye Liu @ 2026-07-17 9:38 ` liuye 2026-07-17 9:47 ` Lorenzo Stoakes (ARM) 2026-07-17 10:29 ` Johannes Weiner 0 siblings, 2 replies; 14+ messages in thread From: liuye @ 2026-07-17 9:38 UTC (permalink / raw) To: Ye Liu, Lorenzo Stoakes (ARM) Cc: Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich, Andrew Morton, David Hildenbrand, Liam R. Howlett, Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Kairui Song, Qi Zheng, Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts, Dev Jain, Lance Yang, Usama Arif, driver-core, linux-kernel, linux-mm, linux-fsdevel, Johannes Weiner 在 2026/7/17 17:24, Ye Liu 写道: > > 在 2026/7/17 16:08, Lorenzo Stoakes (ARM) 写道: >> +cc Johannes >> >> On Fri, Jul 17, 2026 at 02:30:22PM +0800, Ye Liu wrote: >>> From: Ye Liu <liuye@kylinos.cn> >>> >>> Folios on the deferred split list hold physical memory that is >>> invisible in meminfo. When a THP becomes partially mapped, the >>> unmapped pages are removed from AnonPages but remain physically >>> allocated until the shrinker splits the folio. This creates a >>> memory accounting gap where used memory cannot be attributed to >>> any meminfo field. >> Is this really that much of an issue? You're not giving any use cases here. >> >> What real-world use case motivated this? >> > I have indeed encountered this situation in a customer environment, > but the environment is complex, and I cannot clearly explain why this memory > black hole occurs in that scenario. > However, I will provide an example to reproduce it below. > Use case: The system has 100GB of memory. A user-space program requests 80GB of memory. > For each 2MB block, only one page is retained, and the mapping of all other pages is unmapped, > while the process continues to run. > You'll find that in meminfo, MemFree: only 12GB remains, > but the process actually uses very little memory. > This creates a memory black hole situation. > Below is the test program I used, from AI. > ps -p $(pgrep -d, test) -o pid,comm,%mem,rss,vsz PID COMMAND %MEM RSS VSZ 9358 test 0.1 165220 166032 MemTotal: 101606276 kB MemFree: 13172092 kB MemAvailable: 13569668 kB Buffers: 5352 kB Cached: 1169520 kB SwapCached: 0 kB Active: 84730404 kB Inactive: 1129792 kB Active(anon): 84730404 kB Inactive(anon): 0 kB Active(file): 0 kB Inactive(file): 1129792 kB Unevictable: 6192 kB Mlocked: 32 kB SwapTotal: 4194300 kB SwapFree: 4194300 kB Zswap: 0 kB Zswapped: 0 kB Dirty: 24 kB Writeback: 0 kB AnonPages: 969280 kB Mapped: 308848 kB Shmem: 45076 kB KReclaimable: 96144 kB Slab: 269540 kB SReclaimable: 96144 kB SUnreclaim: 173396 kB KernelStack: 27584 kB PageTables: 187408 kB SecPageTables: 0 kB NFS_Unstable: 0 kB Bounce: 0 kB WritebackTmp: 0 kB CommitLimit: 54997436 kB Committed_AS: 6628776 kB VmallocTotal: 261087232 kB VmallocUsed: 38696 kB VmallocChunk: 0 kB Percpu: 42800 kB HardwareCorrupted: 0 kB AnonHugePages: 413696 kB ShmemHugePages: 0 kB ShmemPmdMapped: 0 kB FileHugePages: 172032 kB FilePmdMapped: 28672 kB DeferredSplitPages: 83886080 kB CmaTotal: 131072 kB CmaFree: 122880 kB Balloon: 0 kB GPUActive: 0 kB GPUReclaim: 0 kB HugePages_Total: 0 HugePages_Free: 0 HugePages_Rsvd: 0 HugePages_Surp: 0 Hugepagesize: 2048 kB Hugetlb: 0 kB /* * thp_keep_one_page_per_2mb.c * * Compile: gcc -O2 -o test thp_keep_one_page_per_2mb.c * Run: ./thp_test * * Description: * Allocate 80GB virtual address space, touch pages and record accessibility. * For each 2MB block (if its first page is accessible), keep only the first * 4KB page and unmap all other pages within that block. * The process stays alive for inspection of memory state. */ #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/mman.h> #include <errno.h> #include <signal.h> #include <stdint.h> #include <setjmp.h> #include <stdbool.h> #include <time.h> #define PMD_SIZE (2 * 1024 * 1024) /* 2 MB */ #define PAGE_SIZE 4096 /* 4 KB */ #define TOTAL_SIZE (80ULL * 1024 * 1024 * 1024) /* 80 GB */ #define NR_PAGES (TOTAL_SIZE / PAGE_SIZE) /* ~20.97 million pages */ #define NR_HUGEPAGES (TOTAL_SIZE / PMD_SIZE) /* 40960 THP blocks */ static unsigned char *base_addr; static unsigned char *page_ok; /* 1 = accessible, 0 = inaccessible / already unmapped */ static sigjmp_buf jmpbuf; /* Signal handler to catch page fault errors */ static void segv_handler(int sig, siginfo_t *info, void *context) { (void)sig; (void)info; (void)context; siglongjmp(jmpbuf, 1); } /* Read total AnonHugePages size (in bytes) from /proc/self/smaps */ static unsigned long read_anon_hugepages(void) { FILE *fp = fopen("/proc/self/smaps", "r"); if (!fp) return 0; char line[256]; unsigned long total = 0; while (fgets(line, sizeof(line), fp)) { if (strncmp(line, "AnonHugePages:", 14) == 0) { unsigned long val; if (sscanf(line + 14, "%lu kB", &val) == 1) total += val * 1024; } } fclose(fp); return total; } int main(void) { /* Install signal handlers */ struct sigaction sa = { .sa_sigaction = segv_handler, .sa_flags = SA_SIGINFO, }; sigaction(SIGSEGV, &sa, NULL); sigaction(SIGBUS, &sa, NULL); printf("=== Keep only one 4KB page per 2MB THP, unmap the rest ===\n"); printf("Allocation size: %lu MB (%lu 2MB THP blocks)\n", TOTAL_SIZE / (1024 * 1024), NR_HUGEPAGES); /* 1. Reserve virtual address space (no physical allocation yet) */ base_addr = mmap(NULL, TOTAL_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (base_addr == MAP_FAILED) { perror("mmap failed"); exit(EXIT_FAILURE); } printf("Virtual address base: %p\n", base_addr); /* 2. Hint kernel to use THP (if system policy is madvise) */ if (madvise(base_addr, TOTAL_SIZE, MADV_HUGEPAGE) == -1) perror("madvise (non-fatal)"); /* 3. Allocate page status array */ page_ok = calloc(NR_PAGES, sizeof(unsigned char)); if (!page_ok) { perror("calloc"); exit(EXIT_FAILURE); } /* 4. Touch pages one by one, capturing faults */ printf("Touching pages (write each page, skip on failure)...\n"); const unsigned long print_interval = (256ULL * 1024 * 1024) / PAGE_SIZE; for (unsigned long i = 0; i < NR_PAGES; i++) { volatile unsigned char *p = base_addr + i * PAGE_SIZE; if (sigsetjmp(jmpbuf, 1) == 0) { *p = 0xAA; /* attempt write */ page_ok[i] = 1; /* success */ } else { page_ok[i] = 0; /* fault – page not accessible */ } if ((i + 1) % print_interval == 0) { printf(" Processed %lu MB\n", (i + 1) * PAGE_SIZE / (1024 * 1024)); } } /* Count successfully mapped pages */ unsigned long ok_pages = 0; for (unsigned long i = 0; i < NR_PAGES; i++) if (page_ok[i]) ok_pages++; printf("Touch complete. Successfully mapped %lu pages (%.2f MB)\n", ok_pages, ok_pages * PAGE_SIZE / (1024.0 * 1024)); /* 5. THP usage before the operation */ unsigned long huge_before = read_anon_hugepages(); printf("AnonHugePages before operation: %.2f MB\n", huge_before / (1024.0 * 1024)); /* 6. For each 2MB block: keep the first page, unmap all other pages */ printf("\nProcessing each THP: keep first page, unmap the rest...\n"); int success = 0, fail = 0, skipped = 0; for (unsigned long i = 0; i < NR_HUGEPAGES; i++) { unsigned long start_page = i * (PMD_SIZE / PAGE_SIZE); /* 512 pages per block */ if (page_ok[start_page]) { /* First page is accessible – keep it, unmap the rest of the block */ void *addr = base_addr + i * PMD_SIZE + PAGE_SIZE; size_t len = PMD_SIZE - PAGE_SIZE; /* 2MB - 4KB */ /* Try batch munmap first (requires all pages in range to be mapped) */ if (munmap(addr, len) == 0) { /* Batch succeeded; mark all remaining pages in this block as unmapped */ for (unsigned long j = start_page + 1; j < start_page + 512; j++) { page_ok[j] = 0; } success++; } else { /* Batch failed (some pages were never mapped) – fallback to per‑page unmap */ int sub_success = 0; for (unsigned long j = start_page + 1; j < start_page + 512; j++) { if (page_ok[j]) { if (munmap(base_addr + j * PAGE_SIZE, PAGE_SIZE) == 0) { page_ok[j] = 0; sub_success++; } else { /* ignore errors, keep page_ok[j] as 1 */ } } } if (sub_success > 0) { success++; } else { fail++; } } } else { skipped++; } if ((i + 1) % 1000 == 0) { printf(" Processed %lu/%lu THP (ok:%d, fail:%d, skip:%d)\n", i + 1, NR_HUGEPAGES, success, fail, skipped); } } printf("Operation complete: success %d, fail %d, skipped %d\n", success, fail, skipped); /* 7. THP usage after the operation */ unsigned long huge_after = read_anon_hugepages(); printf("AnonHugePages after operation: %.2f MB\n", huge_after / (1024.0 * 1024)); /* 8. Verification: access only the kept first page of each block */ printf("\nVerifying kept first pages (offset 0 of each block)...\n"); int verify_ok = 0, verify_fail = 0; for (unsigned long i = 0; i < NR_HUGEPAGES; i++) { unsigned long first_page = i * (PMD_SIZE / PAGE_SIZE); if (page_ok[first_page]) { volatile unsigned char *p = base_addr + i * PMD_SIZE; (void)*p; /* read the first byte */ verify_ok++; } else { verify_fail++; } if ((i + 1) % 1000 == 0) { printf(" Verified %lu/%lu THP\n", i + 1, NR_HUGEPAGES); } } printf("Verification complete: %d first pages accessible, %d inaccessible (skipped)\n", verify_ok, verify_fail); /* 9. Keep the process alive – all kept pages remain mapped */ printf("\nEach THP's first page is kept; all other pages have been unmapped.\n"); printf("Process PID = %d, staying alive (press Ctrl+C to exit).\n", getpid()); printf("Use these commands to inspect memory state:\n"); printf(" cat /proc/%d/smaps | grep -A20 '^7f' # view mapping details\n", getpid()); printf(" cat /proc/%d/smaps | grep AnonHugePages\n", getpid()); free(page_ok); /* release status array */ while (1) { sleep(60); /* Optionally print current THP usage (should be near 0) */ unsigned long cur = read_anon_hugepages(); printf("[%ld] Current AnonHugePages: %.2f MB\n", time(NULL), cur / (1024.0 * 1024)); } /* never reached */ munmap(base_addr, TOTAL_SIZE); return 0; } >>> Add NR_DEFERRED_SPLIT_PAGES to track the total memory consumed by >>> folios currently on the deferred_split_lru, updated via >>> mod_node_page_state() at all enqueue/dequeue points. The new field >>> DeferredSplitPages is visible in /proc/meminfo, /proc/vmstat, and >>> per-node /sys/devices/system/node/node*/meminfo. >> Again you're not justifying anything, why do you need a per-node breakdown? >> > Initially, the only plan was to add it to /proc/meminfo; > the others were more of a transitional design. > >>> Signed-off-by: Ye Liu <liuye@kylinos.cn> >> You're updating these stats in a number of places and you've given zero >> explanataion or evidence that you're accounting this correctly. >> > The counter tracks the current total pages of partially_mapped folios on deferred_split_lru. > The six hook points: > - Add: deferred_split_folio() when partially_mapped → the only first-enqueue path > - Add: deferred_split_scan() requeue → net effect is zero paired with isolate's del > - Del: deferred_split_isolate() → temporary removal for shrinker processing > - Del: __folio_unqueue_deferred_split() → permanent removal when folio is freed > - Del: split path → permanent removal when folio is split into sub-folios > >> This is very subtle code and you're a very new contributor to this so I >> wouldn't want us to take someting like this even if it was justified >> without a very strong argument. > True – I’m new to the kernel code, so I’ll definitely benefit from checking in > with you guys more regularly. > >> You'd probably want to do some refactoring stuff first also to combine any >> such updates with moves. >> > Indeed, it would be better to integrate it with folio updates. >>> --- >>> drivers/base/node.c | 4 +++- >>> fs/proc/meminfo.c | 2 ++ >>> include/linux/mmzone.h | 1 + >>> mm/huge_memory.c | 24 +++++++++++++++++++++--- >>> mm/vmstat.c | 1 + >>> 5 files changed, 28 insertions(+), 4 deletions(-) >>> >>> diff --git a/drivers/base/node.c b/drivers/base/node.c >>> index 3da91929ad4e..69463438300e 100644 >>> --- a/drivers/base/node.c >>> +++ b/drivers/base/node.c >>> @@ -519,6 +519,7 @@ static ssize_t node_read_meminfo(struct device *dev, >>> "Node %d ShmemPmdMapped: %8lu kB\n" >>> "Node %d FileHugePages: %8lu kB\n" >>> "Node %d FilePmdMapped: %8lu kB\n" >>> + "Node %d DeferredSplitPages: %8lu kB\n" >>> #endif >>> #ifdef CONFIG_UNACCEPTED_MEMORY >>> "Node %d Unaccepted: %8lu kB\n" >>> @@ -553,7 +554,8 @@ static ssize_t node_read_meminfo(struct device *dev, >>> nid, K(node_page_state(pgdat, NR_SHMEM_THPS)), >>> nid, K(node_page_state(pgdat, NR_SHMEM_PMDMAPPED)), >>> nid, K(node_page_state(pgdat, NR_FILE_THPS)), >>> - nid, K(node_page_state(pgdat, NR_FILE_PMDMAPPED)) >>> + nid, K(node_page_state(pgdat, NR_FILE_PMDMAPPED)), >>> + nid, K(node_page_state(pgdat, NR_DEFERRED_SPLIT_PAGES)) >>> #endif >>> #ifdef CONFIG_UNACCEPTED_MEMORY >>> , >>> diff --git a/fs/proc/meminfo.c b/fs/proc/meminfo.c >>> index b2813ff13cb2..76dbf51ad1e0 100644 >>> --- a/fs/proc/meminfo.c >>> +++ b/fs/proc/meminfo.c >>> @@ -149,6 +149,8 @@ static int meminfo_proc_show(struct seq_file *m, void *v) >>> global_node_page_state(NR_FILE_THPS)); >>> show_val_kb(m, "FilePmdMapped: ", >>> global_node_page_state(NR_FILE_PMDMAPPED)); >>> + show_val_kb(m, "DeferredSplitPages: ", >>> + global_node_page_state(NR_DEFERRED_SPLIT_PAGES)); >>> #endif >>> >>> #ifdef CONFIG_CMA >>> diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h >>> index 0507193b3ae3..1e75adb73d00 100644 >>> --- a/include/linux/mmzone.h >>> +++ b/include/linux/mmzone.h >>> @@ -267,6 +267,7 @@ enum node_stat_item { >>> NR_FILE_THPS, >>> NR_FILE_PMDMAPPED, >>> NR_ANON_THPS, >>> + NR_DEFERRED_SPLIT_PAGES, /* THP/mTHP pages pending deferred split */ >>> NR_VMSCAN_WRITE, >>> NR_VMSCAN_IMMEDIATE, /* Prioritise for reclaim when writeback ends */ >>> NR_DIRTIED, /* page dirtyings since bootup */ >>> diff --git a/mm/huge_memory.c b/mm/huge_memory.c >>> index c642bec967fa..1094b844675c 100644 >>> --- a/mm/huge_memory.c >>> +++ b/mm/huge_memory.c >>> @@ -77,6 +77,14 @@ static unsigned long deferred_split_scan(struct shrinker *shrink, >>> struct shrink_control *sc); >>> static bool split_underused_thp = true; >>> >>> +#define deferred_split_pages_add(folio) \ >>> + mod_node_page_state(NODE_DATA(folio_nid(folio)), \ >>> + NR_DEFERRED_SPLIT_PAGES, folio_nr_pages(folio)) >>> + >>> +#define deferred_split_pages_del(folio) \ >>> + mod_node_page_state(NODE_DATA(folio_nid(folio)), \ >>> + NR_DEFERRED_SPLIT_PAGES, -folio_nr_pages(folio)) >> Why are these macros? >> >> I really dislike the naming too, these read like they're actually >> adding/deleting pages. >> > Rename the helpers to avoid the "add/del pages" confusion > (e.g. mod_deferred_split_stat() or just inline mod_node_page_state). > >>> + >>> static atomic_t huge_zero_refcount; >>> struct folio *huge_zero_folio __read_mostly; >>> unsigned long huge_zero_pfn __read_mostly = ~0UL; >>> @@ -3913,8 +3921,10 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n >>> struct lruvec *lruvec; >>> >>> if (dequeue_deferred) { >>> - __list_lru_del(&deferred_split_lru, lru, >>> - &folio->_deferred_list, folio_nid(folio)); >>> + if (__list_lru_del(&deferred_split_lru, lru, >>> + &folio->_deferred_list, folio_nid(folio)) && >>> + folio_test_partially_mapped(folio)) >>> + deferred_split_pages_del(folio); >>> if (folio_test_partially_mapped(folio)) { >>> folio_clear_partially_mapped(folio); >>> mod_mthp_stat(old_order, >>> @@ -4415,6 +4425,7 @@ bool __folio_unqueue_deferred_split(struct folio *folio) >>> lru = list_lru_lock_irqsave(&deferred_split_lru, nid, &memcg, &flags); >>> if (__list_lru_del(&deferred_split_lru, lru, &folio->_deferred_list, nid)) { >>> if (folio_test_partially_mapped(folio)) { >>> + deferred_split_pages_del(folio); >>> folio_clear_partially_mapped(folio); >>> mod_mthp_stat(folio_order(folio), >>> MTHP_STAT_NR_ANON_PARTIALLY_MAPPED, -1); >>> @@ -4473,6 +4484,8 @@ void deferred_split_folio(struct folio *folio, bool partially_mapped) >>> VM_WARN_ON_FOLIO(folio_test_partially_mapped(folio), folio); >>> } >>> __list_lru_add(&deferred_split_lru, lru, &folio->_deferred_list, nid, memcg); >>> + if (partially_mapped) >>> + deferred_split_pages_add(folio); >>> list_lru_unlock_irqrestore(lru, &flags); >>> rcu_read_unlock(); >>> } >>> @@ -4524,8 +4537,11 @@ static enum lru_status deferred_split_isolate(struct list_head *item, >>> { >>> struct folio *folio = container_of(item, struct folio, _deferred_list); >>> struct list_head *freeable = cb_arg; >>> + bool partially_mapped = folio_test_partially_mapped(folio); >>> >>> if (folio_try_get(folio)) { >>> + if (partially_mapped) >>> + deferred_split_pages_del(folio); >>> list_lru_isolate_move(lru, item, freeable); >>> return LRU_REMOVED; >>> } >>> @@ -4535,7 +4551,8 @@ static enum lru_status deferred_split_isolate(struct list_head *item, >>> * isolate: folio_unqueue_deferred_split() checks list_empty() >>> * locklessly, so once removed the folio can be freed any time. >>> */ >>> - if (folio_test_partially_mapped(folio)) { >>> + if (partially_mapped) { >>> + deferred_split_pages_del(folio); >>> folio_clear_partially_mapped(folio); >>> mod_mthp_stat(folio_order(folio), >>> MTHP_STAT_NR_ANON_PARTIALLY_MAPPED, -1); >>> @@ -4596,6 +4613,7 @@ static unsigned long deferred_split_scan(struct shrinker *shrink, >>> &folio->_deferred_list, >>> folio_nid(folio), >>> folio_memcg(folio)); >>> + deferred_split_pages_add(folio); >>> rcu_read_unlock(); >>> } >>> folio_put(folio); >>> diff --git a/mm/vmstat.c b/mm/vmstat.c >>> index 3b5cb1031f72..ba71cc3e4c30 100644 >>> --- a/mm/vmstat.c >>> +++ b/mm/vmstat.c >>> @@ -1248,6 +1248,7 @@ const char * const vmstat_text[] = { >>> [I(NR_FILE_THPS)] = "nr_file_hugepages", >>> [I(NR_FILE_PMDMAPPED)] = "nr_file_pmdmapped", >>> [I(NR_ANON_THPS)] = "nr_anon_transparent_hugepages", >>> + [I(NR_DEFERRED_SPLIT_PAGES)] = "nr_deferred_split_pages", >>> [I(NR_VMSCAN_WRITE)] = "nr_vmscan_write", >>> [I(NR_VMSCAN_IMMEDIATE)] = "nr_vmscan_immediate_reclaim", >>> [I(NR_DIRTIED)] = "nr_dirtied", >>> -- >>> 2.43.0 >>> >> Cheers, Lorenzo -- Thanks, Ye Liu ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH] mm/thp: expose deferred split folio memory usage in meminfo 2026-07-17 9:38 ` liuye @ 2026-07-17 9:47 ` Lorenzo Stoakes (ARM) 2026-07-17 10:29 ` Johannes Weiner 1 sibling, 0 replies; 14+ messages in thread From: Lorenzo Stoakes (ARM) @ 2026-07-17 9:47 UTC (permalink / raw) To: liuye Cc: Ye Liu, Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich, Andrew Morton, David Hildenbrand, Liam R. Howlett, Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Kairui Song, Qi Zheng, Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts, Dev Jain, Lance Yang, Usama Arif, driver-core, linux-kernel, linux-mm, linux-fsdevel, Johannes Weiner On Fri, Jul 17, 2026 at 05:38:10PM +0800, liuye wrote: > > 在 2026/7/17 17:24, Ye Liu 写道: > > > > 在 2026/7/17 16:08, Lorenzo Stoakes (ARM) 写道: > >> +cc Johannes > >> > >> On Fri, Jul 17, 2026 at 02:30:22PM +0800, Ye Liu wrote: > >>> From: Ye Liu <liuye@kylinos.cn> > >>> > >>> Folios on the deferred split list hold physical memory that is > >>> invisible in meminfo. When a THP becomes partially mapped, the > >>> unmapped pages are removed from AnonPages but remain physically > >>> allocated until the shrinker splits the folio. This creates a > >>> memory accounting gap where used memory cannot be attributed to > >>> any meminfo field. > >> Is this really that much of an issue? You're not giving any use cases here. > >> > >> What real-world use case motivated this? > >> > > I have indeed encountered this situation in a customer environment, And determined the cause so not sure what the issue is. It must be quite a strange workload for it to be a meaningful amount. > > but the environment is complex, and I cannot clearly explain why this memory > > black hole occurs in that scenario. > > However, I will provide an example to reproduce it below. > > Use case: The system has 100GB of memory. A user-space program requests 80GB of memory. > > For each 2MB block, only one page is retained, and the mapping of all other pages is unmapped, > > while the process continues to run. > > You'll find that in meminfo, MemFree: only 12GB remains, > > but the process actually uses very little memory. > > This creates a memory black hole situation. > > Below is the test program I used, from AI. Let me stop you there at AI-generated. Not interested. As David said this exposes internal implementation details so it's a non-starter. NAK. Thanks, Lorenzo ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH] mm/thp: expose deferred split folio memory usage in meminfo 2026-07-17 9:38 ` liuye 2026-07-17 9:47 ` Lorenzo Stoakes (ARM) @ 2026-07-17 10:29 ` Johannes Weiner 1 sibling, 0 replies; 14+ messages in thread From: Johannes Weiner @ 2026-07-17 10:29 UTC (permalink / raw) To: liuye Cc: Ye Liu, Lorenzo Stoakes (ARM), Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich, Andrew Morton, David Hildenbrand, Liam R. Howlett, Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Kairui Song, Qi Zheng, Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts, Dev Jain, Lance Yang, Usama Arif, driver-core, linux-kernel, linux-mm, linux-fsdevel Hello, On Fri, Jul 17, 2026 at 05:38:10PM +0800, liuye wrote: > > 在 2026/7/17 17:24, Ye Liu 写道: > > > > 在 2026/7/17 16:08, Lorenzo Stoakes (ARM) 写道: > >> +cc Johannes > >> > >> On Fri, Jul 17, 2026 at 02:30:22PM +0800, Ye Liu wrote: > >>> From: Ye Liu <liuye@kylinos.cn> > >>> > >>> Folios on the deferred split list hold physical memory that is > >>> invisible in meminfo. When a THP becomes partially mapped, the > >>> unmapped pages are removed from AnonPages but remain physically > >>> allocated until the shrinker splits the folio. This creates a > >>> memory accounting gap where used memory cannot be attributed to > >>> any meminfo field. > >> Is this really that much of an issue? You're not giving any use cases here. > >> > >> What real-world use case motivated this? > >> > > I have indeed encountered this situation in a customer environment, We have run into this before as well. It's not super rare. Applications do partial unmaps or protection changes with no awareness for THPs. The issue is likely to stick around with legacy software for some time. Do note that all information is there if you know how to look for it. But it is cumbersome and somewhat inconsistent, so I'm not opposed to fixing it: > > but the environment is complex, and I cannot clearly explain why this memory > > black hole occurs in that scenario. > > However, I will provide an example to reproduce it below. > > Use case: The system has 100GB of memory. A user-space program requests 80GB of memory. > > For each 2MB block, only one page is retained, and the mapping of all other pages is unmapped, > > while the process continues to run. > > You'll find that in meminfo, MemFree: only 12GB remains, > > but the process actually uses very little memory. > > This creates a memory black hole situation. > > Below is the test program I used, from AI. > > > > ps -p $(pgrep -d, test) -o pid,comm,%mem,rss,vsz > PID COMMAND %MEM RSS VSZ > 9358 test 0.1 165220 166032 > > > MemTotal: 101606276 kB > MemFree: 13172092 kB > MemAvailable: 13569668 kB > Buffers: 5352 kB > Cached: 1169520 kB > SwapCached: 0 kB > Active: 84730404 kB > Inactive: 1129792 kB > Active(anon): 84730404 kB This shows actually allocated anon+shmem. > Inactive(anon): 0 kB > Active(file): 0 kB > Inactive(file): 1129792 kB > Unevictable: 6192 kB > Mlocked: 32 kB > SwapTotal: 4194300 kB > SwapFree: 4194300 kB > Zswap: 0 kB > Zswapped: 0 kB > Dirty: 24 kB > Writeback: 0 kB > AnonPages: 969280 kB This shows anon *mapped*. > Mapped: 308848 kB > Shmem: 45076 kB This shows that your (in)active anon *isn't* driven by shmem. > KReclaimable: 96144 kB > Slab: 269540 kB > SReclaimable: 96144 kB > SUnreclaim: 173396 kB > KernelStack: 27584 kB > PageTables: 187408 kB > SecPageTables: 0 kB > NFS_Unstable: 0 kB > Bounce: 0 kB > WritebackTmp: 0 kB > CommitLimit: 54997436 kB > Committed_AS: 6628776 kB > VmallocTotal: 261087232 kB > VmallocUsed: 38696 kB > VmallocChunk: 0 kB > Percpu: 42800 kB > HardwareCorrupted: 0 kB > AnonHugePages: 413696 kB This shows anon pmd-mapped. > ShmemHugePages: 0 kB > ShmemPmdMapped: 0 kB > FileHugePages: 172032 kB > FilePmdMapped: 28672 kB This shows both allocated huge pages and the pmd-mapped portion. So you *can* work it out by looking at the LRU ((in)active) and cross-checking the shmem position. But it is awkward, granted. IMO if there is a case to be made, it's doing the same split we do for file and shmem - AnonHugePages (allocated) and AnonPmdMapped. ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH] mm/thp: expose deferred split folio memory usage in meminfo 2026-07-17 6:30 [PATCH] mm/thp: expose deferred split folio memory usage in meminfo Ye Liu 2026-07-17 8:08 ` Lorenzo Stoakes (ARM) @ 2026-07-17 8:37 ` David Hildenbrand (Arm) 2026-07-17 8:47 ` Lorenzo Stoakes (ARM) 2026-07-17 9:31 ` Ye Liu 1 sibling, 2 replies; 14+ messages in thread From: David Hildenbrand (Arm) @ 2026-07-17 8:37 UTC (permalink / raw) To: Ye Liu, Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich, Andrew Morton, Lorenzo Stoakes Cc: Ye Liu, Liam R. Howlett, Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Kairui Song, Qi Zheng, Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts, Dev Jain, Lance Yang, Usama Arif, driver-core, linux-kernel, linux-mm, linux-fsdevel On 7/17/26 08:30, Ye Liu wrote: > From: Ye Liu <liuye@kylinos.cn> > > Folios on the deferred split list hold physical memory that is > invisible in meminfo. When a THP becomes partially mapped, the > unmapped pages are removed from AnonPages but remain physically > allocated until the shrinker splits the folio. This creates a > memory accounting gap where used memory cannot be attributed to > any meminfo field. > > Add NR_DEFERRED_SPLIT_PAGES to track the total memory consumed by > folios currently on the deferred_split_lru, updated via > mod_node_page_state() at all enqueue/dequeue points. The new field > DeferredSplitPages is visible in /proc/meminfo, /proc/vmstat, and > per-node /sys/devices/system/node/node*/meminfo. > Oh no. This is really exposing a current implementation detail where we have this information easily available in a way that we will not be able to change that implementation later. -- Cheers, David ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH] mm/thp: expose deferred split folio memory usage in meminfo 2026-07-17 8:37 ` David Hildenbrand (Arm) @ 2026-07-17 8:47 ` Lorenzo Stoakes (ARM) 2026-07-17 9:31 ` Ye Liu 1 sibling, 0 replies; 14+ messages in thread From: Lorenzo Stoakes (ARM) @ 2026-07-17 8:47 UTC (permalink / raw) To: David Hildenbrand (Arm) Cc: Ye Liu, Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich, Andrew Morton, Ye Liu, Liam R. Howlett, Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Kairui Song, Qi Zheng, Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts, Dev Jain, Lance Yang, Usama Arif, driver-core, linux-kernel, linux-mm, linux-fsdevel On Fri, Jul 17, 2026 at 10:37:21AM +0200, David Hildenbrand (Arm) wrote: > On 7/17/26 08:30, Ye Liu wrote: > > From: Ye Liu <liuye@kylinos.cn> > > > > Folios on the deferred split list hold physical memory that is > > invisible in meminfo. When a THP becomes partially mapped, the > > unmapped pages are removed from AnonPages but remain physically > > allocated until the shrinker splits the folio. This creates a > > memory accounting gap where used memory cannot be attributed to > > any meminfo field. > > > > Add NR_DEFERRED_SPLIT_PAGES to track the total memory consumed by > > folios currently on the deferred_split_lru, updated via > > mod_node_page_state() at all enqueue/dequeue points. The new field > > DeferredSplitPages is visible in /proc/meminfo, /proc/vmstat, and > > per-node /sys/devices/system/node/node*/meminfo. > > > > Oh no. > > This is really exposing a current implementation detail where we have this > information easily available in a way that we will not be able to change that > implementation later. Yes, also this :) agree entirely. > > -- > Cheers, > > David Thanks, Lorenzo ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH] mm/thp: expose deferred split folio memory usage in meminfo 2026-07-17 8:37 ` David Hildenbrand (Arm) 2026-07-17 8:47 ` Lorenzo Stoakes (ARM) @ 2026-07-17 9:31 ` Ye Liu 2026-07-17 9:33 ` David Hildenbrand (Arm) 2026-07-17 10:29 ` Vlastimil Babka (SUSE) 1 sibling, 2 replies; 14+ messages in thread From: Ye Liu @ 2026-07-17 9:31 UTC (permalink / raw) To: David Hildenbrand (Arm), Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich, Andrew Morton, Lorenzo Stoakes Cc: Ye Liu, Liam R. Howlett, Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Kairui Song, Qi Zheng, Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts, Dev Jain, Lance Yang, Usama Arif, driver-core, linux-kernel, linux-mm, linux-fsdevel 在 2026/7/17 16:37, David Hildenbrand (Arm) 写道: > On 7/17/26 08:30, Ye Liu wrote: >> From: Ye Liu <liuye@kylinos.cn> >> >> Folios on the deferred split list hold physical memory that is >> invisible in meminfo. When a THP becomes partially mapped, the >> unmapped pages are removed from AnonPages but remain physically >> allocated until the shrinker splits the folio. This creates a >> memory accounting gap where used memory cannot be attributed to >> any meminfo field. >> >> Add NR_DEFERRED_SPLIT_PAGES to track the total memory consumed by >> folios currently on the deferred_split_lru, updated via >> mod_node_page_state() at all enqueue/dequeue points. The new field >> DeferredSplitPages is visible in /proc/meminfo, /proc/vmstat, and >> per-node /sys/devices/system/node/node*/meminfo. >> > > Oh no. > > This is really exposing a current implementation detail where we have this > information easily available in a way that we will not be able to change that > implementation later. > Yes, `/proc/meminfo` is a user-space ABI. Adding it there means you can't change the semantics, and the field will become unmaintainable, yet you can't delete it either. But I think this information is still necessary. Where would be a better place to put it? -- Thanks, Ye Liu ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH] mm/thp: expose deferred split folio memory usage in meminfo 2026-07-17 9:31 ` Ye Liu @ 2026-07-17 9:33 ` David Hildenbrand (Arm) 2026-07-17 9:48 ` Lorenzo Stoakes (ARM) 2026-07-17 10:29 ` Vlastimil Babka (SUSE) 1 sibling, 1 reply; 14+ messages in thread From: David Hildenbrand (Arm) @ 2026-07-17 9:33 UTC (permalink / raw) To: Ye Liu, Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich, Andrew Morton, Lorenzo Stoakes Cc: Ye Liu, Liam R. Howlett, Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Kairui Song, Qi Zheng, Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts, Dev Jain, Lance Yang, Usama Arif, driver-core, linux-kernel, linux-mm, linux-fsdevel On 7/17/26 11:31, Ye Liu wrote: > > > 在 2026/7/17 16:37, David Hildenbrand (Arm) 写道: >> On 7/17/26 08:30, Ye Liu wrote: >>> From: Ye Liu <liuye@kylinos.cn> >>> >>> Folios on the deferred split list hold physical memory that is >>> invisible in meminfo. When a THP becomes partially mapped, the >>> unmapped pages are removed from AnonPages but remain physically >>> allocated until the shrinker splits the folio. This creates a >>> memory accounting gap where used memory cannot be attributed to >>> any meminfo field. >>> >>> Add NR_DEFERRED_SPLIT_PAGES to track the total memory consumed by >>> folios currently on the deferred_split_lru, updated via >>> mod_node_page_state() at all enqueue/dequeue points. The new field >>> DeferredSplitPages is visible in /proc/meminfo, /proc/vmstat, and >>> per-node /sys/devices/system/node/node*/meminfo. >>> >> >> Oh no. >> >> This is really exposing a current implementation detail where we have this >> information easily available in a way that we will not be able to change that >> implementation later. >> > > Yes, `/proc/meminfo` is a user-space ABI. > Adding it there means you can't change the semantics, > and the field will become unmaintainable, yet you can't delete it either. > But I think this information is still necessary. > Where would be a better place to put it? Don't expose it to user space. -- Cheers, David ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH] mm/thp: expose deferred split folio memory usage in meminfo 2026-07-17 9:33 ` David Hildenbrand (Arm) @ 2026-07-17 9:48 ` Lorenzo Stoakes (ARM) 2026-07-17 9:54 ` Ye Liu 0 siblings, 1 reply; 14+ messages in thread From: Lorenzo Stoakes (ARM) @ 2026-07-17 9:48 UTC (permalink / raw) To: David Hildenbrand (Arm) Cc: Ye Liu, Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich, Andrew Morton, Ye Liu, Liam R. Howlett, Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Kairui Song, Qi Zheng, Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts, Dev Jain, Lance Yang, Usama Arif, driver-core, linux-kernel, linux-mm, linux-fsdevel On Fri, Jul 17, 2026 at 11:33:05AM +0200, David Hildenbrand (Arm) wrote: > On 7/17/26 11:31, Ye Liu wrote: > > > > > > 在 2026/7/17 16:37, David Hildenbrand (Arm) 写道: > >> On 7/17/26 08:30, Ye Liu wrote: > >>> From: Ye Liu <liuye@kylinos.cn> > >>> > >>> Folios on the deferred split list hold physical memory that is > >>> invisible in meminfo. When a THP becomes partially mapped, the > >>> unmapped pages are removed from AnonPages but remain physically > >>> allocated until the shrinker splits the folio. This creates a > >>> memory accounting gap where used memory cannot be attributed to > >>> any meminfo field. > >>> > >>> Add NR_DEFERRED_SPLIT_PAGES to track the total memory consumed by > >>> folios currently on the deferred_split_lru, updated via > >>> mod_node_page_state() at all enqueue/dequeue points. The new field > >>> DeferredSplitPages is visible in /proc/meminfo, /proc/vmstat, and > >>> per-node /sys/devices/system/node/node*/meminfo. > >>> > >> > >> Oh no. > >> > >> This is really exposing a current implementation detail where we have this > >> information easily available in a way that we will not be able to change that > >> implementation later. > >> > > > > Yes, `/proc/meminfo` is a user-space ABI. > > Adding it there means you can't change the semantics, > > and the field will become unmaintainable, yet you can't delete it either. > > But I think this information is still necessary. > > Where would be a better place to put it? > > Don't expose it to user space. Yeah please stop. We don't want this change, it exposes internal implementation details. It's a no. Next time send an RFC or a discussion thread. > > -- > Cheers, > > David Thanks, Lorenzo ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH] mm/thp: expose deferred split folio memory usage in meminfo 2026-07-17 9:48 ` Lorenzo Stoakes (ARM) @ 2026-07-17 9:54 ` Ye Liu 2026-07-17 10:29 ` David Hildenbrand (Arm) 0 siblings, 1 reply; 14+ messages in thread From: Ye Liu @ 2026-07-17 9:54 UTC (permalink / raw) To: Lorenzo Stoakes (ARM), David Hildenbrand (Arm) Cc: Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich, Andrew Morton, Ye Liu, Liam R. Howlett, Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Kairui Song, Qi Zheng, Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts, Dev Jain, Lance Yang, Usama Arif, driver-core, linux-kernel, linux-mm, linux-fsdevel 在 2026/7/17 17:48, Lorenzo Stoakes (ARM) 写道: > On Fri, Jul 17, 2026 at 11:33:05AM +0200, David Hildenbrand (Arm) wrote: >> On 7/17/26 11:31, Ye Liu wrote: >>> >>> >>> 在 2026/7/17 16:37, David Hildenbrand (Arm) 写道: >>>> On 7/17/26 08:30, Ye Liu wrote: >>>>> From: Ye Liu <liuye@kylinos.cn> >>>>> >>>>> Folios on the deferred split list hold physical memory that is >>>>> invisible in meminfo. When a THP becomes partially mapped, the >>>>> unmapped pages are removed from AnonPages but remain physically >>>>> allocated until the shrinker splits the folio. This creates a >>>>> memory accounting gap where used memory cannot be attributed to >>>>> any meminfo field. >>>>> >>>>> Add NR_DEFERRED_SPLIT_PAGES to track the total memory consumed by >>>>> folios currently on the deferred_split_lru, updated via >>>>> mod_node_page_state() at all enqueue/dequeue points. The new field >>>>> DeferredSplitPages is visible in /proc/meminfo, /proc/vmstat, and >>>>> per-node /sys/devices/system/node/node*/meminfo. >>>>> >>>> >>>> Oh no. >>>> >>>> This is really exposing a current implementation detail where we have this >>>> information easily available in a way that we will not be able to change that >>>> implementation later. >>>> >>> >>> Yes, `/proc/meminfo` is a user-space ABI. >>> Adding it there means you can't change the semantics, >>> and the field will become unmaintainable, yet you can't delete it either. >>> But I think this information is still necessary. >>> Where would be a better place to put it? >> >> Don't expose it to user space. > > Yeah please stop. > > We don't want this change, it exposes internal implementation details. It's a > no. > > Next time send an RFC or a discussion thread. > Yes, sending an RFC would be more appropriate. I'd also like to ask if it's possible to implement this in `/sys/kernel/debug/deferred_split_pages`, or are you completely against querying this statistic? How can we quickly identify this situation? I believe some people will still be confused: where did the memory go? >> >> -- >> Cheers, >> >> David > > Thanks, Lorenzo -- Thanks, Ye Liu ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH] mm/thp: expose deferred split folio memory usage in meminfo 2026-07-17 9:54 ` Ye Liu @ 2026-07-17 10:29 ` David Hildenbrand (Arm) 0 siblings, 0 replies; 14+ messages in thread From: David Hildenbrand (Arm) @ 2026-07-17 10:29 UTC (permalink / raw) To: Ye Liu, Lorenzo Stoakes (ARM) Cc: Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich, Andrew Morton, Ye Liu, Liam R. Howlett, Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Kairui Song, Qi Zheng, Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts, Dev Jain, Lance Yang, Usama Arif, driver-core, linux-kernel, linux-mm, linux-fsdevel On 7/17/26 11:54, Ye Liu wrote: > > > 在 2026/7/17 17:48, Lorenzo Stoakes (ARM) 写道: >> On Fri, Jul 17, 2026 at 11:33:05AM +0200, David Hildenbrand (Arm) wrote: >>> >>> Don't expose it to user space. >> >> Yeah please stop. >> >> We don't want this change, it exposes internal implementation details. It's a >> no. >> >> Next time send an RFC or a discussion thread. >> > Yes, sending an RFC would be more appropriate. > > I'd also like to ask if it's possible to implement this in > `/sys/kernel/debug/deferred_split_pages`, or are you completely against querying this statistic? It's not pages. It's folios. You don't know how much memory is there to reclaim. You don't know if it can even be reclaimed. You don't even know if there can be any memory reclaimed *at all*, because there are scenarios where we add folios that are actually *not* partially mapped today. You don't even know the relationship to memcgs. > > How can we quickly identify this situation? > I believe some people will still be confused: where did the memory go? Just like other caches that are shrunk on memory pressure. We do have a "split_deferred" and a "nr_anon_partially_mapped" counter in "/sys/kernel/mm/transparent_hugepage/hugepages-2048kB/", maybe that helps. -- Cheers, David ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH] mm/thp: expose deferred split folio memory usage in meminfo 2026-07-17 9:31 ` Ye Liu 2026-07-17 9:33 ` David Hildenbrand (Arm) @ 2026-07-17 10:29 ` Vlastimil Babka (SUSE) 1 sibling, 0 replies; 14+ messages in thread From: Vlastimil Babka (SUSE) @ 2026-07-17 10:29 UTC (permalink / raw) To: Ye Liu, David Hildenbrand (Arm), Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich, Andrew Morton, Lorenzo Stoakes Cc: Ye Liu, Liam R. Howlett, Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Kairui Song, Qi Zheng, Shakeel Butt, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu, Zi Yan, Baolin Wang, Nico Pache, Ryan Roberts, Dev Jain, Lance Yang, Usama Arif, driver-core, linux-kernel, linux-mm, linux-fsdevel On 7/17/26 11:31, Ye Liu wrote: > 在 2026/7/17 16:37, David Hildenbrand (Arm) 写道: >> On 7/17/26 08:30, Ye Liu wrote: >>> From: Ye Liu <liuye@kylinos.cn> >>> >>> Folios on the deferred split list hold physical memory that is >>> invisible in meminfo. When a THP becomes partially mapped, the >>> unmapped pages are removed from AnonPages but remain physically >>> allocated until the shrinker splits the folio. This creates a >>> memory accounting gap where used memory cannot be attributed to >>> any meminfo field. >>> >>> Add NR_DEFERRED_SPLIT_PAGES to track the total memory consumed by >>> folios currently on the deferred_split_lru, updated via >>> mod_node_page_state() at all enqueue/dequeue points. The new field >>> DeferredSplitPages is visible in /proc/meminfo, /proc/vmstat, and >>> per-node /sys/devices/system/node/node*/meminfo. >>> >> >> Oh no. >> >> This is really exposing a current implementation detail where we have this >> information easily available in a way that we will not be able to change that >> implementation later. >> > > Yes, `/proc/meminfo` is a user-space ABI. > Adding it there means you can't change the semantics, > and the field will become unmaintainable, yet you can't delete it either. > But I think this information is still necessary. > Where would be a better place to put it? I once added KReclaimable to /proc/meminfo, its value used to be SReclaimable plus ION allocator (I think), but that one was later removed so it's now equivalent and I regret adding it and it stays there. If the deferred split folios are just waiting for a memory pressure kick a shrinker to reclaim them, the KReclaimable counter could however be a sensible fit and wouldn't need adding a new user-visible output. However I assume there's tradeoff with having to track this (complexity, overhead) and possibly overhead of reading /proc/meminfo so it's not clear-cut. Just pointing it out as a possibility. ^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-07-17 10:29 UTC | newest] Thread overview: 14+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-07-17 6:30 [PATCH] mm/thp: expose deferred split folio memory usage in meminfo Ye Liu 2026-07-17 8:08 ` Lorenzo Stoakes (ARM) 2026-07-17 9:24 ` Ye Liu 2026-07-17 9:38 ` liuye 2026-07-17 9:47 ` Lorenzo Stoakes (ARM) 2026-07-17 10:29 ` Johannes Weiner 2026-07-17 8:37 ` David Hildenbrand (Arm) 2026-07-17 8:47 ` Lorenzo Stoakes (ARM) 2026-07-17 9:31 ` Ye Liu 2026-07-17 9:33 ` David Hildenbrand (Arm) 2026-07-17 9:48 ` Lorenzo Stoakes (ARM) 2026-07-17 9:54 ` Ye Liu 2026-07-17 10:29 ` David Hildenbrand (Arm) 2026-07-17 10:29 ` Vlastimil Babka (SUSE)
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.