* [PATCH v11 mm-new 00/10] mm, bpf: BPF-MM, BPF-THP
@ 2025-10-20 3:10 Yafang Shao
2025-10-20 3:10 ` [PATCH v11 mm-new 01/10] mm: thp: remove vm_flags parameter from khugepaged_enter_vma() Yafang Shao
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: Yafang Shao @ 2025-10-20 3:10 UTC (permalink / raw)
To: akpm, ast, daniel, andrii, martin.lau, eddyz87, song,
yonghong.song, john.fastabend, kpsingh, sdf, haoluo, jolsa,
david, ziy, lorenzo.stoakes, Liam.Howlett, npache, ryan.roberts,
dev.jain, hannes, usamaarif642, gutierrez.asier, willy,
ameryhung, rientjes, corbet, 21cnbao, shakeel.butt, tj,
lance.yang, rdunlap
Cc: bpf, linux-mm, linux-doc, linux-kernel, Yafang Shao
History
=======
RFC v1: fmod_ret based BPF-THP hook
https://lore.kernel.org/linux-mm/20250429024139.34365-1-laoar.shao@gmail.com/
RFC v2: struct_ops based BPF-THP hook
https://lore.kernel.org/linux-mm/20250520060504.20251-1-laoar.shao@gmail.com/
RFC v4: Get THP order with interface get_suggested_order()
https://lore.kernel.org/linux-mm/20250729091807.84310-1-laoar.shao@gmail.com/
v4->v9: Simplify the interface to:
int thp_get_order(struct vm_area_struct *vma, enum tva_type type,
unsigned long orders);
https://lore.kernel.org/linux-mm/20250930055826.9810-1-laoar.shao@gmail.com/
v9->RFC v10: Scope BPF-THP to individual processes
RFC v10->v11: Remove the RFC tag
The Design
==========
Scoping BPF-THP to cgroup is rejected
-------------------------------------
As explained by Gutierrez:
1. It breaks the cgroup hierarchy when 2 siblings have different THP policies
2. Cgroup was designed for resource management not for grouping processes and
tune those processes
3. We set a precedent for other people adding new flags to cgroup and
potentially polluting cgroups. We may end up with cgroups having tens of
different flags, making sysadmin's job more complex
The related links are:
https://lore.kernel.org/linux-mm/1940d681-94a6-48fb-b889-cd8f0b91b330@huawei-partners.com/
https://lore.kernel.org/linux-mm/20241030150851.GB706616@cmpxchg.org/
So we has to scope it to process.
Scoping BPF-THP to process
--------------------------
To eliminate potential conflicts among competing BPF-THP instances, we
enforce that each process is exclusively managed by a single BPF-THP. This
approach has received agreement from David. For context, see:
https://lore.kernel.org/linux-mm/3577f7fd-429a-49c5-973b-38174a67be15@redhat.com/
When registering a BPF-THP, we specify the PID of a target task. The
BPF-THP is then installed in the task's `mm_struct`
struct mm_struct {
struct bpf_thp_ops __rcu *thp_thp;
};
Inheritance Behavior:
- Existing child processes are unaffected
- Newly forked children inherit the BPF-THP from their parent
- The BPF-THP persists across execve() calls
A new linked list tracks all tasks managed by each BPF-THP instance:
- Newly managed tasks are added to the list
- Exiting tasks are automatically removed from the list
- During BPF-THP unregistration (e.g., when the BPF link is removed), all
managed tasks have their bpf_thp pointer set to NULL
- BPF-THP instances can be dynamically updated, with all tracked tasks
automatically migrating to the new version.
This design simplifies BPF-THP management in production environments by
providing clear lifecycle management and preventing conflicts between
multiple BPF-THP instances.
Global Mode
-----------
The per-process BPF-THP mode is unsuitable for managing shared resources
such as shmem THP and file-backed THP. This aligns with known cgroup
limitations for similar scenarios:
https://lore.kernel.org/linux-mm/YwNold0GMOappUxc@slm.duckdns.org/
Introduce a global BPF-THP mode to address this gap. When registered:
- All existing per-process instances are disabled
- New per-process registrations are blocked
- Existing per-process instances remain registered (no forced unregistration)
The global mode takes precedence over per-process instances. Updates are
type-isolated: global instances can only be updated by new global
instances, and per-process instances by new per-process instances.
Yafang Shao (10):
mm: thp: remove vm_flags parameter from khugepaged_enter_vma()
mm: thp: remove vm_flags parameter from thp_vma_allowable_order()
mm: thp: add support for BPF based THP order selection
mm: thp: decouple THP allocation between swap and page fault paths
mm: thp: enable THP allocation exclusively through khugepaged
mm: bpf-thp: add support for global mode
Documentation: add BPF THP
selftests/bpf: add a simple BPF based THP policy
selftests/bpf: add test case to update THP policy
selftests/bpf: add test case for BPF-THP inheritance across fork
Documentation/admin-guide/mm/transhuge.rst | 113 +++++
MAINTAINERS | 3 +
fs/exec.c | 1 +
fs/proc/task_mmu.c | 3 +-
include/linux/huge_mm.h | 59 ++-
include/linux/khugepaged.h | 10 +-
include/linux/mm_types.h | 17 +
kernel/fork.c | 1 +
mm/Kconfig | 22 +
mm/Makefile | 1 +
mm/huge_memory.c | 7 +-
mm/huge_memory_bpf.c | 419 ++++++++++++++++++
mm/khugepaged.c | 35 +-
mm/madvise.c | 7 +
mm/memory.c | 22 +-
mm/mmap.c | 1 +
mm/shmem.c | 2 +-
mm/vma.c | 6 +-
tools/testing/selftests/bpf/config | 3 +
.../selftests/bpf/prog_tests/thp_adjust.c | 357 +++++++++++++++
.../selftests/bpf/progs/test_thp_adjust.c | 53 +++
21 files changed, 1092 insertions(+), 50 deletions(-)
create mode 100644 mm/huge_memory_bpf.c
create mode 100644 tools/testing/selftests/bpf/prog_tests/thp_adjust.c
create mode 100644 tools/testing/selftests/bpf/progs/test_thp_adjust.c
--
2.47.3
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v11 mm-new 01/10] mm: thp: remove vm_flags parameter from khugepaged_enter_vma()
2025-10-20 3:10 [PATCH v11 mm-new 00/10] mm, bpf: BPF-MM, BPF-THP Yafang Shao
@ 2025-10-20 3:10 ` Yafang Shao
2025-10-20 3:10 ` [PATCH v11 mm-new 02/10] mm: thp: remove vm_flags parameter from thp_vma_allowable_order() Yafang Shao
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Yafang Shao @ 2025-10-20 3:10 UTC (permalink / raw)
To: akpm, ast, daniel, andrii, martin.lau, eddyz87, song,
yonghong.song, john.fastabend, kpsingh, sdf, haoluo, jolsa,
david, ziy, lorenzo.stoakes, Liam.Howlett, npache, ryan.roberts,
dev.jain, hannes, usamaarif642, gutierrez.asier, willy,
ameryhung, rientjes, corbet, 21cnbao, shakeel.butt, tj,
lance.yang, rdunlap
Cc: bpf, linux-mm, linux-doc, linux-kernel, Yafang Shao, Yang Shi
The khugepaged_enter_vma() function requires handling in two specific
scenarios:
1. New VMA creation
When a new VMA is created (for anon vma, it is deferred to pagefault), if
vma->vm_mm is not present in khugepaged_mm_slot, it must be added. In
this case, khugepaged_enter_vma() is called after vma->vm_flags have been
set, allowing direct use of the VMA's flags.
2. VMA flag modification
When vma->vm_flags are modified (particularly when VM_HUGEPAGE is set),
the system must recheck whether to add vma->vm_mm to khugepaged_mm_slot.
Currently, khugepaged_enter_vma() is called before the flag update, so
the call must be relocated to occur after vma->vm_flags have been set.
In the VMA merging path, khugepaged_enter_vma() is also called. For this
case, since VMA merging only occurs when the vm_flags of both VMAs are
identical (excluding special flags like VM_SOFTDIRTY), we can safely use
target->vm_flags instead. (It is worth noting that khugepaged_enter_vma()
can be removed from the VMA merging path because the VMA has already been
added in the two aforementioned cases. We will address this cleanup in a
separate patch.)
After this change, we can further remove vm_flags parameter from
thp_vma_allowable_order(). That will be handled in a followup patch.
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
Cc: Yang Shi <shy828301@gmail.com>
Cc: Usama Arif <usamaarif642@gmail.com>
---
include/linux/khugepaged.h | 10 ++++++----
mm/huge_memory.c | 2 +-
mm/khugepaged.c | 27 ++++++++++++++-------------
mm/madvise.c | 7 +++++++
mm/vma.c | 6 +++---
5 files changed, 31 insertions(+), 21 deletions(-)
diff --git a/include/linux/khugepaged.h b/include/linux/khugepaged.h
index eb1946a70cff..b30814d3d665 100644
--- a/include/linux/khugepaged.h
+++ b/include/linux/khugepaged.h
@@ -13,8 +13,8 @@ extern void khugepaged_destroy(void);
extern int start_stop_khugepaged(void);
extern void __khugepaged_enter(struct mm_struct *mm);
extern void __khugepaged_exit(struct mm_struct *mm);
-extern void khugepaged_enter_vma(struct vm_area_struct *vma,
- vm_flags_t vm_flags);
+extern void khugepaged_enter_vma(struct vm_area_struct *vma);
+extern void khugepaged_enter_mm(struct mm_struct *mm);
extern void khugepaged_min_free_kbytes_update(void);
extern bool current_is_khugepaged(void);
extern int collapse_pte_mapped_thp(struct mm_struct *mm, unsigned long addr,
@@ -38,8 +38,10 @@ static inline void khugepaged_fork(struct mm_struct *mm, struct mm_struct *oldmm
static inline void khugepaged_exit(struct mm_struct *mm)
{
}
-static inline void khugepaged_enter_vma(struct vm_area_struct *vma,
- vm_flags_t vm_flags)
+static inline void khugepaged_enter_vma(struct vm_area_struct *vma)
+{
+}
+static inline void khugepaged_enter_mm(struct mm_struct *mm)
{
}
static inline int collapse_pte_mapped_thp(struct mm_struct *mm,
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index bfb52c564fb3..ea3199ea98fc 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -1390,7 +1390,7 @@ vm_fault_t do_huge_pmd_anonymous_page(struct vm_fault *vmf)
ret = vmf_anon_prepare(vmf);
if (ret)
return ret;
- khugepaged_enter_vma(vma, vma->vm_flags);
+ khugepaged_enter_vma(vma);
if (!(vmf->flags & FAULT_FLAG_WRITE) &&
!mm_forbids_zeropage(vma->vm_mm) &&
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index e947b96e1443..c2c683f11251 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -353,12 +353,6 @@ int hugepage_madvise(struct vm_area_struct *vma,
#endif
*vm_flags &= ~VM_NOHUGEPAGE;
*vm_flags |= VM_HUGEPAGE;
- /*
- * If the vma become good for khugepaged to scan,
- * register it here without waiting a page fault that
- * may not happen any time soon.
- */
- khugepaged_enter_vma(vma, *vm_flags);
break;
case MADV_NOHUGEPAGE:
*vm_flags &= ~VM_HUGEPAGE;
@@ -457,14 +451,21 @@ void __khugepaged_enter(struct mm_struct *mm)
wake_up_interruptible(&khugepaged_wait);
}
-void khugepaged_enter_vma(struct vm_area_struct *vma,
- vm_flags_t vm_flags)
+void khugepaged_enter_mm(struct mm_struct *mm)
{
- if (!mm_flags_test(MMF_VM_HUGEPAGE, vma->vm_mm) &&
- hugepage_pmd_enabled()) {
- if (thp_vma_allowable_order(vma, vm_flags, TVA_KHUGEPAGED, PMD_ORDER))
- __khugepaged_enter(vma->vm_mm);
- }
+ if (mm_flags_test(MMF_VM_HUGEPAGE, mm))
+ return;
+ if (!hugepage_pmd_enabled())
+ return;
+
+ __khugepaged_enter(mm);
+}
+
+void khugepaged_enter_vma(struct vm_area_struct *vma)
+{
+ if (!thp_vma_allowable_order(vma, vma->vm_flags, TVA_KHUGEPAGED, PMD_ORDER))
+ return;
+ khugepaged_enter_mm(vma->vm_mm);
}
void __khugepaged_exit(struct mm_struct *mm)
diff --git a/mm/madvise.c b/mm/madvise.c
index fb1c86e630b6..8de7c39305dd 100644
--- a/mm/madvise.c
+++ b/mm/madvise.c
@@ -1425,6 +1425,13 @@ static int madvise_vma_behavior(struct madvise_behavior *madv_behavior)
VM_WARN_ON_ONCE(madv_behavior->lock_mode != MADVISE_MMAP_WRITE_LOCK);
error = madvise_update_vma(new_flags, madv_behavior);
+ /*
+ * If the vma become good for khugepaged to scan,
+ * register it here without waiting a page fault that
+ * may not happen any time soon.
+ */
+ if (!error && new_flags & VM_HUGEPAGE)
+ khugepaged_enter_mm(vma->vm_mm);
out:
/*
* madvise() returns EAGAIN if kernel resources, such as
diff --git a/mm/vma.c b/mm/vma.c
index a2e1ae954662..64bf384fc325 100644
--- a/mm/vma.c
+++ b/mm/vma.c
@@ -973,7 +973,7 @@ static __must_check struct vm_area_struct *vma_merge_existing_range(
if (err || commit_merge(vmg))
goto abort;
- khugepaged_enter_vma(vmg->target, vmg->vm_flags);
+ khugepaged_enter_vma(vmg->target);
vmg->state = VMA_MERGE_SUCCESS;
return vmg->target;
@@ -1093,7 +1093,7 @@ struct vm_area_struct *vma_merge_new_range(struct vma_merge_struct *vmg)
* following VMA if we have VMAs on both sides.
*/
if (vmg->target && !vma_expand(vmg)) {
- khugepaged_enter_vma(vmg->target, vmg->vm_flags);
+ khugepaged_enter_vma(vmg->target);
vmg->state = VMA_MERGE_SUCCESS;
return vmg->target;
}
@@ -2520,7 +2520,7 @@ static int __mmap_new_vma(struct mmap_state *map, struct vm_area_struct **vmap)
* call covers the non-merge case.
*/
if (!vma_is_anonymous(vma))
- khugepaged_enter_vma(vma, map->vm_flags);
+ khugepaged_enter_vma(vma);
*vmap = vma;
return 0;
--
2.47.3
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v11 mm-new 02/10] mm: thp: remove vm_flags parameter from thp_vma_allowable_order()
2025-10-20 3:10 [PATCH v11 mm-new 00/10] mm, bpf: BPF-MM, BPF-THP Yafang Shao
2025-10-20 3:10 ` [PATCH v11 mm-new 01/10] mm: thp: remove vm_flags parameter from khugepaged_enter_vma() Yafang Shao
@ 2025-10-20 3:10 ` Yafang Shao
2025-10-20 3:10 ` [PATCH v11 mm-new 03/10] mm: thp: add support for BPF based THP order selection Yafang Shao
2025-10-20 3:10 ` [PATCH v11 mm-new 04/10] mm: thp: decouple THP allocation between swap and page fault paths Yafang Shao
3 siblings, 0 replies; 5+ messages in thread
From: Yafang Shao @ 2025-10-20 3:10 UTC (permalink / raw)
To: akpm, ast, daniel, andrii, martin.lau, eddyz87, song,
yonghong.song, john.fastabend, kpsingh, sdf, haoluo, jolsa,
david, ziy, lorenzo.stoakes, Liam.Howlett, npache, ryan.roberts,
dev.jain, hannes, usamaarif642, gutierrez.asier, willy,
ameryhung, rientjes, corbet, 21cnbao, shakeel.butt, tj,
lance.yang, rdunlap
Cc: bpf, linux-mm, linux-doc, linux-kernel, Yafang Shao
Because all calls to thp_vma_allowable_order() pass vma->vm_flags as the
vma_flags argument, we can remove the parameter and have the function
access vma->vm_flags directly.
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
Acked-by: Usama Arif <usamaarif642@gmail.com>
---
fs/proc/task_mmu.c | 3 +--
include/linux/huge_mm.h | 16 ++++++++--------
mm/huge_memory.c | 4 ++--
mm/khugepaged.c | 10 +++++-----
mm/memory.c | 11 +++++------
mm/shmem.c | 2 +-
6 files changed, 22 insertions(+), 24 deletions(-)
diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c
index fc35a0543f01..e713d1905750 100644
--- a/fs/proc/task_mmu.c
+++ b/fs/proc/task_mmu.c
@@ -1369,8 +1369,7 @@ static int show_smap(struct seq_file *m, void *v)
__show_smap(m, &mss, false);
seq_printf(m, "THPeligible: %8u\n",
- !!thp_vma_allowable_orders(vma, vma->vm_flags, TVA_SMAPS,
- THP_ORDERS_ALL));
+ !!thp_vma_allowable_orders(vma, TVA_SMAPS, THP_ORDERS_ALL));
if (arch_pkeys_enabled())
seq_printf(m, "ProtectionKey: %8u\n", vma_pkey(vma));
diff --git a/include/linux/huge_mm.h b/include/linux/huge_mm.h
index 8eec7a2a977b..5e5f4a8d3c59 100644
--- a/include/linux/huge_mm.h
+++ b/include/linux/huge_mm.h
@@ -101,8 +101,8 @@ enum tva_type {
TVA_FORCED_COLLAPSE, /* Forced collapse (e.g. MADV_COLLAPSE). */
};
-#define thp_vma_allowable_order(vma, vm_flags, type, order) \
- (!!thp_vma_allowable_orders(vma, vm_flags, type, BIT(order)))
+#define thp_vma_allowable_order(vma, type, order) \
+ (!!thp_vma_allowable_orders(vma, type, BIT(order)))
#define split_folio(f) split_folio_to_list(f, NULL)
@@ -266,14 +266,12 @@ static inline unsigned long thp_vma_suitable_orders(struct vm_area_struct *vma,
}
unsigned long __thp_vma_allowable_orders(struct vm_area_struct *vma,
- vm_flags_t vm_flags,
enum tva_type type,
unsigned long orders);
/**
* thp_vma_allowable_orders - determine hugepage orders that are allowed for vma
* @vma: the vm area to check
- * @vm_flags: use these vm_flags instead of vma->vm_flags
* @type: TVA type
* @orders: bitfield of all orders to consider
*
@@ -287,10 +285,11 @@ unsigned long __thp_vma_allowable_orders(struct vm_area_struct *vma,
*/
static inline
unsigned long thp_vma_allowable_orders(struct vm_area_struct *vma,
- vm_flags_t vm_flags,
enum tva_type type,
unsigned long orders)
{
+ vm_flags_t vm_flags = vma->vm_flags;
+
/*
* Optimization to check if required orders are enabled early. Only
* forced collapse ignores sysfs configs.
@@ -309,7 +308,7 @@ unsigned long thp_vma_allowable_orders(struct vm_area_struct *vma,
return 0;
}
- return __thp_vma_allowable_orders(vma, vm_flags, type, orders);
+ return __thp_vma_allowable_orders(vma, type, orders);
}
struct thpsize {
@@ -329,8 +328,10 @@ struct thpsize {
* through madvise or prctl.
*/
static inline bool vma_thp_disabled(struct vm_area_struct *vma,
- vm_flags_t vm_flags, bool forced_collapse)
+ bool forced_collapse)
{
+ vm_flags_t vm_flags = vma->vm_flags;
+
/* Are THPs disabled for this VMA? */
if (vm_flags & VM_NOHUGEPAGE)
return true;
@@ -571,7 +572,6 @@ static inline unsigned long thp_vma_suitable_orders(struct vm_area_struct *vma,
}
static inline unsigned long thp_vma_allowable_orders(struct vm_area_struct *vma,
- vm_flags_t vm_flags,
enum tva_type type,
unsigned long orders)
{
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index ea3199ea98fc..2ad35e5d225e 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -98,7 +98,6 @@ static inline bool file_thp_enabled(struct vm_area_struct *vma)
}
unsigned long __thp_vma_allowable_orders(struct vm_area_struct *vma,
- vm_flags_t vm_flags,
enum tva_type type,
unsigned long orders)
{
@@ -106,6 +105,7 @@ unsigned long __thp_vma_allowable_orders(struct vm_area_struct *vma,
const bool in_pf = type == TVA_PAGEFAULT;
const bool forced_collapse = type == TVA_FORCED_COLLAPSE;
unsigned long supported_orders;
+ vm_flags_t vm_flags = vma->vm_flags;
/* Check the intersection of requested and supported orders. */
if (vma_is_anonymous(vma))
@@ -122,7 +122,7 @@ unsigned long __thp_vma_allowable_orders(struct vm_area_struct *vma,
if (!vma->vm_mm) /* vdso */
return 0;
- if (thp_disabled_by_hw() || vma_thp_disabled(vma, vm_flags, forced_collapse))
+ if (thp_disabled_by_hw() || vma_thp_disabled(vma, forced_collapse))
return 0;
/* khugepaged doesn't collapse DAX vma, but page fault is fine. */
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index c2c683f11251..107796e0e921 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -463,7 +463,7 @@ void khugepaged_enter_mm(struct mm_struct *mm)
void khugepaged_enter_vma(struct vm_area_struct *vma)
{
- if (!thp_vma_allowable_order(vma, vma->vm_flags, TVA_KHUGEPAGED, PMD_ORDER))
+ if (!thp_vma_allowable_order(vma, TVA_KHUGEPAGED, PMD_ORDER))
return;
khugepaged_enter_mm(vma->vm_mm);
}
@@ -914,7 +914,7 @@ static int hugepage_vma_revalidate(struct mm_struct *mm, unsigned long address,
if (!thp_vma_suitable_order(vma, address, PMD_ORDER))
return SCAN_ADDRESS_RANGE;
- if (!thp_vma_allowable_order(vma, vma->vm_flags, type, PMD_ORDER))
+ if (!thp_vma_allowable_order(vma, type, PMD_ORDER))
return SCAN_VMA_CHECK;
/*
* Anon VMA expected, the address may be unmapped then
@@ -1521,7 +1521,7 @@ int collapse_pte_mapped_thp(struct mm_struct *mm, unsigned long addr,
* and map it by a PMD, regardless of sysfs THP settings. As such, let's
* analogously elide sysfs THP settings here and force collapse.
*/
- if (!thp_vma_allowable_order(vma, vma->vm_flags, TVA_FORCED_COLLAPSE, PMD_ORDER))
+ if (!thp_vma_allowable_order(vma, TVA_FORCED_COLLAPSE, PMD_ORDER))
return SCAN_VMA_CHECK;
/* Keep pmd pgtable for uffd-wp; see comment in retract_page_tables() */
@@ -2416,7 +2416,7 @@ static unsigned int khugepaged_scan_mm_slot(unsigned int pages, int *result,
progress++;
break;
}
- if (!thp_vma_allowable_order(vma, vma->vm_flags, TVA_KHUGEPAGED, PMD_ORDER)) {
+ if (!thp_vma_allowable_order(vma, TVA_KHUGEPAGED, PMD_ORDER)) {
skip:
progress++;
continue;
@@ -2747,7 +2747,7 @@ int madvise_collapse(struct vm_area_struct *vma, unsigned long start,
BUG_ON(vma->vm_start > start);
BUG_ON(vma->vm_end < end);
- if (!thp_vma_allowable_order(vma, vma->vm_flags, TVA_FORCED_COLLAPSE, PMD_ORDER))
+ if (!thp_vma_allowable_order(vma, TVA_FORCED_COLLAPSE, PMD_ORDER))
return -EINVAL;
cc = kmalloc(sizeof(*cc), GFP_KERNEL);
diff --git a/mm/memory.c b/mm/memory.c
index 19615bcf234f..8bb458de4fc0 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -4558,7 +4558,7 @@ static struct folio *alloc_swap_folio(struct vm_fault *vmf)
* Get a list of all the (large) orders below PMD_ORDER that are enabled
* and suitable for swapping THP.
*/
- orders = thp_vma_allowable_orders(vma, vma->vm_flags, TVA_PAGEFAULT,
+ orders = thp_vma_allowable_orders(vma, TVA_PAGEFAULT,
BIT(PMD_ORDER) - 1);
orders = thp_vma_suitable_orders(vma, vmf->address, orders);
orders = thp_swap_suitable_orders(swp_offset(entry),
@@ -5107,7 +5107,7 @@ static struct folio *alloc_anon_folio(struct vm_fault *vmf)
* for this vma. Then filter out the orders that can't be allocated over
* the faulting address and still be fully contained in the vma.
*/
- orders = thp_vma_allowable_orders(vma, vma->vm_flags, TVA_PAGEFAULT,
+ orders = thp_vma_allowable_orders(vma, TVA_PAGEFAULT,
BIT(PMD_ORDER) - 1);
orders = thp_vma_suitable_orders(vma, vmf->address, orders);
@@ -5379,7 +5379,7 @@ vm_fault_t do_set_pmd(struct vm_fault *vmf, struct folio *folio, struct page *pa
* PMD mappings if THPs are disabled. As we already have a THP,
* behave as if we are forcing a collapse.
*/
- if (thp_disabled_by_hw() || vma_thp_disabled(vma, vma->vm_flags,
+ if (thp_disabled_by_hw() || vma_thp_disabled(vma,
/* forced_collapse=*/ true))
return ret;
@@ -6280,7 +6280,6 @@ static vm_fault_t __handle_mm_fault(struct vm_area_struct *vma,
.gfp_mask = __get_fault_gfp_mask(vma),
};
struct mm_struct *mm = vma->vm_mm;
- vm_flags_t vm_flags = vma->vm_flags;
pgd_t *pgd;
p4d_t *p4d;
vm_fault_t ret;
@@ -6295,7 +6294,7 @@ static vm_fault_t __handle_mm_fault(struct vm_area_struct *vma,
return VM_FAULT_OOM;
retry_pud:
if (pud_none(*vmf.pud) &&
- thp_vma_allowable_order(vma, vm_flags, TVA_PAGEFAULT, PUD_ORDER)) {
+ thp_vma_allowable_order(vma, TVA_PAGEFAULT, PUD_ORDER)) {
ret = create_huge_pud(&vmf);
if (!(ret & VM_FAULT_FALLBACK))
return ret;
@@ -6329,7 +6328,7 @@ static vm_fault_t __handle_mm_fault(struct vm_area_struct *vma,
goto retry_pud;
if (pmd_none(*vmf.pmd) &&
- thp_vma_allowable_order(vma, vm_flags, TVA_PAGEFAULT, PMD_ORDER)) {
+ thp_vma_allowable_order(vma, TVA_PAGEFAULT, PMD_ORDER)) {
ret = create_huge_pmd(&vmf);
if (!(ret & VM_FAULT_FALLBACK))
return ret;
diff --git a/mm/shmem.c b/mm/shmem.c
index b50ce7dbc84a..9549c780801a 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -1780,7 +1780,7 @@ unsigned long shmem_allowable_huge_orders(struct inode *inode,
vm_flags_t vm_flags = vma ? vma->vm_flags : 0;
unsigned int global_orders;
- if (thp_disabled_by_hw() || (vma && vma_thp_disabled(vma, vm_flags, shmem_huge_force)))
+ if (thp_disabled_by_hw() || (vma && vma_thp_disabled(vma, shmem_huge_force)))
return 0;
global_orders = shmem_huge_global_enabled(inode, index, write_end,
--
2.47.3
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v11 mm-new 03/10] mm: thp: add support for BPF based THP order selection
2025-10-20 3:10 [PATCH v11 mm-new 00/10] mm, bpf: BPF-MM, BPF-THP Yafang Shao
2025-10-20 3:10 ` [PATCH v11 mm-new 01/10] mm: thp: remove vm_flags parameter from khugepaged_enter_vma() Yafang Shao
2025-10-20 3:10 ` [PATCH v11 mm-new 02/10] mm: thp: remove vm_flags parameter from thp_vma_allowable_order() Yafang Shao
@ 2025-10-20 3:10 ` Yafang Shao
2025-10-20 3:10 ` [PATCH v11 mm-new 04/10] mm: thp: decouple THP allocation between swap and page fault paths Yafang Shao
3 siblings, 0 replies; 5+ messages in thread
From: Yafang Shao @ 2025-10-20 3:10 UTC (permalink / raw)
To: akpm, ast, daniel, andrii, martin.lau, eddyz87, song,
yonghong.song, john.fastabend, kpsingh, sdf, haoluo, jolsa,
david, ziy, lorenzo.stoakes, Liam.Howlett, npache, ryan.roberts,
dev.jain, hannes, usamaarif642, gutierrez.asier, willy,
ameryhung, rientjes, corbet, 21cnbao, shakeel.butt, tj,
lance.yang, rdunlap
Cc: bpf, linux-mm, linux-doc, linux-kernel, Yafang Shao
The Motivation
==============
This patch introduces a new BPF struct_ops called bpf_thp_ops for dynamic
THP tuning. It includes a hook bpf_hook_thp_get_order(), allowing BPF
programs to influence THP order selection based on factors such as:
- Workload identity
For example, workloads running in specific containers or cgroups.
- Allocation context
Whether the allocation occurs during a page fault, khugepaged, swap or
other paths.
- VMA's memory advice settings
MADV_HUGEPAGE or MADV_NOHUGEPAGE
- Memory pressure
PSI system data or associated cgroup PSI metrics
The BPF-THP Interface
=====================
The kernel API of this new BPF hook is as follows,
/**
* thp_get_order: Get the suggested THP order from a BPF program for allocation
* @vma: vm_area_struct associated with the THP allocation
* @type: TVA type for current @vma
* @orders: Bitmask of available THP orders for this allocation
*
* Return: The suggested THP order for allocation from the BPF program. Must be
* a valid, available order.
*/
int thp_get_order(struct vm_area_struct *vma,
enum tva_type type,
unsigned long orders);
This functionality is only active when system-wide THP is configured to
madvise or always mode. It remains disabled in never mode. Additionally,
if THP is explicitly disabled for a specific task via prctl(), this BPF
functionality will also be unavailable for that task.
The Design of Per Process BPF-THP
=================================
As suggested by Alexei, we need to scoping the BPF-THP [0].
Scoping BPF-THP to cgroup is not acceptible
-------------------------------------------
As explained by Gutierrez: [1]
1. It breaks the cgroup hierarchy when 2 siblings have different THP policies
2. Cgroup was designed for resource management not for grouping processes and
une those processes
3. We set a precedent for other people adding new flags to cgroup and
potentially polluting cgroups. We may end up with cgroups having tens of
different flags, making sysadmin's job more complex
Scoping BPF-THP to process
--------------------------
To eliminate potential conflicts among competing BPF-THP instances, we
enforce that each process is exclusively managed by a single BPF-THP. This
approach has received agreement from David [2].
When registering a BPF-THP, we specify the PID of a target task. The
BPF-THP is then installed in the task's `mm_struct`
struct mm_struct {
struct bpf_thp_ops __rcu *thp_thp;
};
Inheritance Behavior:
- Existing child processes are unaffected
- Newly forked children inherit the BPF-THP from their parent
- The BPF-THP persists across execve() calls
A new linked list tracks all tasks managed by each BPF-THP instance:
- Newly managed tasks are added to the list
- Exiting tasks are automatically removed from the list
- During BPF-THP unregistration (e.g., when the BPF link is removed), all
managed tasks have their bpf_thp pointer set to NULL
- BPF-THP instances can be dynamically updated, with all tracked tasks
automatically migrating to the new version.
This design simplifies BPF-THP management in production environments by
providing clear lifecycle management and preventing conflicts between
multiple BPF-THP instances.
WARNING
=======
This feature requires CONFIG_BPF_THP (EXPERIMENTAL) to be enabled. Note
that this capability is currently unstable and may undergo significant
changes—including potential removal—in future kernel versions.
Link: https://lore.kernel.org/linux-mm/CAADnVQJtrJZOCWZKH498GBA8M0mYVztApk54mOEejs8Wr3nSiw@mail.gmail.com/ [0]
Link: https://lore.kernel.org/linux-mm/1940d681-94a6-48fb-b889-cd8f0b91b330@huawei-partners.com/ [1]
Link: https://lore.kernel.org/linux-mm/3577f7fd-429a-49c5-973b-38174a67be15@redhat.com/ [2]
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
---
MAINTAINERS | 1 +
fs/exec.c | 1 +
include/linux/huge_mm.h | 40 +++++
include/linux/mm_types.h | 17 +++
kernel/fork.c | 1 +
mm/Kconfig | 22 +++
mm/Makefile | 1 +
mm/huge_memory_bpf.c | 314 +++++++++++++++++++++++++++++++++++++++
mm/mmap.c | 1 +
9 files changed, 398 insertions(+)
create mode 100644 mm/huge_memory_bpf.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 46126ce2f968..50faf3860a13 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16520,6 +16520,7 @@ F: include/linux/huge_mm.h
F: include/linux/khugepaged.h
F: include/trace/events/huge_memory.h
F: mm/huge_memory.c
+F: mm/huge_memory_bpf.c
F: mm/khugepaged.c
F: mm/mm_slot.h
F: tools/testing/selftests/mm/khugepaged.c
diff --git a/fs/exec.c b/fs/exec.c
index 6b70c6726d31..41d7703368e9 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -890,6 +890,7 @@ static int exec_mmap(struct mm_struct *mm)
activate_mm(active_mm, mm);
if (IS_ENABLED(CONFIG_ARCH_WANT_IRQS_OFF_ACTIVATE_MM))
local_irq_enable();
+ bpf_thp_retain_mm(mm, old_mm);
lru_gen_add_mm(mm);
task_unlock(tsk);
lru_gen_use_mm(mm);
diff --git a/include/linux/huge_mm.h b/include/linux/huge_mm.h
index 5e5f4a8d3c59..5c280ab0897d 100644
--- a/include/linux/huge_mm.h
+++ b/include/linux/huge_mm.h
@@ -269,6 +269,41 @@ unsigned long __thp_vma_allowable_orders(struct vm_area_struct *vma,
enum tva_type type,
unsigned long orders);
+#ifdef CONFIG_BPF_THP
+
+unsigned long
+bpf_hook_thp_get_orders(struct vm_area_struct *vma, enum tva_type type,
+ unsigned long orders);
+
+void bpf_thp_exit_mm(struct mm_struct *mm);
+void bpf_thp_retain_mm(struct mm_struct *mm, struct mm_struct *old_mm);
+void bpf_thp_fork(struct mm_struct *mm, struct mm_struct *old_mm);
+
+#else
+
+static inline unsigned long
+bpf_hook_thp_get_orders(struct vm_area_struct *vma, enum tva_type type,
+ unsigned long orders)
+{
+ return orders;
+}
+
+static inline void bpf_thp_exit_mm(struct mm_struct *mm)
+{
+}
+
+static inline void
+bpf_thp_retain_mm(struct mm_struct *mm, struct mm_struct *old_mm)
+{
+}
+
+static inline void
+bpf_thp_fork(struct mm_struct *mm, struct mm_struct *old_mm)
+{
+}
+
+#endif
+
/**
* thp_vma_allowable_orders - determine hugepage orders that are allowed for vma
* @vma: the vm area to check
@@ -290,6 +325,11 @@ unsigned long thp_vma_allowable_orders(struct vm_area_struct *vma,
{
vm_flags_t vm_flags = vma->vm_flags;
+ /* The BPF-specified order overrides which order is selected. */
+ orders &= bpf_hook_thp_get_orders(vma, type, orders);
+ if (!orders)
+ return 0;
+
/*
* Optimization to check if required orders are enabled early. Only
* forced collapse ignores sysfs configs.
diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index 4e5d59997e4a..0b4ac19e14ba 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -976,6 +976,19 @@ struct mm_cid {
};
#endif
+#ifdef CONFIG_BPF_THP
+struct bpf_thp_ops;
+#endif
+
+#ifdef CONFIG_BPF_MM
+struct bpf_mm_ops {
+#ifdef CONFIG_BPF_THP
+ struct bpf_thp_ops __rcu *bpf_thp;
+ struct list_head bpf_thp_list;
+#endif
+};
+#endif
+
/*
* Opaque type representing current mm_struct flag state. Must be accessed via
* mm_flags_xxx() helper functions.
@@ -1273,6 +1286,10 @@ struct mm_struct {
#ifdef CONFIG_MM_ID
mm_id_t mm_id;
#endif /* CONFIG_MM_ID */
+
+#ifdef CONFIG_BPF_MM
+ struct bpf_mm_ops bpf_mm;
+#endif
} __randomize_layout;
/*
diff --git a/kernel/fork.c b/kernel/fork.c
index 3da0f08615a9..dc24f3d012df 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -1130,6 +1130,7 @@ static inline void __mmput(struct mm_struct *mm)
exit_aio(mm);
ksm_exit(mm);
khugepaged_exit(mm); /* must run before exit_mmap */
+ bpf_thp_exit_mm(mm);
exit_mmap(mm);
mm_put_huge_zero_folio(mm);
set_mm_exe_file(mm, NULL);
diff --git a/mm/Kconfig b/mm/Kconfig
index e47321051d76..a0304c1f2fa8 100644
--- a/mm/Kconfig
+++ b/mm/Kconfig
@@ -1363,6 +1363,28 @@ config PT_RECLAIM
config FIND_NORMAL_PAGE
def_bool n
+menuconfig BPF_MM
+ bool "BPF-based Memory Management (EXPERIMENTAL)"
+ depends on BPF_SYSCALL
+
+ help
+ Enable BPF-based Memory Management Policy. This feature is currently
+ experimental.
+
+ WARNING: This feature is unstable and may change in future kernel
+
+if BPF_MM
+config BPF_THP
+ bool "BPF-based THP Policy (EXPERIMENTAL)"
+ depends on TRANSPARENT_HUGEPAGE && BPF_MM
+
+ help
+ Enable dynamic THP policy adjustment using BPF programs. This feature
+ is currently experimental.
+
+ WARNING: This feature is unstable and may change in future kernel
+endif # BPF_MM
+
source "mm/damon/Kconfig"
endmenu
diff --git a/mm/Makefile b/mm/Makefile
index 21abb3353550..4efca1c8a919 100644
--- a/mm/Makefile
+++ b/mm/Makefile
@@ -99,6 +99,7 @@ obj-$(CONFIG_MIGRATION) += migrate.o
obj-$(CONFIG_NUMA) += memory-tiers.o
obj-$(CONFIG_DEVICE_MIGRATION) += migrate_device.o
obj-$(CONFIG_TRANSPARENT_HUGEPAGE) += huge_memory.o khugepaged.o
+obj-$(CONFIG_BPF_THP) += huge_memory_bpf.o
obj-$(CONFIG_PAGE_COUNTER) += page_counter.o
obj-$(CONFIG_MEMCG_V1) += memcontrol-v1.o
obj-$(CONFIG_MEMCG) += memcontrol.o vmpressure.o
diff --git a/mm/huge_memory_bpf.c b/mm/huge_memory_bpf.c
new file mode 100644
index 000000000000..e8894c10d1d9
--- /dev/null
+++ b/mm/huge_memory_bpf.c
@@ -0,0 +1,314 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * BPF-based THP policy management
+ *
+ * Author: Yafang Shao <laoar.shao@gmail.com>
+ */
+
+#include <linux/bpf.h>
+#include <linux/btf.h>
+#include <linux/huge_mm.h>
+#include <linux/khugepaged.h>
+
+/**
+ * @thp_order_fn_t: Get the suggested THP order from a BPF program for allocation
+ * @vma: vm_area_struct associated with the THP allocation
+ * @type: TVA type for current @vma
+ * @orders: Bitmask of available THP orders for this allocation
+ *
+ * Return: The suggested THP order for allocation from the BPF program. Must be
+ * a valid, available order.
+ */
+typedef int thp_order_fn_t(struct vm_area_struct *vma,
+ enum tva_type type,
+ unsigned long orders);
+
+struct bpf_thp_ops {
+ pid_t pid; /* The pid to attach */
+ thp_order_fn_t *thp_get_order;
+
+ /* private */
+ /*The list of mm_struct objects managed by this BPF-THP instance. */
+ struct list_head mm_list;
+};
+
+static DEFINE_SPINLOCK(thp_ops_lock);
+
+unsigned long bpf_hook_thp_get_orders(struct vm_area_struct *vma,
+ enum tva_type type,
+ unsigned long orders)
+{
+ struct mm_struct *mm = vma->vm_mm;
+ struct bpf_thp_ops *bpf_thp;
+ int bpf_order;
+
+ if (!mm)
+ return orders;
+
+ rcu_read_lock();
+ bpf_thp = rcu_dereference(mm->bpf_mm.bpf_thp);
+ if (!bpf_thp || !bpf_thp->thp_get_order)
+ goto out;
+
+ bpf_order = bpf_thp->thp_get_order(vma, type, orders);
+ orders &= BIT(bpf_order);
+
+out:
+ rcu_read_unlock();
+ return orders;
+}
+
+void bpf_thp_exit_mm(struct mm_struct *mm)
+{
+ if (!rcu_access_pointer(mm->bpf_mm.bpf_thp))
+ return;
+
+ spin_lock(&thp_ops_lock);
+ if (!rcu_access_pointer(mm->bpf_mm.bpf_thp)) {
+ spin_unlock(&thp_ops_lock);
+ return;
+ }
+ list_del(&mm->bpf_mm.bpf_thp_list);
+ RCU_INIT_POINTER(mm->bpf_mm.bpf_thp, NULL);
+ spin_unlock(&thp_ops_lock);
+
+}
+
+void bpf_thp_retain_mm(struct mm_struct *mm, struct mm_struct *old_mm)
+{
+ struct bpf_thp_ops *bpf_thp;
+
+ if (!old_mm || !rcu_access_pointer(old_mm->bpf_mm.bpf_thp))
+ return;
+
+ spin_lock(&thp_ops_lock);
+ bpf_thp = rcu_dereference_protected(old_mm->bpf_mm.bpf_thp,
+ lockdep_is_held(&thp_ops_lock));
+ if (!bpf_thp) {
+ spin_unlock(&thp_ops_lock);
+ return;
+ }
+
+ /* The new mm_struct is under initialization. */
+ RCU_INIT_POINTER(mm->bpf_mm.bpf_thp, bpf_thp);
+
+ /* The old mm_struct is being destroyed. */
+ RCU_INIT_POINTER(old_mm->bpf_mm.bpf_thp, NULL);
+ list_replace(&old_mm->bpf_mm.bpf_thp_list, &mm->bpf_mm.bpf_thp_list);
+ spin_unlock(&thp_ops_lock);
+}
+
+void bpf_thp_fork(struct mm_struct *mm, struct mm_struct *old_mm)
+{
+ struct bpf_thp_ops *bpf_thp;
+
+ if (!rcu_access_pointer(old_mm->bpf_mm.bpf_thp))
+ return;
+
+ spin_lock(&thp_ops_lock);
+ bpf_thp = rcu_dereference_protected(old_mm->bpf_mm.bpf_thp,
+ lockdep_is_held(&thp_ops_lock));
+ if (!bpf_thp) {
+ spin_unlock(&thp_ops_lock);
+ return;
+ }
+
+ /* The new mm_struct is under initialization. */
+ RCU_INIT_POINTER(mm->bpf_mm.bpf_thp, bpf_thp);
+
+ list_add_tail(&mm->bpf_mm.bpf_thp_list, &bpf_thp->mm_list);
+ spin_unlock(&thp_ops_lock);
+}
+
+static bool bpf_thp_ops_is_valid_access(int off, int size,
+ enum bpf_access_type type,
+ const struct bpf_prog *prog,
+ struct bpf_insn_access_aux *info)
+{
+ return bpf_tracing_btf_ctx_access(off, size, type, prog, info);
+}
+
+static const struct bpf_func_proto *
+bpf_thp_get_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
+{
+ return bpf_base_func_proto(func_id, prog);
+}
+
+static const struct bpf_verifier_ops thp_bpf_verifier_ops = {
+ .get_func_proto = bpf_thp_get_func_proto,
+ .is_valid_access = bpf_thp_ops_is_valid_access,
+};
+
+static int bpf_thp_init(struct btf *btf)
+{
+ return 0;
+}
+
+static int bpf_thp_check_member(const struct btf_type *t,
+ const struct btf_member *member,
+ const struct bpf_prog *prog)
+{
+ /* The call site operates under RCU protection. */
+ if (prog->sleepable)
+ return -EINVAL;
+ return 0;
+}
+
+static int bpf_thp_init_member(const struct btf_type *t,
+ const struct btf_member *member,
+ void *kdata, const void *udata)
+{
+ const struct bpf_thp_ops *ubpf_thp;
+ struct bpf_thp_ops *kbpf_thp;
+ u32 moff;
+
+ ubpf_thp = (const struct bpf_thp_ops *)udata;
+ kbpf_thp = (struct bpf_thp_ops *)kdata;
+
+ moff = __btf_member_bit_offset(t, member) / 8;
+ switch (moff) {
+ case offsetof(struct bpf_thp_ops, pid):
+ /* bpf_struct_ops only handles func ptrs and zero-ed members.
+ * Return 1 to bypass the default handler.
+ */
+ kbpf_thp->pid = ubpf_thp->pid;
+ return 1;
+ }
+ return 0;
+}
+
+static int bpf_thp_reg(void *kdata, struct bpf_link *link)
+{
+ struct bpf_thp_ops *bpf_thp = kdata;
+ struct list_head *mm_list;
+ struct task_struct *p;
+ struct mm_struct *mm;
+ int err = -EINVAL;
+ pid_t pid;
+
+ pid = bpf_thp->pid;
+ p = find_get_task_by_vpid(pid);
+ if (!p)
+ return -ESRCH;
+
+ if (p->flags & PF_EXITING) {
+ put_task_struct(p);
+ return -ESRCH;
+ }
+
+ mm = get_task_mm(p);
+ put_task_struct(p);
+ if (!mm)
+ goto out;
+
+ err = -EBUSY;
+
+ /* To prevent conflicts, use this lock when multiple BPF-THP instances
+ * might register this task simultaneously.
+ */
+ spin_lock(&thp_ops_lock);
+ /* Each process is exclusively managed by a single BPF-THP. */
+ if (rcu_access_pointer(mm->bpf_mm.bpf_thp))
+ goto out_lock;
+ err = 0;
+ rcu_assign_pointer(mm->bpf_mm.bpf_thp, bpf_thp);
+
+ mm_list = &bpf_thp->mm_list;
+ INIT_LIST_HEAD(mm_list);
+ list_add_tail(&mm->bpf_mm.bpf_thp_list, mm_list);
+
+out_lock:
+ spin_unlock(&thp_ops_lock);
+out:
+ mmput(mm);
+ return err;
+}
+
+static void bpf_thp_unreg(void *kdata, struct bpf_link *link)
+{
+ struct bpf_thp_ops *bpf_thp = kdata;
+ struct bpf_mm_ops *bpf_mm;
+ struct list_head *pos, *n;
+
+ spin_lock(&thp_ops_lock);
+ list_for_each_safe(pos, n, &bpf_thp->mm_list) {
+ bpf_mm = list_entry(pos, struct bpf_mm_ops, bpf_thp_list);
+ WARN_ON_ONCE(!bpf_mm);
+ rcu_replace_pointer(bpf_mm->bpf_thp, NULL, lockdep_is_held(&thp_ops_lock));
+ list_del(pos);
+ }
+ spin_unlock(&thp_ops_lock);
+
+ synchronize_rcu();
+}
+
+static int bpf_thp_update(void *kdata, void *old_kdata, struct bpf_link *link)
+{
+ struct bpf_thp_ops *old_bpf_thp = old_kdata;
+ struct bpf_thp_ops *bpf_thp = kdata;
+ struct bpf_mm_ops *bpf_mm;
+ struct list_head *pos, *n;
+
+ INIT_LIST_HEAD(&bpf_thp->mm_list);
+
+ /* Could be optimized to a per-instance lock if this lock becomes a bottleneck. */
+ spin_lock(&thp_ops_lock);
+ list_for_each_safe(pos, n, &old_bpf_thp->mm_list) {
+ bpf_mm = list_entry(pos, struct bpf_mm_ops, bpf_thp_list);
+ WARN_ON_ONCE(!bpf_mm);
+ rcu_replace_pointer(bpf_mm->bpf_thp, bpf_thp, lockdep_is_held(&thp_ops_lock));
+ list_del(pos);
+ list_add_tail(&bpf_mm->bpf_thp_list, &bpf_thp->mm_list);
+ }
+ spin_unlock(&thp_ops_lock);
+
+ synchronize_rcu();
+ return 0;
+}
+
+static int bpf_thp_validate(void *kdata)
+{
+ struct bpf_thp_ops *ops = kdata;
+
+ if (!ops->thp_get_order) {
+ pr_err("bpf_thp: required ops isn't implemented\n");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int bpf_thp_get_order(struct vm_area_struct *vma,
+ enum tva_type type,
+ unsigned long orders)
+{
+ return -1;
+}
+
+static struct bpf_thp_ops __bpf_thp_ops = {
+ .thp_get_order = (thp_order_fn_t *)bpf_thp_get_order,
+};
+
+static struct bpf_struct_ops bpf_bpf_thp_ops = {
+ .verifier_ops = &thp_bpf_verifier_ops,
+ .init = bpf_thp_init,
+ .check_member = bpf_thp_check_member,
+ .init_member = bpf_thp_init_member,
+ .reg = bpf_thp_reg,
+ .unreg = bpf_thp_unreg,
+ .update = bpf_thp_update,
+ .validate = bpf_thp_validate,
+ .cfi_stubs = &__bpf_thp_ops,
+ .owner = THIS_MODULE,
+ .name = "bpf_thp_ops",
+};
+
+static int __init bpf_thp_ops_init(void)
+{
+ int err;
+
+ err = register_bpf_struct_ops(&bpf_bpf_thp_ops, bpf_thp_ops);
+ if (err)
+ pr_err("bpf_thp: Failed to register struct_ops (%d)\n", err);
+ return err;
+}
+late_initcall(bpf_thp_ops_init);
diff --git a/mm/mmap.c b/mm/mmap.c
index 644f02071a41..cf811e6678e3 100644
--- a/mm/mmap.c
+++ b/mm/mmap.c
@@ -1841,6 +1841,7 @@ __latent_entropy int dup_mmap(struct mm_struct *mm, struct mm_struct *oldmm)
vma_iter_free(&vmi);
if (!retval) {
mt_set_in_rcu(vmi.mas.tree);
+ bpf_thp_fork(mm, oldmm);
ksm_fork(mm, oldmm);
khugepaged_fork(mm, oldmm);
} else {
--
2.47.3
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v11 mm-new 04/10] mm: thp: decouple THP allocation between swap and page fault paths
2025-10-20 3:10 [PATCH v11 mm-new 00/10] mm, bpf: BPF-MM, BPF-THP Yafang Shao
` (2 preceding siblings ...)
2025-10-20 3:10 ` [PATCH v11 mm-new 03/10] mm: thp: add support for BPF based THP order selection Yafang Shao
@ 2025-10-20 3:10 ` Yafang Shao
3 siblings, 0 replies; 5+ messages in thread
From: Yafang Shao @ 2025-10-20 3:10 UTC (permalink / raw)
To: akpm, ast, daniel, andrii, martin.lau, eddyz87, song,
yonghong.song, john.fastabend, kpsingh, sdf, haoluo, jolsa,
david, ziy, lorenzo.stoakes, Liam.Howlett, npache, ryan.roberts,
dev.jain, hannes, usamaarif642, gutierrez.asier, willy,
ameryhung, rientjes, corbet, 21cnbao, shakeel.butt, tj,
lance.yang, rdunlap
Cc: bpf, linux-mm, linux-doc, linux-kernel, Yafang Shao
The new BPF capability enables finer-grained THP policy decisions by
introducing separate handling for swap faults versus normal page faults.
As highlighted by Barry:
We’ve observed that swapping in large folios can lead to more
swap thrashing for some workloads- e.g. kernel build. Consequently,
some workloads might prefer swapping in smaller folios than those
allocated by alloc_anon_folio().
While prtcl() could potentially be extended to leverage this new policy,
doing so would require modifications to the uAPI.
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
Reviewed-by: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
Acked-by: Usama Arif <usamaarif642@gmail.com>
Cc: Barry Song <21cnbao@gmail.com>
---
include/linux/huge_mm.h | 3 ++-
mm/huge_memory.c | 2 +-
mm/memory.c | 2 +-
3 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/include/linux/huge_mm.h b/include/linux/huge_mm.h
index 5c280ab0897d..56b360a08500 100644
--- a/include/linux/huge_mm.h
+++ b/include/linux/huge_mm.h
@@ -96,9 +96,10 @@ extern struct kobj_attribute thpsize_shmem_enabled_attr;
enum tva_type {
TVA_SMAPS, /* Exposing "THPeligible:" in smaps. */
- TVA_PAGEFAULT, /* Serving a page fault. */
+ TVA_PAGEFAULT, /* Serving a non-swap page fault. */
TVA_KHUGEPAGED, /* Khugepaged collapse. */
TVA_FORCED_COLLAPSE, /* Forced collapse (e.g. MADV_COLLAPSE). */
+ TVA_SWAP_PAGEFAULT, /* serving a swap page fault. */
};
#define thp_vma_allowable_order(vma, type, order) \
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 2ad35e5d225e..e105604868a5 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -102,7 +102,7 @@ unsigned long __thp_vma_allowable_orders(struct vm_area_struct *vma,
unsigned long orders)
{
const bool smaps = type == TVA_SMAPS;
- const bool in_pf = type == TVA_PAGEFAULT;
+ const bool in_pf = (type == TVA_PAGEFAULT || type == TVA_SWAP_PAGEFAULT);
const bool forced_collapse = type == TVA_FORCED_COLLAPSE;
unsigned long supported_orders;
vm_flags_t vm_flags = vma->vm_flags;
diff --git a/mm/memory.c b/mm/memory.c
index 8bb458de4fc0..7a242cb07d56 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -4558,7 +4558,7 @@ static struct folio *alloc_swap_folio(struct vm_fault *vmf)
* Get a list of all the (large) orders below PMD_ORDER that are enabled
* and suitable for swapping THP.
*/
- orders = thp_vma_allowable_orders(vma, TVA_PAGEFAULT,
+ orders = thp_vma_allowable_orders(vma, TVA_SWAP_PAGEFAULT,
BIT(PMD_ORDER) - 1);
orders = thp_vma_suitable_orders(vma, vmf->address, orders);
orders = thp_swap_suitable_orders(swp_offset(entry),
--
2.47.3
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2025-10-20 3:12 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-10-20 3:10 [PATCH v11 mm-new 00/10] mm, bpf: BPF-MM, BPF-THP Yafang Shao
2025-10-20 3:10 ` [PATCH v11 mm-new 01/10] mm: thp: remove vm_flags parameter from khugepaged_enter_vma() Yafang Shao
2025-10-20 3:10 ` [PATCH v11 mm-new 02/10] mm: thp: remove vm_flags parameter from thp_vma_allowable_order() Yafang Shao
2025-10-20 3:10 ` [PATCH v11 mm-new 03/10] mm: thp: add support for BPF based THP order selection Yafang Shao
2025-10-20 3:10 ` [PATCH v11 mm-new 04/10] mm: thp: decouple THP allocation between swap and page fault paths Yafang Shao
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox