* [PATCH bpf-next v4 0/4] bpf: arena: handle memory.max on fault-in with reclaim/OOM
@ 2026-08-21 5:01 Jiayuan Chen
2026-08-21 5:06 ` [PATCH bpf-next v4 1/4] bpf: Add a sleepable page allocator for map memory Jiayuan Chen
0 siblings, 1 reply; 2+ messages in thread
From: Jiayuan Chen @ 2026-08-21 5:01 UTC (permalink / raw)
To: bpf
Cc: Jiayuan Chen, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Ihor Solodrai, John Fastabend, Shuah Khan,
Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
linux-kernel, linux-kselftest, linux-rt-devel
Since commit e66fe1bc6d25 ("bpf: arena: Reintroduce memcg accounting"),
arena pages are charged to the memcg of the process that created the arena.
That accounting exposes two problems in the arena user page fault path.
1. The fault-in allocation runs under arena->spinlock, so it can only use the
non-blocking allocator, which never reclaims. Once memory.current is at
memory.max the allocation simply fails. Reaching memory.max is completely
normal for a healthy application - e.g. reading a large file fills
memory.current with page cache - so the process ends up killed for no real
reason.
2. That failure is turned into VM_FAULT_SIGSEGV, which is misleading: the
faulting address is a perfectly valid arena address. When we know it is an
out-of-memory condition we can return VM_FAULT_OOM and let the memcg OOM
path handle it properly.
Preallocate the page outside the lock (patch 2), the way do_anonymous_page()
does, so the allocation can sleep and go through reclaim and the OOM path.
This needs a sleepable allocator (patch 1), because can_alloc_pages() is a
conservative guess for BPF program context and always forces the non-blocking
allocator under PREEMPT_RT. patch 3&4 adds a selftest that faults an arena in
under a memory.max limit: without the fix the child gets SIGSEGV on a valid
address, with it the child is killed by the memcg OOM killer.
v3 -> v4:
- rebase bpf-next and fix conflict
- add Reviewed-by tag from Emil Tsalapatis
v2 -> v3:
- selftest: check the memcg OOM via memory.events "oom_kill" instead of
the exit signal; it only aims to pass on the fixed kernel, since the
unfixed SIGSEGV is racy.
v1 -> v2:
- Rebase on the separate deadlock fix (found by the Sashiko AI review),
now applied to bpf-next.
- Honor the map's NUMA node on fault-in.
- Return VM_FAULT_SIGBUS for the non-recoverable faults (lock, range-tree
and page-table failures); a scratch-page hole stays VM_FAULT_SIGSEGV
only under BPF_F_SEGV_ON_FAULT. (Kumar Kartikeya Dwivedi)
- Add read_cgroup_file() to cgroup_helpers instead of open-coding the
/mnt/... path in the test. (Emil Tsalapatis)
- Dump the cgroup memory stats on test failure to ease debugging.
v2:
https://lore.kernel.org/bpf/20260805091720.139924-1-jiayuan.chen@linux.dev/
v1:
https://lore.kernel.org/bpf/20260727062521.376231-1-jiayuan.chen@linux.dev/
Jiayuan Chen (4):
bpf: Add a sleepable page allocator for map memory
bpf: arena: allocate the fault-in page outside the lock
selftests/bpf: Add read_cgroup_file() to cgroup_helpers
selftests/bpf: Add a test for arena fault-in under memory.max
include/linux/bpf.h | 1 +
kernel/bpf/arena.c | 92 ++++++++---
kernel/bpf/syscall.c | 21 ++-
tools/testing/selftests/bpf/cgroup_helpers.c | 67 ++++++++
tools/testing/selftests/bpf/cgroup_helpers.h | 4 +
.../selftests/bpf/prog_tests/arena_memcg.c | 156 ++++++++++++++++++
.../testing/selftests/bpf/progs/arena_memcg.c | 24 +++
7 files changed, 340 insertions(+), 25 deletions(-)
create mode 100644 tools/testing/selftests/bpf/prog_tests/arena_memcg.c
create mode 100644 tools/testing/selftests/bpf/progs/arena_memcg.c
--
2.43.0
^ permalink raw reply [flat|nested] 2+ messages in thread
* [PATCH bpf-next v4 1/4] bpf: Add a sleepable page allocator for map memory
2026-08-21 5:01 [PATCH bpf-next v4 0/4] bpf: arena: handle memory.max on fault-in with reclaim/OOM Jiayuan Chen
@ 2026-08-21 5:06 ` Jiayuan Chen
0 siblings, 0 replies; 2+ messages in thread
From: Jiayuan Chen @ 2026-08-21 5:06 UTC (permalink / raw)
To: bpf
Cc: Jiayuan Chen, Emil Tsalapatis, Alexei Starovoitov,
Daniel Borkmann, John Fastabend, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Song Liu, Yonghong Song, Jiri Olsa, Ihor Solodrai,
Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
linux-kernel, linux-rt-devel
bpf_map_alloc_pages() picks the allocator via can_alloc_pages(), a
conservative guess for BPF program context that is always false under
PREEMPT_RT. So even a caller that really is sleepable gets the
non-blocking allocator, which never reclaims and never engages the OOM
machinery.
Add bpf_map_alloc_page_sleepable() for callers that know they are
sleepable. Like the other bpf map allocators it places the page on the
map's numa_node and does not follow the faulting task's NUMA mempolicy;
arena memory is shared, so the map's node is the right placement policy.
The next patch uses it from the arena page fault handler.
Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
---
To sashiko:
- The alloc_pages_nolock() fallback still zeroes: it forces __GFP_ZERO
internally and only accepts __GFP_ACCOUNT.
- __GFP_ZERO is unchanged from the existing __bpf_alloc_page(), and no
arena-capable arch has D-cache aliasing, so there is no dcache concern.
---
include/linux/bpf.h | 1 +
kernel/bpf/syscall.c | 21 +++++++++++++++++----
2 files changed, 18 insertions(+), 4 deletions(-)
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index ffa5626411ac..18e84ec4692d 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -2784,6 +2784,7 @@ struct bpf_prog *bpf_prog_get_curr_or_next(u32 *id);
int bpf_map_alloc_pages(const struct bpf_map *map, int nid,
unsigned long nr_pages, struct page **page_array);
+struct page *bpf_map_alloc_page_sleepable(const struct bpf_map *map);
#ifdef CONFIG_MEMCG
void bpf_map_memcg_enter(const struct bpf_map *map, struct mem_cgroup **old_memcg,
struct mem_cgroup **new_memcg);
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 6874ba1424af..f9b81638e537 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -602,15 +602,14 @@ static bool can_alloc_pages(void)
!IS_ENABLED(CONFIG_PREEMPT_RT);
}
+#define BPF_PAGE_GFP (GFP_KERNEL | __GFP_ZERO | __GFP_ACCOUNT | __GFP_NOWARN)
+
static struct page *__bpf_alloc_page(int nid)
{
if (!can_alloc_pages())
return alloc_pages_nolock(__GFP_ACCOUNT, nid, 0);
- return alloc_pages_node(nid,
- GFP_KERNEL | __GFP_ZERO | __GFP_ACCOUNT
- | __GFP_NOWARN,
- 0);
+ return alloc_pages_node(nid, BPF_PAGE_GFP, 0);
}
int bpf_map_alloc_pages(const struct bpf_map *map, int nid,
@@ -636,6 +635,20 @@ int bpf_map_alloc_pages(const struct bpf_map *map, int nid,
return ret;
}
+/*
+ * For callers that know they run in a sleepable context, e.g. a user page
+ * fault handler. can_alloc_pages() is a conservative guess made for BPF
+ * program context - notably it is always false on PREEMPT_RT - so going
+ * through bpf_map_alloc_pages() there would needlessly pick the
+ * non-blocking allocator, which never reclaims and never engages the OOM
+ * machinery.
+ */
+struct page *bpf_map_alloc_page_sleepable(const struct bpf_map *map)
+{
+ might_sleep();
+ return alloc_pages_node(map->numa_node, BPF_PAGE_GFP, 0);
+}
+
static int btf_field_cmp(const void *a, const void *b)
{
const struct btf_field *f1 = a, *f2 = b;
--
2.43.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-08-21 5:06 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-21 5:01 [PATCH bpf-next v4 0/4] bpf: arena: handle memory.max on fault-in with reclaim/OOM Jiayuan Chen
2026-08-21 5:06 ` [PATCH bpf-next v4 1/4] bpf: Add a sleepable page allocator for map memory Jiayuan Chen
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox