Re: [PATCH v3 0/7] mm/page_owner: add bpf_iter and kfuncs
From: zhen.ni
Date: Sat Oct 24 2026 - 22:18:02 EST
在 2026/10/25 03:08, Alexei Starovoitov 写道:
On Sat, Oct 24, 2026 at 10:19 AM zhen.ni <zhen.ni@xxxxxxxxxxxx> wrote:
For this one node alone, about 97% of spanned is holes. With
bpf_for(pfn, start, end) plus a bpf_page_owner_read() per iteration,
that is at least 16,712,944 kfunc calls, and bpf_for has no scheduling
point inside, and all of them are wasted work. The gap grows with memory
Right. spanned includes the holes. So don't pass them.
/sys/devices/system/memory/ has memoryN only for present blocks.
/sys/devices/system/node/nodeN/ links to the blocks of that node.
/proc/iomem has "System RAM" ranges.
Thanks for taking the time to review this.
From /sys/devices/system/memory/ you get a discrete list of memory
blocks. The prog (or userspace) still has to walk those blocks, handle
boundaries, and track progress. That is traversal logic that an iterator
already absorbs into a single next() call. Pushing it out just moves the
complexity; it doesn't eliminate it.
Therefore, this only demonstrates feasibility, not superiority.
523024 pages in a 32G node looks like 64K pages.
Then the whole 256G box is 4M pfns.
When a hole is passed anyway the call ends at !online_section() in
pfn_to_online_page(). /proc/kpageflags does that plus put_user()
for every pfn, holes included.
True, the kernel safely returns an error for offline PFNs. But paying a
kfunc call just to learn that a PFN is ineligible is exactly the waste
the iterator eliminates — the kernel-side cursor skips holes and
ineligible pages before the prog ever sees them.
Syscall progs run under migrate_disable() only.
Agreed, migrate_disable() pins the task to one CPU without disabling
preemption on PREEMPT_RT. But being preemptible doesn't mean the loop is
free — an 8M-iteration tight loop still consumes CPU, delays RCU grace
periods, and piles up softirq work. Skipping ineligible pages up front
reduces that pressure.
front. The spanned of this node is already 17.23M, so a single bpf_for
cannot cover it; you have to split it into N chunks and poll, with the
chunking state kept in a prog map, i.e. a loop nested in a loop.
The loop over chunks is in user space. One bpf_prog_test_run_opts()
per range with start and nr in ctx_in. No map, no nested loop.
The outer loop being in userspace doesn't remove the chunking burden —
it just relocates it. Userspace still has to parse sysfs, construct
ranges, issue one syscall per chunk, read the map, accumulate results
into a global aggregate, clear the map, and handle partial failures. And
in the snippet, i is an offset within the chunk while start and pfn are
64-bit — the chunking arithmetic itself is non-trivial. An iterator
absorbs all of this into next().
for 52-bit PA. A 32-bit PFN range will overflow here.
The snippet was:
bpf_for(i, 0, ctx->nr) {
pfn = ctx->start + i;
i is an offset within the chunk. start and pfn are 64-bit.
Solving all of this requires yet another, more complex kfunc—and it
looks increasingly like bpf iter. So why not just use the iter?
Nothing of the above needs kernel code.
Feasibility isn't in question. The question is whether we want every
prog writer to reinvent this traversal plumbing, or let the kernel do
what iterators are designed for.
v3 is +474 -83 lines on the kernel side. The diff I sent is +55 -1
and it doesn't touch the debugfs path.
That diff includes my improvements to the page_owner infrastructure, so
the line count doesn't really establish that bpf_for is better than
iter. The iter approach only adds an exit on the debugfs path — thin
shells over the single page_owner_next_eligible() extracted from
read_page_owner(). It changes no logic on the debugfs path (aside from
the mem-hotplug locking fix, which addresses a pre-existing bug in the
debugfs interface).
And the fact that the bpf_for approach doesn't touch the debugfs path
is, if anything, a disadvantage: it means two sets of eligibility rules
would have to be maintained going forward.
With the iter approach, any future change only needs to be made in
page_owner_next_eligible().
Thanks,
Zhen