Skip to content

Commit ca1036f

Browse files
mrpreKernel Patches Daemon
authored andcommitted
selftests/bpf: Add a test for arena fault-in under memory.max
A child joins a memcg capped at 64M and faults an arena in until it runs out of the budget. Without the kernel fix the child dies with SIGSEGV on a valid arena address; with it, the child is killed by the memcg OOM killer. With the fix: serial_test_arena_memcg:PASS:child killed by signal serial_test_arena_memcg:PASS:not killed by SIGSEGV #5 arena_memcg:OK # dmesg arena_vm_fault+0x655/0xa90 Memory cgroup out of memory: Killed process 512, file-rss:67920kB Without the fix: serial_test_arena_memcg:PASS:child killed by signal serial_test_arena_memcg:FAIL:not killed by SIGSEGV: actual 11 #5 arena_memcg:FAIL # dmesg test_progs[508]: segfault at 100004025000 ... Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
1 parent edc0616 commit ca1036f

2 files changed

Lines changed: 163 additions & 0 deletions

File tree

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
#include <test_progs.h>
4+
#include <fcntl.h>
5+
#include <signal.h>
6+
#include <sys/mman.h>
7+
#include <sys/wait.h>
8+
#include <unistd.h>
9+
#include <sys/user.h>
10+
#ifndef PAGE_SIZE /* on some archs it comes in sys/user.h */
11+
#include <unistd.h>
12+
#define PAGE_SIZE getpagesize()
13+
#endif
14+
15+
#include "cgroup_helpers.h"
16+
#include "arena_memcg.skel.h"
17+
18+
#define CG_PATH "/arena_memcg"
19+
20+
/* Budget the arena gets on top of whatever is already charged after load. */
21+
#define ARENA_BUDGET (64 * 1024 * 1024)
22+
23+
static void dump_memcg(int (*rd)(const char *, const char *, char *, size_t))
24+
{
25+
char buf[512];
26+
27+
/*
28+
* memory.current reads 0 once the child has left the cgroup, so it only
29+
* carries information when dumped from the live child; memory.peak and
30+
* memory.events survive the child and tell the story either way.
31+
*/
32+
if (!rd(CG_PATH, "memory.current", buf, sizeof(buf)))
33+
fprintf(stderr, "memory.current: %s", buf);
34+
if (!rd(CG_PATH, "memory.max", buf, sizeof(buf)))
35+
fprintf(stderr, "memory.max: %s", buf);
36+
if (!rd(CG_PATH, "memory.peak", buf, sizeof(buf)))
37+
fprintf(stderr, "memory.peak: %s", buf);
38+
if (!rd(CG_PATH, "memory.events", buf, sizeof(buf)))
39+
fprintf(stderr, "memory.events:\n%s", buf);
40+
fflush(NULL); /* _exit() in the child would not flush stdio otherwise */
41+
}
42+
43+
void serial_test_arena_memcg(void)
44+
{
45+
int cgroup_fd = -1, status;
46+
const long ps = PAGE_SIZE;
47+
char buf[64];
48+
pid_t pid;
49+
50+
if (setup_cgroup_environment())
51+
return;
52+
53+
cgroup_fd = create_and_get_cgroup(CG_PATH);
54+
if (!ASSERT_OK_FD(cgroup_fd, "create_and_get_cgroup"))
55+
goto out;
56+
57+
/* No memory controller -> nothing to test. */
58+
if (read_cgroup_file(CG_PATH, "memory.current", buf, sizeof(buf))) {
59+
test__skip();
60+
goto out;
61+
}
62+
63+
pid = fork();
64+
if (!ASSERT_GE(pid, 0, "fork"))
65+
goto out;
66+
if (pid == 0) {
67+
struct arena_memcg *cskel;
68+
__u32 i, npages;
69+
char *base;
70+
size_t sz;
71+
long cur;
72+
73+
/*
74+
* Do everything from the child: the arena vma is VM_DONTCOPY so
75+
* it would not survive fork(), only the child should be under the
76+
* limit so that a memcg OOM cannot pick test_progs, and a map is
77+
* charged to the memcg of the task that creates it - so join
78+
* before load. The cgroup work dir belongs to the parent that set
79+
* the environment up, so reach it with the _parent() helpers.
80+
* Errors are reported to the parent through the exit code, since
81+
* ASSERT_* in a forked child does not reach it.
82+
*/
83+
snprintf(buf, sizeof(buf), "%d", getpid());
84+
if (write_cgroup_file_parent(CG_PATH, "cgroup.procs", buf))
85+
_exit(2);
86+
87+
cskel = arena_memcg__open_and_load();
88+
if (!cskel)
89+
_exit(3);
90+
91+
base = bpf_map__initial_value(cskel->maps.arena, &sz);
92+
if (!base)
93+
_exit(4);
94+
npages = bpf_map__max_entries(cskel->maps.arena);
95+
96+
/*
97+
* Cap only now, after load: everything but the fault-in is
98+
* charged, so the arena gets a fixed budget regardless of what
99+
* the load itself cost, and the load can never hit the limit.
100+
*/
101+
if (read_cgroup_file_parent(CG_PATH, "memory.current", buf, sizeof(buf)))
102+
_exit(5);
103+
cur = strtol(buf, NULL, 10);
104+
snprintf(buf, sizeof(buf), "%ld", cur + ARENA_BUDGET);
105+
if (write_cgroup_file_parent(CG_PATH, "memory.max", buf))
106+
_exit(6);
107+
108+
for (i = 0; i < npages; i++)
109+
base[(size_t)i * ps] = 1;
110+
/* Faulted everything without dying: no pressure built, dump why. */
111+
dump_memcg(read_cgroup_file_parent);
112+
_exit(0);
113+
}
114+
115+
if (!ASSERT_EQ(waitpid(pid, &status, 0), pid, "waitpid"))
116+
goto out;
117+
118+
/* A non-zero exit means the child failed to set up; the code says where. */
119+
if (WIFEXITED(status) && WEXITSTATUS(status)) {
120+
ASSERT_OK(WEXITSTATUS(status), "child setup");
121+
goto out;
122+
}
123+
124+
/*
125+
* Faulting a valid arena address until memory.max is hit must not look
126+
* like an invalid access. Without the fix the fault path allocated with
127+
* the non-blocking allocator, turned its -ENOMEM into VM_FAULT_SIGSEGV,
128+
* and the child died with SIGSEGV on a valid address; now it is handled
129+
* by the memcg OOM path and the child is killed by SIGKILL instead.
130+
*/
131+
if (!ASSERT_TRUE(WIFSIGNALED(status), "child killed by signal"))
132+
goto out;
133+
if (!ASSERT_NEQ(WTERMSIG(status), SIGSEGV, "not killed by SIGSEGV"))
134+
dump_memcg(read_cgroup_file);
135+
out:
136+
if (cgroup_fd >= 0)
137+
close(cgroup_fd);
138+
cleanup_cgroup_environment();
139+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
#include <vmlinux.h>
4+
#include <bpf/bpf_helpers.h>
5+
#include "bpf_arena_common.h"
6+
7+
struct {
8+
__uint(type, BPF_MAP_TYPE_ARENA);
9+
__uint(map_flags, BPF_F_MMAPABLE);
10+
__uint(max_entries, 100000); /* number of pages */
11+
#ifdef __TARGET_ARCH_arm64
12+
__ulong(map_extra, 0x1ull << 32); /* start of mmap() region */
13+
#else
14+
__ulong(map_extra, 0x1ull << 44); /* start of mmap() region */
15+
#endif
16+
} arena SEC(".maps");
17+
18+
SEC("syscall")
19+
int noop(void *ctx)
20+
{
21+
return 0;
22+
}
23+
24+
char _license[] SEC("license") = "GPL";

0 commit comments

Comments
 (0)