Re: [PATCH v3 0/7] mm/page_owner: add bpf_iter and kfuncs

From: Alexei Starovoitov

Date: Wed Oct 21 2026 - 18:00:54 EST


On Wed, Oct 21, 2026 at 04:24 AM Zhen Ni <zhen.ni@xxxxxxxxxxxx> wrote:
> Exposing the same walk to eBPF lets a program filter and aggregate in
> the kernel and surface only the records it cares about. This series
> adds that access as a seq_file bpf_iter target and an open-coded
> iterator.

This is overkill. Two iterators, two kfuncs that print strings,
a rewrite of read_page_owner(), a new file and a new header.

All that the prog needs from the kernel is the page_owner record
of a pfn. That's one kfunc. The walk, the eligibility rules and
the filters belong in the bpf prog:

bpf_for(i, 0, ctx->nr) {
pfn = ctx->start + i;
if (bpf_page_owner_read(pfn, &po) ||
pfn & ((1ul << po.order) - 1))
continue;
if (po.pid != pid || bpf_page_cgroup_ino(pfn) != cgrp)
continue;
/* add 1 << po.order to a hash map keyed by po.handle */
}


Something like this:

diff --git a/mm/bpf_memcontrol.c b/mm/bpf_memcontrol.c
index d8f579c..3adb716 100644
--- a/mm/bpf_memcontrol.c
+++ b/mm/bpf_memcontrol.c
@@ -161,6 +161,20 @@ __bpf_kfunc void bpf_mem_cgroup_flush_stats(struct mem_cgroup *memcg)
mem_cgroup_flush_stats(memcg);
}

+/**
+ * bpf_page_cgroup_ino - Get the cgroup a page is charged to
+ * @pfn: the page
+ *
+ * Return: What /proc/kpagecgroup has for @pfn: the inode number of the closest
+ * online ancestor of the memory cgroup, 0 if the page is not charged.
+ */
+__bpf_kfunc u64 bpf_page_cgroup_ino(unsigned long pfn)
+{
+ struct page *page = pfn_to_online_page(pfn);
+
+ return page ? page_cgroup_ino(page) : 0;
+}
+
/**
* bpf_proactive_reclaim - proactively reclaim memory from a memory cgroup
* @memcg: the target memory cgroup to reclaim from.
@@ -218,6 +232,7 @@ BTF_ID_FLAGS(func, bpf_mem_cgroup_memory_events)
BTF_ID_FLAGS(func, bpf_mem_cgroup_usage)
BTF_ID_FLAGS(func, bpf_mem_cgroup_page_state)
BTF_ID_FLAGS(func, bpf_mem_cgroup_flush_stats, KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_page_cgroup_ino, KF_PERFMON)

BTF_KFUNCS_END(bpf_memcontrol_kfuncs)

diff --git a/mm/page_owner.c b/mm/page_owner.c
index fbbda7b..a26d7fe 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -12,6 +12,7 @@
#include <linux/seq_file.h>
#include <linux/memcontrol.h>
#include <linux/sched/clock.h>
+#include <linux/btf.h>

#include "page_alloc.h"

@@ -1144,6 +1145,44 @@ static int threshold_set(void *data, u64 val)

DEFINE_SIMPLE_ATTRIBUTE(threshold_fops, &threshold_get, &threshold_set, "%llu\n");

+__bpf_kfunc_start_defs();
+
+/**
+ * bpf_page_owner_read - Copy the page_owner record of an allocated page
+ * @pfn: the page
+ * @po__uninit: where to copy the record to
+ *
+ * All pages of a high-order allocation have the same record.
+ *
+ * Return: 0 on success, -ENOENT if @pfn is not online or not allocated.
+ */
+__bpf_kfunc int bpf_page_owner_read(unsigned long pfn, struct page_owner *po__uninit)
+{
+ struct page *page = pfn_to_online_page(pfn);
+ struct page_ext *page_ext = page ? page_ext_get(page) : NULL;
+
+ if (!page_ext || !test_bit(PAGE_EXT_OWNER_ALLOCATED, &page_ext->flags)) {
+ page_ext_put(page_ext);
+ memset(po__uninit, 0, sizeof(*po__uninit));
+ return -ENOENT;
+ }
+
+ *po__uninit = *get_page_owner(page_ext);
+ page_ext_put(page_ext);
+ return 0;
+}
+
+__bpf_kfunc_end_defs();
+
+BTF_KFUNCS_START(page_owner_kfuncs)
+BTF_ID_FLAGS(func, bpf_page_owner_read, KF_PERFMON)
+BTF_KFUNCS_END(page_owner_kfuncs)
+
+static const struct btf_kfunc_id_set page_owner_kfunc_set = {
+ .owner = THIS_MODULE,
+ .set = &page_owner_kfuncs,
+};
+
static int __init pageowner_init(void)
{
struct dentry *dir;
@@ -1169,6 +1208,6 @@ static int __init pageowner_init(void)
&stack_fops);
debugfs_create_file("count_threshold", 0600, dir, NULL,
&threshold_fops);
- return 0;
+ return register_btf_kfunc_id_set(BPF_PROG_TYPE_UNSPEC, &page_owner_kfunc_set);
}
late_initcall(pageowner_init)

pw-bot: cr