From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from out-186.mta1.migadu.com (out-186.mta1.migadu.com [95.215.58.186]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0426C2045AD for ; Fri, 17 Jul 2026 06:31:23 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.186 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784269888; cv=none; b=WQCUKe5A66ez9DNJ2PuvDWG/eTkuT7OjOnEU8X+Ka4xk6k7KfQzfS7bqlA+jyMO1k/BLTHaz/Q5T+SZNRTl1Y0v5xXvzOm46weipfyPBU6D1pYqhFcvMsDmLM3TvFdGh1w/QaWpfA44MZRSQELc8Jo0wmzW5i+Wj0wddnzmRuzA= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784269888; c=relaxed/simple; bh=Svg2Y8wTOAGd6IPOnPBio+4QPIkpbi2qYQQnU1fR04c=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=Bj5u1sbqtfKhVAZe2UGrxfTZilGHRkid4oKnqnt6A93hh/tR41uXtX+i49f8iT4iZy4fTgzKGLjPUDS0nzq6P7g1vx0rKp+qrqD/4Sy6YK2BAcLoat94LAXakoVx6T3pXz8TEfqO9Yu76/VwufRSdGeKdmQUnhT65OxaC6sYVww= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev; spf=pass smtp.mailfrom=linux.dev; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b=iAvO9NTL; arc=none smtp.client-ip=95.215.58.186 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b="iAvO9NTL" X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.dev; s=key1; t=1784269881; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding; bh=dtmAsjOZRbEJt8owf9zTbDLUylJSS4K6ysbF5XED/rI=; b=iAvO9NTLFOK4wGbutvHypW4H3LqgyMz7jH7ChYnLbA1a/WW3fuZGVu8gEBVzvQ/eEJ84MB MSCnUKkJxgUdDk1nIXK1SgnSg9+8ULO2/U3LtacoMyIdP9RbvNPJfbhl/aXdjrrHSK71Bg CxTdNRpZVLcyW4kVejoPPvf7RBpjkrk= From: Ye Liu 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@lists.linux.dev, linux-kernel@vger.kernel.org, linux-mm@kvack.org, linux-fsdevel@vger.kernel.org Subject: [PATCH] mm/thp: expose deferred split folio memory usage in meminfo Date: Fri, 17 Jul 2026 14:30:22 +0800 Message-ID: <20260717063025.168436-1-ye.liu@linux.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Migadu-Flow: FLOW_OUT From: Ye Liu 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 --- 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